0

I am creating a float point control where you can increment by 10, 100, 1000 etc.

The text I display in the control is myFloatNumber.ToString("E");

as I was increasing a number I noticed:

1.0E+1f  =  1.0E+1f
1.0E+2f  =  1.0E+2f
1.0E+3f  =  1.0E+3f
......
......
1.0E+25f  =  1.0E+25f
1.0E+26f  =  1.0E+26f
1.0E+27f  =  1.0E+27f
1.0E+28f  =  9.999999E+27f   // Why!!?????

The solution to this problem is to use a double but I am very curios as to why is this behavior?


here is the code:

        string str1 = (1.0E27f).ToString();  // str1 = "1E+27"
        string str2 = (1.0E28f).ToString();  // str2 = "9.999999E+27";
        string str3 = (1.0E29f).ToString();  // str3 = "1E+29"
Tono Nam
  • 34,064
  • 78
  • 298
  • 470
  • 4
    Because `float` doesn't have enough precision. – SLaks Feb 04 '14 at 04:04
  • Yeah you can have `1.0E31f` for instance. In other words `float.MaxValue > 1.0E28f` – Tono Nam Feb 04 '14 at 04:05
  • 2
    Double is not a solution. It will have same problems with precision. – x2. Feb 04 '14 at 04:07
  • Maximum value of float is `3.40282347E+38` it def has precision to hold `1.0E+28` – Tono Nam Feb 04 '14 at 04:10
  • What if you do .ToString("G9")? The doc says: "By default, the return value only contains 7 digits of precision although a maximum of 9 digits is maintained internally." http://msdn.microsoft.com/en-us/library/f71z6k0c(v=vs.110).aspx – MaximR Feb 04 '14 at 05:41
  • Yeah, probably has to do with internal representation losing precision. If you do "E9" it "degrades" earlier, but such is the nature of floats. – MaximR Feb 04 '14 at 05:58
  • 1
    Are you actually evaluating `(1.0E+28f).ToString()` as shown in the title, or are you producing 1e28 by other calculations and then displaying it? If the latter, there are rounding errors in the calculations that make the final value differ slightly from `1e28f`. You should show actual code that reproduces the problem, complete code that other people can execute. – Eric Postpischil Feb 04 '14 at 10:46
  • 1
    You need to learn about _precision_. http://en.wikipedia.org/wiki/Floating_point – SLaks Feb 04 '14 at 13:12
  • `decimal` on the other hand is a solution for this problem since it can represent all those examples exactly thanks to using base 10. (But it still can't represent 1/3 exactly, so there are still similar problems) – CodesInChaos Feb 04 '14 at 21:32

1 Answers1

4

You have to take into account that 1.0e28f is a decimal (base 10) representation.
Converting this string (either a literal in source code or value in a data file) to a floating point imply a conversion to base 2 (the float internal representation).

The 3 nearest floating point values in the neighbourhood of 10^28 are exactly

0.9999998261528069050908803072e28f
0.9999999442119689768320106496e28f <- the nearest one
1.0000000622711310485731409920e28f

or in base 2:

1.00000010011111100111000 * 2^93
1.00000010011111100111001 * 2^93
1.00000010011111100111010 * 2^93

The nearest one (in the middle) is chosen and that is the float in memory, not 1.0e28.

When you request ToString(), you are converting back 0.9999999442119689768320106496e28f to a decimal form.
It sounds like default format is to print 1 decimal before the floating point separator, and 6 decimals after the floating point (much like C printf %f format)

The nearest such decimal to 0.9999999442119689768320106496e28f is indeed 9.999999e27, that's pretty straight forward.

EDIT

Note that 5^3 is near 2^7, since single precision float has 24 bits significand, and 24*3/7 is approximately 10, the maximum power of 5 represented exactly in single precision float is 5^10.

Hence, the maximum power of 10 representable exactly in float is 1.0e10.
Every power from 11 to 27 did print like you expect somehow by luck.
Well, not exactly by luck, but because the roundoff error was less than 0.5*10^(n-7).

EDIT 2 - about double

And double does not really "solve" things, because with 53 bits significand you can go up to 53*3/7 that is 10^22 without rounding error.
Every power of 10 from 23 and above are subject to round off.
It somehow solves however if you keep printing 6 decimals, because the round off occurs around the 15th digit.

aka.nice
  • 9,100
  • 1
  • 28
  • 40
  • It's interesting to note that "9.999999E+27" is accurate to 0.6 least-significant unit, "1.000000E+28" would be accurate to less than 0.07 least-significant units. – supercat May 07 '14 at 19:39