Double vs Decimal Rounding in C#

2019-01-12 09:36发布

问题:

Why does:

double dividend = 1.0;
double divisor = 3.0;
Console.WriteLine(dividend / divisor * divisor);

output 1.0,

but:

decimal dividend = 1;
decimal divisor = 3;
Console.WriteLine(dividend / divisor * divisor);

outputs 0.9999999999999999999999999999

?

I understand that 1/3 can't be computed exactly, so there must be some rounding. But why does Double round the answer to 1.0, but Decimal does not?

Also, why does double compute 1.0/3.0 to be 0.33333333333333331? If rounding is used, then wouldn't the last 3 get rounded to 0, why 1?

回答1:

Why 1/3 as a double is 0.33333333333333331

The closest way to represent 1/3 in binary is like this: 0.0101010101... That's the same as the series 1/4 + (1/4)^2 + (1/4)^3 + (1/4)^4...

Of course, this is limited by the number of bits you can store in a double. A double is 64 bits, but one of those is the sign bit and another 11 represent the exponent (think of it like scientific notation, but in binary). So the rest, which is called the mantissa or significand is 52 bits. Assume a 1 to start and then use two bits for each subsequent power of 1/4. That means you can store: 1/4 + 1/4^2 + ... + 1/4 ^ 27 which is 0.33333333333333331

Why multiplying by 3 rounds this to 1

So 1/3 represented in binary and limited by the size of a double is: 0.010101010101010101010101010101010101010101010101010101 I'm not saying that's how it's stored. Like I said, you store the bits starting after the 1, and you use separate bits for the exponent and the sign. But I think it's useful to consider how you'd actually write it in base 2.

Let's stick with this "mathematician's binary" representation and ignore the size limits of a double. You don't have to do it this way, but I find it convenient. If we want to take this approximation for 1/3 and multiply by 3, that's the same as bit shifting to multiply by 2 and then adding what you started with. This gives us 1/3 * 3 = 0.111111111111111111111111111111111111111111111111111111

But can a double store that? No, remember, you can only have 52 bits of mantissa after the first 1, and that number has 54 ones. So we know that it'll be rounded, in this case rounded up to exactly 1.

Why for decimal you get 0.9999999999999999999999999999

With decimal, you get 96 bits to represent an integer, with additional bits representing the exponent up to 28 powers of 10. So even though ultimately it's all stored as binary, here we're working with powers of 10 so it makes sense to think of the number in base 10. 96 bits lets us express up to 79,228,162,514,264,337,593,543,950,335, but to represent 1/3 we're going to go with all 3's, up to the 28 of them that we can shift to the right of the decimal point: 0.3333333333333333333333333333.

Multiplying this approximation for 1/3 by 3 gives us a number we can represent exactly. It's just 28 9's, all shifted to the right of the decimal point: 0.9999999999999999999999999999. So unlike with double's there's not a second round of rounding at this point.



回答2:

This is by design of the decimal type which is optimized for accuracy unlike the double type which is optimized for low accuracy but higher performance.

The Decimal value type represents decimal numbers ranging from positive 79,228,162,514,264,337,593,543,950,335 to negative 79,228,162,514,264,337,593,543,950,335.

The Decimal value type is appropriate for financial calculations requiring large numbers of significant integral and fractional digits and no round-off errors. The Decimal type does not eliminate the need for rounding. Rather, it minimizes errors due to rounding. Thus your code produces a result of 0.9999999999999999999999999999 rather than 1.

One reason that infinite decimals are a necessary extension of finite decimals is to represent fractions. Using long division, a simple division of integers like 1⁄9 becomes a recurring decimal, 0.111…, in which the digits repeat without end. This decimal yields a quick proof for 0.999… = 1. Multiplication of 9 times 1 produces 9 in each digit, so 9 × 0.111… equals 0.999… and 9 × 1⁄9 equals 1, so 0.999… = 1: