Reputation: 2915
I've been fighting decimal precision in C# coming from a SQL Decimal (38,30) and I've finally made it all the way to a rounding oddity. I know I'm probably overlooking the obvious here, but I need a little insight.
The problem I'm having is that C# doesn't produce what I would consider to be consistent output.
decimal a = 0.387518769125m;
decimal b = 0.3875187691250002636113061835m;
Console.WriteLine(Math.Round(a, 11));
Console.WriteLine(Math.Round(b, 11));
Console.WriteLine(Math.Round(a, 11) == Math.Round(b, 11));
Yields
0.38751876912
0.38751876913
False
Uhh, 0.38751876913? Really? What am I missing here?
From MSDN:
If the digit in the decimals position is odd, it is changed to an even digit. Otherwise, it is left unchanged.
Why am I seeing inconsistent results? The additional precision isn't changing the 'digit in the decimals position'...
Upvotes: 39
Views: 7658
Reputation: 86146
Let's shift both numbers over 11 digits to the left:
38751876912.5
38751876912.50002636113061835
Using banker's rounding, we round the first one down. Under every midpoint-rounding system, we round the second number up (because it is not at the midpoint).
.Net is doing exactly what we'd expect it to.
Upvotes: 4
Reputation: 241779
From MSDN:
If there is a single non-zero digit in
d
to the right of thedecimals
decimal position and its value is5
, the digit in the decimals position is rounded up if it is odd, or left unchanged if it is even. Ifd
has fewer fractional digits thandecimals
,d
is returned unchanged.
In your first case
decimal a = 0.387518769125m;
Console.WriteLine(Math.Round(a, 11));
there is a single digit to the right of the 11th place, and that number is 5
. Therefore, since position 11 is even, it is left unchanged. Thus, you get
0.38751876912
In your second case
decimal b = 0.3875187691250002636113061835m;
Console.WriteLine(Math.Round(b, 11));
there is not a single digit to the right of the 11th place. Therefore, this is straight up grade-school rounding; you round up if the next digit is greater than 4, otherwise you round down. Since the digit to the right of the 11th place is more than 4 (it's a 5), we round up so you see
0.38751876913
Why am I seeing inconsistent results?
You're not. The results are completely consistent with the documentation.
Upvotes: 47
Reputation: 47068
The part "single non-zero digit in d to the right of the decimals decimal position and its value is 5" explains the result. Only when the part to round is exactly 0,5 the rounding rule comes into play.
Upvotes: 4
Reputation: 499352
From MSDN - Math.Round Method (Decimal, Int32):
If there is a single non-zero digit in d to the right of the decimals decimal position and its value is 5, the digit in the decimals position is rounded up if it is odd, or left unchanged if it is even. If d has fewer fractional digits than decimals, d is returned unchanged.
The behavior of this method follows IEEE Standard 754, section 4. This kind of rounding is sometimes called rounding to nearest, or banker's rounding. It minimizes rounding errors that result from consistently rounding a midpoint value in a single direction.
Note the use of single non-zero digit. This corresponds to your first examples, but not the second.
And:
To control the type of rounding used by the Round(Decimal, Int32) method, call the Decimal.Round(Decimal, Int32, MidpointRounding) overload.
Upvotes: 26