1

In a nutshell, I'm wondering why the ToString() lines below don't match my expectations.

float actual = 111253.469F;
double rounded = Math.Round(actual, 2); //  111253.47 (expected)
string stringified = actual.ToString(); //  111253.50 (expected 111253.469)
string currency = actual.ToString("C"); // $111253.50 (expected 111253.47 or possibly .46)

Can someone explain what is going on?

Note: I don't expect the floating point value to be exact, per the other questions on floating point numbers. However I do expect the string output to reflect the string value of the floating point number in question.

In this case, if I set a break point and look at the value of 'actual' in a quick watch, it doesn't display '111253.5', it displays '111253.469'. I don't understand why that wouldn't be the case when printing as well. And especially in the currency case, I would expect it to round to the appropriate decimal point and then display, rather than picking some other literal value. This isn't a math question, it is a ToString() behavior question.

Edit: Per Quantic's comment below, it appears the ToString is using the G format by default which limits the number to 7 significant digits, resulting in the number being seen here.

SWC
  • 141
  • 2
  • 10
  • 2
    Impossible. 111253 will never give 11253 as output – Thomas Weller Nov 08 '16 at 18:59
  • @ThomasWeller Sorry, my commented lines were inexact (left out the first 1), but it is the decimal piece that is throwing me, sorry. I'll correct. – SWC Nov 08 '16 at 19:07
  • If you want two numbers on decimal piece you can use these samples: ToString("0.00"); ToString("n2"); // for number ToString("c2"); // for currency – tdog Nov 08 '16 at 19:10
  • 3
    [`float.ToString()`](https://msdn.microsoft.com/en-us/library/w5dbkt9x(v=vs.110).aspx) uses the "G" modifier by default, and the [`G Modifier`](https://msdn.microsoft.com/en-us/library/dwhawy9k(v=vs.110).aspx#GFormatString) uses 7 digit precision for a single aka float. This is because, [A Single value has up to 7 decimal digits of precision](https://msdn.microsoft.com/en-us/library/system.single(v=vs.110).aspx). – Quantic Nov 08 '16 at 19:15
  • @utaco Using any of those formats also results in the '.50' value, as opposed to the expected. – SWC Nov 08 '16 at 19:16
  • @Quantic thank you, that does clarify the default 'ToString()' operation. Still a little confused by the currency behavior, per edit. – SWC Nov 08 '16 at 19:25
  • sorry my fault. check this. string stringified = actual.ToString("R"); – tdog Nov 08 '16 at 19:28
  • Just as a comment, this is all much much easier if you store the currency as an integer where 1 unit = 1 smallest unit in that currency (eg $100.52 = 10052) – Tom Gullen Nov 08 '16 at 19:29
  • 1
    `float` only has 7 digits of precision, so the only one that surprises me is the `Math.Round` result. I'd expect it to have an input value of `111253.50`, which would still be `111253.50` after rounding,. I wonder if the compiler in inlining the scalar value into the IL. – D Stanley Nov 08 '16 at 19:30
  • I don't know the answer to that, I actually don't know why I can declare this which is obviously more than 7 digits: `float actual = 11122342342342342342323534232344234234.42342234234234234234234423234423342369F;`, I think this "decimal representation of a binary floating point number" is converted to the nearest 7 digit precision "binary floating point" exponential number which is `1.11223423E+37`. *shrug*, I do wonder *how* it does that conversion though. – Quantic Nov 08 '16 at 19:30
  • Do you get the same behavior if you use `float actual = float.Parse("111253.469");`? – D Stanley Nov 08 '16 at 19:33
  • @DStanley: yeah, that's a little weird. Playing around in dotnetfiddle and looking at the generated il, you can see it loads `111253.67` as a float with `ldc.r4` and then converts it to a double with `conv.r8` before calling `System.Math.Round` – Matt Burland Nov 08 '16 at 19:54

2 Answers2

1

According to MSDN about Single.ToString() (emphasis mine):

By default, the return value only contains 7 digits of precision although a maximum of 9 digits is maintained internally. If the value of this instance has greater than 7 digits, M:System.Single.ToString(System.String) returns P:System.Globalization.NumberFormatInfo.PositiveInfinitySymbol or P:System.Globalization.NumberFormatInfo.NegativeInfinitySymbol instead of the expected number. If you require more precision, specify format with the "G9" format specification, which always returns 9 digits of precision, or "R", which returns 7 digits if the number can be represented with that precision or 9 digits if the number can only be represented with maximum precision.

So your code example behaves exactly as documented:

  • ToString() will use "G" by default, which will use 7 digits precision
  • ToString("C") will also use 7 digits of precision

Both "R" and "G9" will return 111253.469, "G8" returns 111253.47.

Thomas Weller
  • 55,411
  • 20
  • 125
  • 222
0

You do not have a ToString() issue, you have a float issue.

A float is a floating-point value that by definition has a 7-digit precision: MSDN

Now, you have more than 7 digits: float actual = 111253.469F, so the system rounds least significant ones and you get 111253.5 as a result of your operations. Notice that double is calculated correctly, as it can handle more than 7 significant digits.

user270576
  • 987
  • 10
  • 16