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”.