DoubleToString
Moderator: Committer
DoubleToString
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
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
- Geoff the Medio
- Programming, Design, Admin
- Posts: 13587
- Joined: Wed Oct 08, 2003 1:33 am
- Location: Munich
Re: DoubleToString
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.
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.
Re: DoubleToString
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.
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.
- Geoff the Medio
- Programming, Design, Admin
- Posts: 13587
- Joined: Wed Oct 08, 2003 1:33 am
- Location: Munich
Re: DoubleToString
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.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?
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.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.
Re: DoubleToString
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 sayGeoff 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.
Code: Select all
if (value < A_SMALL_NUMBER) { return "0.00"; }
There is however a problem with the "prefix" flag: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.
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.
- Geoff the Medio
- Programming, Design, Admin
- Posts: 13587
- Joined: Wed Oct 08, 2003 1:33 am
- Location: Munich
Re: DoubleToString
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.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.
It's not that simple because you still need to return the correct number of digits of 0.Thus, if it is not important, we can save ourselves the trouble of converting the number and simply sayCode: Select all
if (value < A_SMALL_NUMBER) { return "0.00"; }
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.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)?
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).
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.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.
Re: DoubleToString
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.
An example of the output is:
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?
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);
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
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
- Geoff the Medio
- Programming, Design, Admin
- Posts: 13587
- Joined: Wed Oct 08, 2003 1:33 am
- Location: Munich
Re: DoubleToString
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().
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().
Re: DoubleToString
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.
Re: DoubleToString
Ok.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 +.
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.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.
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?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 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?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().
Thanks
- Geoff the Medio
- Programming, Design, Admin
- Posts: 13587
- Joined: Wed Oct 08, 2003 1:33 am
- Location: Munich
Re: DoubleToString
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).francys wrote: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?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.
Upon further investigation, I think you'd actually need <cmath>.As far as I can see, <algorithm> has the min() and max() functions but lacks pow() and log10().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 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.Also, I am not sure how abs() works.. isn't it for integers while fabs() is for floating-point numbers?Regardless, you should probably be using std::abs() instead of fabs().
Re: DoubleToString
Here's an implementation with boost.
- Attachments
-
- doubletostring.tar.gz
- (3.26 KiB) Downloaded 157 times
- Geoff the Medio
- Programming, Design, Admin
- Posts: 13587
- Joined: Wed Oct 08, 2003 1:33 am
- Location: Munich
Re: DoubleToString
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.
Re: DoubleToString
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.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.
- Geoff the Medio
- Programming, Design, Admin
- Posts: 13587
- Joined: Wed Oct 08, 2003 1:33 am
- Location: Munich
Re: DoubleToString
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.francys wrote:I feel somewhat reluctant to uninstall official debian packages and compile&install newer versions by hand.