-1

I have the following code

    double temp3 = 61.01;

    //This can actually be various other types, but float is the one that causes problems
    dynamic temp = 61.01f;
    double temp2 = (double)Convert.ChangeType(temp, typeof(double));

    double newValue = temp2 - temp3;

    //newValue should == 0 but it does not

    Console.WriteLine(String.Format("  {0:F20}", temp));
    Console.WriteLine(String.Format("  {0:F20}", temp2));
    Console.WriteLine(String.Format("  {0:F20}", temp3));
    Console.WriteLine(String.Format("  {0:F20}", newValue));

Which produces

61.01000000000000000000

61.00999832153320000000

61.01000000000000000000

-0.00000167846679488548

Why is Convert.ChangeType losing precision?

We are using Convert.ChangeType because of the use of a dynamic variable which can be byte/uint/float/double etc

rollsch
  • 2,518
  • 4
  • 39
  • 65
  • I'm fairly certain the answer will still be the same as [this post](https://stackoverflow.com/questions/588004/is-floating-point-math-broken). Your source data type is `float`, and `.01` can't be accurately represented in binary: `111101.000000101000111101` ([conversion source](https://www.exploringbinary.com/floating-point-converter/)). – ProgrammingLlama Dec 07 '18 at 05:17
  • 1
    By the way, change your format to `G20`, not `F20`. – ProgrammingLlama Dec 07 '18 at 05:24
  • Also 62.01 can be stored with full precision in both double and float. There should be no precision loss for what I'm doing. The full print of 62.01 has no decimal places up to 20 decimals for float OR for double. Why is precision lost ? – rollsch Dec 07 '18 at 05:28
  • How do you represent `.01` in binary? – ProgrammingLlama Dec 07 '18 at 05:28
  • 1
    I understand now. The F20 was my problem, by using G20 I can see the true value. – rollsch Dec 07 '18 at 05:30
  • 1
    @John: Outside of the formatting and conversion from source text, there does not appear to be any floating-point arithmetic errors in this question. The issue appears to be a consequence of Microsoft’s formatting, not the nature of floating-point. Please do not promiscuously close floating-point questions as duplicates of [that question](https://stackoverflow.com/questions/588004/is-floating-point-math-broken), as it prevents people from getting correct answers. – Eric Postpischil Dec 07 '18 at 10:57
  • 1
    @LasseVågsætherKarlsen: Outside of the formatting and conversion from source text, there does not appear to be any floating-point arithmetic errors in this question. The issue appears to be a consequence of Microsoft’s formatting, not the nature of floating-point. Please do not promiscuously close floating-point questions as duplicates of [that question](https://stackoverflow.com/questions/588004/is-floating-point-math-broken), as it prevents people from getting correct answers. – Eric Postpischil Dec 07 '18 at 11:00

2 Answers2

2

The issue observed in this question is caused largely by Microsoft’s choice of formatting, notably that Microsoft software fails to show the exact values because it limits the number of digits used to convert to decimal even when the format string requests more digits. Furthermore, it uses fewer digits when converting float than when converting double. Thus, if a float and double with the same value are formatted, the results may be different because the float formatting will use fewer significant digits.

Below, I go through the code statements in the question one by one. In summary, the crux of the matter is that the value 61.0099983215332 is formatted as “61.0100000000000” when it is a float and “61.0099983215332” when it is a double. This is purely Microsoft’s choice of formatting and is not caused by the nature of floating-point arithmetic.

The statement double temp3 = 61.01 initializes temp3 to exactly 61.00999999999999801048033987171947956085205078125. This change from 61.01 is necessary due to the nature of a binary floating-point format—it cannot represent exactly 61.01, so the nearest value representable in double is used.

The statement dynamic temp = 61.01f initializes temp to exactly 61.009998321533203125. As with double, the nearest representable value has been used, but, since float has less precision, the nearest value is not as close as in the double case.

The statement double temp2 = (double)Convert.ChangeType(temp, typeof(double)); converts temp to a double that has the same value as temp, so it has the value 61.009998321533203125.

The statement double newValue = temp2 - temp3; correctly subtracts the two values, producing the exact result 0.00000167846679488548033987171947956085205078125, with no error.

The statement Console.WriteLine(String.Format(" {0:F20}", temp)); formats the float named temp. Formatting a float involves callling Single.ToString. Microsoft‘s documentation is a bit vague. It says that, by default, only seven (decimal) digits of precision are returned. It says to use G or R formats to get up to nine, and F20 uses neither G nor R. So I believe only seven digits are used. When 61.009998321533203125 is rounded to seven significant decimal digits, the result is “61.01000”. The ToString method then pads this to twenty digits after the decimal point, producing “61.01000000000000000000”.

I will address your third WriteLine statement next and come back to the second one afterward.

The statement Console.WriteLine(String.Format(" {0:F20}", temp3)); formats the double named temp3. Since temp3 is a double, Double.ToString is called. This method uses 15 digits of precision (unless G orR are used). When 61.00999999999999801048033987171947956085205078125 is rounded to 15 significant decimal digits, the result is “61.0100000000000”. The ToString method then pads this to twenty digits after the decimal point, producing “61.01000000000000000000”.

The statement Console.WriteLine(String.Format(" {0:F20}", temp2)); formats the double named temp2. temp2 is a double that contains the value from the float temp, so it contains 61.009998321533203125. When this is converted to 15 significant decimal digits, the result is “61.0099983215332”. The ToString method then pads this to twenty digits after the decimal point, producing “61.00999832153320000000”.

Finally, the statement Console.WriteLine(String.Format(" {0:F20}", newValue)); formats newValue. Formatting .00000167846679488548033987171947956085205078125 to 15 significant digits produces “0.00000167846679488548”.

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
  • This is absolutely the problem, my use of F20 instead of G20 obfuscated the issue at hand. Thanks for taking the time to explain it properly. – rollsch Dec 09 '18 at 00:41
-1

this is because floats have smaller amounts of bits allocated to them than doubles. floats usually have 23 bits but doubles have 52. Therefore, by converting you are basically chopping off bits until you can fit it into a float. So that is why you loose precision.

Ali TK
  • 13
  • 4