Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Math.round bug - what to do?

Tags:

c#

.net

rounding

Math.Round(8.075, 2, MidpointRounding.AwayFromZero) returns 8.07, though it should return 8.08. Mysteriously enough, 7.075 is working fine, but 9.075 also returns 9.07!

What to do? Does anybody know a rounding method without such bugs?

like image 769
user2396251 Avatar asked May 18 '13 07:05

user2396251


People also ask

How do you do the round method in math?

The Math. round() method rounds a number to the nearest integer. 2.49 will be rounded down (2), and 2.5 will be rounded up (3).

What does math round do?

The Math. round() function returns the value of a number rounded to the nearest integer.

Why does math round round down?

5 to be rounded is rounded either up or down so that the result of the rounding is always an even number. Thus 2.5 rounds to 2.0, 3.5 to 4.0, 4.5 to 4.0, 5.5 to 6.0, and so on.


2 Answers

If you count with 10 fingers, like humans do, you don't have any trouble expressing the decimal value 8.075 precisely:

  8.075 = 8 x 10^1 + 0 x 10^0 + 7 x 10^-1 + 5 x 10^-2

But computers count with 2 fingers, they need to express that value in powers of 2:

  8.075 = 1 x 2^3  + 0 x 2^2  + 0 x 2^1  + 0 x 2^0  + 0 x 2^-1 + 0 x 2^-2 + 0 x 2^-3 + 
          1 x 2^-4 + 0 x 2^-5 + 0 x 2^-6 + 1 x 2^-7 + 1 x 2^-8 + 0 x 2^-9 + 0 x 2^-10 +
          1 x 2^-11 + ...

I gave up with finger cramp typing the terms but the point is that no matter how many powers of 2 you add, you'll never get exactly 8.075m. A similar problem to how humans can never write the result of 10 / 3 precisely, it has an infinite number of digits in the fraction. You can only write the result of that expression accurately when you count with 6 fingers.

A processor of course doesn't have enough storage to store an infinite number of bits to represent a value. So they must truncate the digit sequence, a value of type double can store 53 bits.

As a result, the decimal value 8.075 gets rounded when it is stored in the processor. The sequence of 53 bits, converted back to decimal, is the value ~8.074999999999999289. Which then, as expected, gets rounded to 8.07 by your code.

If you want 10 finger math results, you'll need to use a data type that stores numbers in base 10. That's the System.Decimal type in .NET. Fix:

decimal result = Math.Round(8.075m, 2, MidpointRounding.AwayFromZero)

Note the usage of the letter m in the 8.075m literal in the snippet, a literal of type decimal. Which selects the Math.Round() overload that counts with 10 fingers, previously you used the overload that uses System.Double, the 2 finger version.

Do note that there's a significant disadvantage to calculating with System.Decimal, it is slow. Much, much slower than calculating with System.Double, a value type that's directly supported by the processor. Decimal math is done in software and is not hardware accelerated.

like image 190
Hans Passant Avatar answered Sep 22 '22 06:09

Hans Passant


I am not a .net specialist, but these numbers can't be exactly represented as double, so the rounding is accurate if you take into account the real value of those 3 numbers:

7.075 ==> 7.07500000000000017763568394002504646778106689453125
8.075 ==> 8.074999999999999289457264239899814128875732421875
9.075 ==> 9.074999999999999289457264239899814128875732421875

More about floating-point precision: What Every Computer Scientist Should Know About Floating-Point Arithmetic.

like image 23
assylias Avatar answered Sep 22 '22 06:09

assylias