Why team >? Math.Round(0.45f, 1, MidpointRounding.AwayFromZero) >? Math.Round(0.45f, 1, MidpointRounding.AwayFromZero) returns 0.4, and the command >? Math.Round(0.45d, 1, MidpointRounding.AwayFromZero) >? Math.Round(0.45d, 1, MidpointRounding.AwayFromZero) 0.5?

Commands are executed in the command window of Visual Studio 2008, version .Net Framework 3.5

  • one
    I suspect that 0.45f != 0.45d and have deviations in different directions from 0.45 from that and the difference. - Dmitry Chistik

2 answers 2

The fact is that 0.45 does not have an exact binary representation. This leads to a loss of accuracy even where the developer expects a more accurate result.

In your case, upcast float values ​​of 0.45 to double occur.

0.45f looks like this

 0 01111101 1100 11001100 11001100 110 

This is approximately 0.449999988079071044921875 .

.NET knows that float accuracy is insufficient, and when converting to a string, uses 7 significant digits. 0.4499999 88 displayed as 0.45 . Therefore, in the debugging, and when outputting to the console, you will see 0.45 .

0.45d - looks like this:

 0 0111111 1101 1100 11001100 11001100 11001100 11001100 11001100 11001101 

That is approximately equal to 0.450000000000000011102230246252 . For double, when converting to a string, .NET uses 14 significant digits. The value is represented as 0.45000000000000 00 -> 0.45

Ok, now you give the processor 0.45f and ask it to convert it to double. The processor takes and finishes with zeros:

 0 0111111 1101 1100 11001100 11001100 11000000 00000000 00000000 00000000 

What is still 4.49999988079071044921875

From the point of view of the processor - the accuracy was not affected. But now it is double, and during operations with it it is assumed that it is precise enough to grab more numbers when working with it as a decimal number.

It is no longer "around 0.45 ". This is double. Now there are about 14 significant digits in it - the value from the point of view of .NET and the processor has turned into "approximately 0.449999988079071 ".

Those. in the binary representation, the accuracy was not lost, in the decimal - also - but the resulting number now differs from 0.45d, which leads to rounding in an unexpected direction.


Why does a similar call work with the cast to decimal:

 Math.Round((Decimal)0.45f, 1, MidpointRounding.AwayFromZero) // 0.5 

The reduction to decimal occurs through the system call of the method VarDecFromR4 , which converts the float to 7 significant decimal digits when converting, making 0.449999988079071044921875 nice 0.4500000 :

 // Round the input to a 7-digit integer. The R4 format has // only 7 digits of precision, and we want to keep garbage digits // out of the Decimal were making. 

Actually, the conversion code itself (a pair of screens) is mainly devoted to this rounding, followed by cutting off the insignificant zeros.

  • What is this upcast going for? Why can't I immediately round float? - 4per
  • one
    Got it! just no overload of Math.Round for float - 4per
  • one
    @ 3per: And even if it were - in which direction to round 0.449999988079071044921875 ? This is not a midpoint, it is strictly less than the middle. - VladD
  • @VladD, look at the code in my answer. If it is strictly less than the middle, why when converting to Decimal, it is rounded "correctly"? - 4per
  • @ 3per: Apparently, when converting to decimal there was a round-up of exactly 3.45. - VladD

From https://stackoverflow.com/a/8240013/5574962 To work around this problem, you can use this approach. Such calculations are, of course, slower.

 Math.Round((Decimal)0.45f, 1, MidpointRounding.AwayFromZero) 

Returns 0.5