11

I have a float value: 12345.6489

When I format this using:

(12345.6489f).ToString("F1")

Then I get a result of

12345.7

But this is incorrect, since it should be 12345.6.

Does anyone understand why this might occur? Another hint is that casting to double before I format returns the correct result, and if my float value is a little smaller, for example 1234.6489, then too I get the correct result.

L. Desjardins
  • 1,465
  • 1
  • 12
  • 22
  • 1
    @DavidStratton: `Double` isn't exact either, and will give similar oddities. They just have different precisions and different ranges. – Jon Skeet Jan 14 '13 at 19:34
  • @JonSkeet - thanks for the clarification! I deleted the comment. – David Jan 14 '13 at 19:37
  • 1
    Single.ToString is doing something very odd. I think you'll need to look at the IL. The documentation (http://msdn.microsoft.com/en-us/library/f71z6k0c.aspx) says "By default, the return value only contains 7 digits of precision although a maximum of 9 digits is maintained internally" but I'm sure I've no idea what that means. – David Heffernan Jan 14 '13 at 19:50
  • Hmm, `[MethodImpl(MethodImplOptions.InternalCall)]`, so no IL. – David Heffernan Jan 14 '13 at 20:05
  • @DavidHeffernan It is also interesting to note that while in _most_ regions, a `float` is more precise than a decimal number with 7 significant digits, there are a few places where this is not true. In those regions, you get funny results because `ToString()` gives 7 decimal digits. Example: Near `9E+09f` (9 billion), the precision of a decimal with 7 digits is `1000`, while the precision of a `float` is only `1024`. So for example `float x = 9E+09f; Console.WrireLine(x);` gives a surprise (9,000,000,000 is 512 modulo 1024). – Jeppe Stig Nielsen Jan 14 '13 at 20:07
  • Assuming `float` is 32-bit IEEE single precision, the closest representable value to `12345.6489` is exactly `12345.6484375`. The next representable value above that is exactly `12345.6494140625`. So there's more than enough precision to get one or two digits after the decimal point right. For `double` (IEEE 64 bits), the surrounding values are `12345.648899999998320708982646465301513671875` and `12345.64890000000013969838619232177734375` – Keith Thompson Jan 15 '13 at 01:23

2 Answers2

3

This seems to be related to a question I asked some time ago: Round-twice error in .NET's Double.ToString method

Note that if you call .ToString("G") on your number, it is correctly rounded to 12345.65. If you round the rounded number, to one decimal, the problem occurs.

When I investigated my own question earlier, I also found some examples that couldn't be explained as round-twice errors, so check that thread as well.

Addition: Note that any number that can be represented (exactly) by a float, can also be represented (with a lot of zero bits) by a double. One can use the following trick (which the question also mentions):

float x = 12345.6489f;
string trick = ((double)x).ToString("F1");
Community
  • 1
  • 1
Jeppe Stig Nielsen
  • 60,409
  • 11
  • 110
  • 181
1

Thanks for this question! It is very interesting subject to be engaged in investigations. But I wanted to mention other party of a medal. You asked the following:

(12345.6489f).ToString("F1")

Then I get a result of

12345.7

But this is incorrect, since it should be 12345.6.

Well, I'm wondering how you have figured out what is correct and what is incorrect output of this string formatting routine? Those formatting strings are not supposed to be used to rounding purposes. And documentation clearly says about it:

http://msdn.microsoft.com/en-us/library/dwhawy9k.aspx#FFormatString


Honestly when I first looked at number from your question - the first idea was about rounding algorithm which Hans Passant mentioned in his answer. So, I'm not even surprised that such algorithm was choosen, it is actually pretty intuitive :) I even wouldn't be surprised they would consider plain truncate as algorithm for formatting of floating points numbers. It would be still pretty accurate and valid.

So, despite of the fact that all this is very interesting and looking like a bug/paradox/miracle, this in fact is just our wrong expectations from the function which is designed to do (and actually does pretty well) another thing.

SergeyS
  • 3,515
  • 18
  • 27
  • Ah I thought that I had read somewhere that the string format should round the number for you, which is why I thought that 12345.6 would be the correct answer. Yes Math.Round would definately more reliable! – L. Desjardins Jan 15 '13 at 03:44
  • Math.Round doesn't help at all. That returns a floating point value. You still need to get a string which takes you back to ToString. You need to round as part of the conversion to string. This is critical because floating point uses binary representation, but the string representaion is decimal. – David Heffernan Jan 15 '13 at 07:26