DoubleToString

Programmers discuss here anything related to FreeOrion programming. Primarily for the developers to discuss.

Moderator: Committer

Message
Author
User avatar
francys
Space Squid
Posts: 54
Joined: Sat Oct 25, 2008 8:53 pm
Location: Victoria, BC

DoubleToString

#1 Post by francys »

Hi again

I was looking at the DoubleToString function in ClientUI.cpp, as listed in Programming Work. I was wondering whether the requirements of the function are still the same. For instance, should milli, micro, nano prefixes be used, or such numbers always returned as 0? Or, how many digits should be shown? Is it important to show a specific number of digits or can default choices be made?

I hope this is not confusing :?

User avatar
Geoff the Medio
Programming, Design, Admin
Posts: 13587
Joined: Wed Oct 08, 2003 1:33 am
Location: Munich

Re: DoubleToString

#2 Post by Geoff the Medio »

I suppose it would be nice if DoubleToString retained the ability to handles powers of ten much less than 1 (as in 10^0) by using milli, micro and nano prefixes, but if it just rendered all positive numbers that are too small to have any nonzero digits as 0.00... to as many digits as are requested, that'd be fine.

For example, if asked for three digits:
given 26325 render 2.63k
given 1.5233 render 1.52
given 0.0124 render 0.01
given 0.0007 render 0.01
given 0.0004 render 0.00

It should still take a parameter for the number of digits to render... Doing that exactly as desired is the main reason the function exists.

I don't know what you mean by "default choices"... if 3 digits are requested, then show 3 digits. If 5, show 5.

You could add a default value for the number of digits parameter, I suppose, but if another value is supplied, the output should have the requested number of digits. Different places in the UI will want different numbers of digits, and that should be supported.

User avatar
francys
Space Squid
Posts: 54
Joined: Sat Oct 25, 2008 8:53 pm
Location: Victoria, BC

Re: DoubleToString

#3 Post by francys »

Ok.
In my DoubleToString function, I am using prefixes from k up to G, and from m down to n. Using your example, when asked for 3 digits, it returns

26.3k
1.52
12.4m
700u
400u

If 700 u is too small for practical use, there could be a lower limit such that for every number less than the limit, 0.0 (with the correct number of digits) is returned. If however we like precision, we could use the prefixes. What's the choice?

I am also using another flag (for testing purposes at least), which specifies whether to use a prefix. If set to false, 700 u would become 0.00.

User avatar
Geoff the Medio
Programming, Design, Admin
Posts: 13587
Joined: Wed Oct 08, 2003 1:33 am
Location: Munich

Re: DoubleToString

#4 Post by Geoff the Medio »

francys wrote:If 700 u is too small for practical use, there could be a lower limit such that for every number less than the limit, 0.0 (with the correct number of digits) is returned. If however we like precision, we could use the prefixes. What's the choice?
I'm not sure what you're asking. At some point, you'll run out of prefixes and have to return 0.00... there's no way to avoid this, other than adding prefixes until the numeric limits of double, which is probably impractical and not very useful.
I am also using another flag (for testing purposes at least), which specifies whether to use a prefix. If set to false, 700 u would become 0.00.
That flag should probably replace the integerize flag, which does something similar, but which we probably won't use if we have a way to disable power of ten indicators of m and below.

User avatar
francys
Space Squid
Posts: 54
Joined: Sat Oct 25, 2008 8:53 pm
Location: Victoria, BC

Re: DoubleToString

#5 Post by francys »

Geoff the Medio wrote:I'm not sure what you're asking. At some point, you'll run out of prefixes and have to return 0.00... there's no way to avoid this, other than adding prefixes until the numeric limits of double, which is probably impractical and not very useful.
I meant something like that, yes. Eventually 0.00 will be returned. So my question is, in gameplay, is it really important to distinguish between 1 n and 0? What about 1 u and 0? Knowing that growth is 0.000000001, to me as a user at least, seems useless. Thus, if it is not important, we can save ourselves the trouble of converting the number and simply say

Code: Select all

if (value < A_SMALL_NUMBER) { return "0.00"; }
That flag should probably replace the integerize flag, which does something similar, but which we probably won't use if we have a way to disable power of ten indicators of m and below.
There is however a problem with the "prefix" flag:
If prefix is false (so k, M, G, and m, u n will not be displayed), and we want 2 digits, then there is no way to display a number like 153106.
So, shouldn't the prefix flag always be true (prefixes always used)?

As for integerize, at the moment I am using integerize to drop the decimal digits regardless of what they are: they are dropped in the number 1.53, which becomes 1, and in 3.64M, which becomes 3M.

User avatar
Geoff the Medio
Programming, Design, Admin
Posts: 13587
Joined: Wed Oct 08, 2003 1:33 am
Location: Munich

Re: DoubleToString

#6 Post by Geoff the Medio »

francys wrote:So my question is, in gameplay, is it really important to distinguish between 1 n and 0? What about 1 u and 0? Knowing that growth is 0.000000001, to me as a user at least, seems useless.
Indeed it is useless... and it's almost as useless to know that growth is 0.001, which is why I've suggested rewriting the function so that there's a way to tell it not to show numbers smaller than can fit in the available digits without a less-than-1 prefix. So, if asked for 4 digits, show 0.001 for values of 0.0005 to 0.000149999... and show 0.000 for values of 0.000049999... and below.
Thus, if it is not important, we can save ourselves the trouble of converting the number and simply say

Code: Select all

if (value < A_SMALL_NUMBER) { return "0.00"; }
It's not that simple because you still need to return the correct number of digits of 0.
There is however a problem with the "prefix" flag:
If prefix is false (so k, M, G, and m, u n will not be displayed), and we want 2 digits, then there is no way to display a number like 153106.
So, shouldn't the prefix flag always be true (prefixes always used)?
There should not be a general "prefix" flag. There should only be a flag to disable prefixes for powers of 10 less than a threshold. The simplest implementation would be a binary setting that disables prefixes m, mu, n and others below 10^0. Thus, if asked for 3 digits, you'd show 10.0, 0.10 or 0.01 but show 0.00 if the magnitude of the number fell below 0.005.

A more complicated, but probably not much more useful option would be to have a setting to specifiy what power of 10 below which to not use any more prefixes. If this was -3, then you'd show 34m or 0.04m but 0.00 if the value dropped below 0.0005 (at which the rounding would give 0.001 but below which the rounding should give 0.000).

It's probably fine to have 3 digits be the minumum allowed, since displaying 0.5k (2 digits) when the value is 500 (3 digits written out) is awkward. Feel free to make the minimum 2, though. (1 is impossible to implement usefully).
As for integerize, at the moment I am using integerize to drop the decimal digits regardless of what they are: they are dropped in the number 1.53, which becomes 1, and in 3.64M, which becomes 3M.
The idea of integerize is to round to the nearest whole number. 1.53 should be rounded to 2.00 and 3640000 should be rounded to 3640000. You'd then display 2 or 3.64M. No digits would be used to show values to precision less than multiples of 1, but when using prefixes of k or M, you'd still show decimals in the number since the actual value of those digits is more than 1. That is, in 1.52k the actual value of the 0.02 is 20, which is more than 1, so is shown.

User avatar
francys
Space Squid
Posts: 54
Joined: Sat Oct 25, 2008 8:53 pm
Location: Victoria, BC

Re: DoubleToString

#7 Post by francys »

Alright. Attached is a simple implementation of the DoubleToString function. It's in its own file right now as this allows easier testing.

The function lets the user specify a value, how many digits to display, whether to make the value an integer, whether to show the sign, and until what power of 10 to show units.

Code: Select all

std::string DoubleToString(double val, int digits, bool integerize, bool showsign, int minpower);
An example of the output is:

Code: Select all

DoubleToString(26325, 3, false, false, 0); //    returns 26.3k
DoubleToString(-0.004, 4, false, false, -10); // returns -4.000m
DoubleToString(-0.004, 4, false, false, -2); //  returns -0.004
The sign is shown, even though showsign is false, since the value is negative. Showsign=true shows the plus sign for positive numbers.

There is still a TODO in the code, referring to values that are too big or too small for this double-to-string conversion.
At the moment the function returns the "placeholder" values + inf and - inf, but something more elegant could be used.

What do you think?
Attachments
doubletostring.tar.gz
(1.71 KiB) Downloaded 162 times

User avatar
Geoff the Medio
Programming, Design, Admin
Posts: 13587
Joined: Wed Oct 08, 2003 1:33 am
Location: Munich

Re: DoubleToString

#8 Post by Geoff the Medio »

You're going to need to rewrite that without using sprintf. FreeOrion is written in C++, not C. You can do this using boost::format as is done in the current DoubleToString.

Use the UTF-8 μ (not u) for the micro prefix.

Keep the check that's there now for the UNKNOWN_UI_DISPLAY_VALUE and LARGE_UI_DISPLAY_VALUE.

If the displayed number is 0.000 and showsign is true, show + or - if the actual full-precision value of the number is positive or negative (before rounding or integerizing). If it's actually 0, then show +.

For values with magnitude greater than can be displayed, show 9999.99999 etc. Alternatively, show UserString("INFINITY_SYMBOL") and put something appropriate into the stringtable. "Inf" probably isn't language-independent.

It doesn't look like you're rounding properly for the last displayed digit. Add 0.5 times the value of 1 in the last displayed digit to the number, multiply by the power of 10 to put the last displayed digit into the 1's place, floor the result, and divide by the same value. Perhaps do some tests to be sure that floating point rounding doesn't cause problems despite these efforts.

Also be careful about cases where rounding might mess up previously determined numbers of digits... I added a comment in the current DoubleToString about rounding down at the last digit to ensure that 0.998 wasn't rounded to 1.00 and then shown as 1.00 when asked for two digits, since it had previously been determined that the 0.01 digit was the smallest needed to display a number less than 1 to two digits.

You can probably get whatever you need from <math.h> from the C++ header <algorithm>, but neither seems to be included in ClientUI.cpp where the current DoubleToString is located. Regardless, you should probably be using std::abs() instead of fabs().

User avatar
kroddn
Static Linker
Posts: 347
Joined: Thu Jun 28, 2007 10:28 am

Re: DoubleToString

#9 Post by kroddn »

The very small numbers should be separated in "very small" and "zero" numbers. So, maybe show "> 0.0" for non-zero and "0" for exactly zero values. The user should be able to recognize that there is a very small value on the one hand and that there is no value at all on the other hand.

User avatar
francys
Space Squid
Posts: 54
Joined: Sat Oct 25, 2008 8:53 pm
Location: Victoria, BC

Re: DoubleToString

#10 Post by francys »

Geoff the Medio wrote:You're going to need to rewrite that without using sprintf. FreeOrion is written in C++, not C. You can do this using boost::format as is done in the current DoubleToString.

Use the UTF-8 μ (not u) for the micro prefix.

Keep the check that's there now for the UNKNOWN_UI_DISPLAY_VALUE and LARGE_UI_DISPLAY_VALUE.

If the displayed number is 0.000 and showsign is true, show + or - if the actual full-precision value of the number is positive or negative (before rounding or integerizing). If it's actually 0, then show +.
Ok.
For values with magnitude greater than can be displayed, show 9999.99999 etc. Alternatively, show UserString("INFINITY_SYMBOL") and put something appropriate into the stringtable. "Inf" probably isn't language-independent.
In the current implementation, numbers greater than a certain limit (LARGE_UI_DISPLAY_VALUE) are set to that limit, and then shown. I'll do the same.
It doesn't look like you're rounding properly for the last displayed digit. Add 0.5 times the value of 1 in the last displayed digit to the number, multiply by the power of 10 to put the last displayed digit into the 1's place, floor the result, and divide by the same value. Perhaps do some tests to be sure that floating point rounding doesn't cause problems despite these efforts.

Also be careful about cases where rounding might mess up previously determined numbers of digits... I added a comment in the current DoubleToString about rounding down at the last digit to ensure that 0.998 wasn't rounded to 1.00 and then shown as 1.00 when asked for two digits, since it had previously been determined that the 0.01 digit was the smallest needed to display a number less than 1 to two digits.
I am not sure what you mean. What is the problem with showing 1.0 when asked for two digits? If you mean 0.9 or 0.99 should be returned, they are further from the original value than 1.0.. so what rounding are you talking about?
You can probably get whatever you need from <math.h> from the C++ header <algorithm>, but neither seems to be included in ClientUI.cpp where the current DoubleToString is located. Regardless, you should probably be using std::abs() instead of fabs().
I don't think so. As far as I can see, <algorithm> has the min() and max() functions but lacks pow() and log10(). Also, I am not sure how abs() works.. isn't it for integers while fabs() is for floating-point numbers?

Thanks

User avatar
Geoff the Medio
Programming, Design, Admin
Posts: 13587
Joined: Wed Oct 08, 2003 1:33 am
Location: Munich

Re: DoubleToString

#11 Post by Geoff the Medio »

francys wrote:
I added a comment in the current DoubleToString about rounding down at the last digit to ensure that 0.998 wasn't rounded to 1.00 and then shown as 1.00 when asked for two digits, since it had previously been determined that the 0.01 digit was the smallest needed to display a number less than 1 to two digits.
I am not sure what you mean. What is the problem with showing 1.0 when asked for two digits? If you mean 0.9 or 0.99 should be returned, they are further from the original value than 1.0.. so what rounding are you talking about?
1.00 has three digits, not two. It should be 1.0 (better) or 0.99 (user won't know the difference, and it has the right number of significant digits).
You can probably get whatever you need from <math.h> from the C++ header <algorithm>, but neither seems to be included in ClientUI.cpp where the current DoubleToString is located.
As far as I can see, <algorithm> has the min() and max() functions but lacks pow() and log10().
Upon further investigation, I think you'd actually need <cmath>.
Regardless, you should probably be using std::abs() instead of fabs().
Also, I am not sure how abs() works.. isn't it for integers while fabs() is for floating-point numbers?
As far as I can tell, in C++ (<cmath>, not <math.h>) std::abs has overloads for various types of input, including int and double. You probably could also call std::fabs for doubles, though.

User avatar
francys
Space Squid
Posts: 54
Joined: Sat Oct 25, 2008 8:53 pm
Location: Victoria, BC

Re: DoubleToString

#12 Post by francys »

Here's an implementation with boost.
Attachments
doubletostring.tar.gz
(3.26 KiB) Downloaded 157 times

User avatar
Geoff the Medio
Programming, Design, Admin
Posts: 13587
Joined: Wed Oct 08, 2003 1:33 am
Location: Munich

Re: DoubleToString

#13 Post by Geoff the Medio »

I'll take a look at this when I get a chance. I'm presently trying to get the MSVC project files working again after the recent major changes... Ogre isn't playing nice, unfortunately.

User avatar
francys
Space Squid
Posts: 54
Joined: Sat Oct 25, 2008 8:53 pm
Location: Victoria, BC

Re: DoubleToString

#14 Post by francys »

Geoff the Medio wrote:I'll take a look at this when I get a chance. I'm presently trying to get the MSVC project files working again after the recent major changes... Ogre isn't playing nice, unfortunately.
Oh I know. I'm having the same issues, and I feel somewhat reluctant to uninstall official debian packages and compile&install newer versions by hand.

User avatar
Geoff the Medio
Programming, Design, Admin
Posts: 13587
Joined: Wed Oct 08, 2003 1:33 am
Location: Munich

Re: DoubleToString

#15 Post by Geoff the Medio »

francys wrote:I feel somewhat reluctant to uninstall official debian packages and compile&install newer versions by hand.
Unfortunately it's not practical to limit our library dependency versions to those in (popular) distros' repositories. Ogre and bullet shouldn't be too hard to build from source, I imagine.

Post Reply