1

I'm having problems with a Rounding issue in c#. I would like to round the result of a calculation up to 4 decimals (awayfromzero). If I use Math.Round(variable,...) it rounds down, if I enter the result manually, it rounds up.. I have no idea why..

What am I doing wrong? The result of the below code is: Rounded: 591.24575 591.2457 - 591.2458

double number1 = 1136.81;
double number2 = 4.00;
double number3 = 2182.257;
double result = (number1 * number2 - number3) / 4;
Console.WriteLine("Rounded: " +result+" " + Math.Round(result, 4, MidpointRounding.AwayFromZero) + " - " + Math.Round(591.24575, 4, MidpointRounding.AwayFromZero));
Console.ReadLine();
SeKo
  • 31
  • 2
  • 5
  • 4
    because `double` is a binary floating-point data type that is not 100% accurate when converting to/from base-10 numbers (by using `Round`, `Floor`, etc). Use `decimal` if you need base-10 precision. – D Stanley Aug 27 '14 at 15:51
  • 3
    Always fascinates me that the assumption is there is a bug in the framework or C# when something doesn't quite work how someone expects. – Daniel Kelley Aug 27 '14 at 15:56
  • 1
    @DanielKelley To be fair, the user did say "What am *I* doing wrong?" – LarsTech Aug 27 '14 at 15:58
  • @LarsTech So why use a title of "Rounding bug in c#"? – Daniel Kelley Aug 27 '14 at 16:00
  • well, the decimal type only has better precision, that will in most cases be enough to be "precise", but under the hood also works with binary data as floating point number (see [http://msdn.microsoft.com/de-de/library/system.decimal.aspx]: "A decimal number is a floating-point value that consists of a sign, a numeric value where each digit in the value ranges from 0 to 9, and a scaling factor that indicates the position of a floating decimal point that separates the integral and fractional parts of the numeric value. ..." – eFloh Aug 27 '14 at 16:00

3 Answers3

4

If you use my DoubleConverter, you can see the exact value of result:

Console.WriteLine(DoubleConverter.ToExactString(result));

That prints:

591.2457499999999299689079634845256805419921875

... which rounds to 591.2457 when rounded to 4 decimal places. When you just print result, it's printing it already rounded to a certain number of decimal places, which can affect the result if you then (mentally) rounded to 4 DP.

You can see this without any of the "normal" oddities of binary floating point. Consider this code:

decimal exact = 1.2345m;
decimal rounded2 = Math.Round(exact, 2, MidpointRounding.AwayFromZero);
decimal rounded3 = Math.Round(exact, 3, MidpointRounding.AwayFromZero);
decimal rounded3Then2 = Math.Round(rounded3, 2, MidpointRounding.AwayFromZero);
Console.WriteLine(rounded2); // 1.23
Console.WriteLine(rounded3); // 1.235
Console.WriteLine(rounded3Then2); // 1.24

In your code you weren't actually performing the "two roundings" - but you were mentally doing so by taking the printed value of result (591.24575) and assuming you could take that to be accurate in order to round it further.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
3

result is not exactly 591.24575, it is some number that is very close to 591.24575 but it is just slightly smaller, something along the lines of 591.24574999999999, as a result of the fact that certain numbers that have a finite number of digits in base 10 cannot be represented with a finite number of digits in base 2. When you set the number exactly you are avoiding ever having a double set to one of those values as an intermediate value of your calculations.

If you are dealing with numbers that have a known fixed number of base 10 digits and it's important that you not have these precision errors than it may be appropriate to use Decimal in this context.

Servy
  • 202,030
  • 26
  • 332
  • 449
  • Ah, beaten by half a minute :) – user247702 Aug 27 '14 at 15:55
  • Additionally going forward something you could do to help with this sort of problem is put a breakpoint in your code right after assigning to result. Debug your app and mouseover result to see its actual value – Kritner Aug 27 '14 at 16:01
2

result doesn't have the value you think it does, it's being truncated by putting it in a string representation. As for why that is, read What Every Computer Scientist Should Read About Floating Point

Take a look while debugging:

result: 591.24574999999993
result.ToString(): "591,24575"
user247702
  • 23,641
  • 15
  • 110
  • 157