6

为什么:

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

输出 1.0,

但:

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

输出 0.9999999999999999999999999999

?

我知道 1/3 不能精确计算,所以必须进行一些舍入。但是为什么 Double 将答案四舍五入为 1.0,而 Decimal 却没有呢?

另外,为什么双倍计算 1.0/3.0 为 0.33333333333333331?如果使用四舍五入,那么最后 3 个不会四舍五入为 0,为什么是 1?

4

2 回答 2

14

为什么双倍的 1/3 是 0.33333333333333331

用二进制表示 1/3 的最接近的方法是:0.0101010101... 这与系列 1/4 + (1/4)^2 + (1/4)^3 + (1/4)^ 相同4...

当然,这受到您可以存储在 double 中的位数的限制。double 是 64 位,但其中一个是符号位,另一个 11 代表指数(把它想象成科学记数法,但是是二进制的)。所以其余的,称为尾数或有效位是 52 位。假设以 1 开始,然后为每个后续的 1/4 幂使用两位。这意味着您可以存储: 1/4 + 1/4^2 + ... + 1/4 ^ 27 即 0.33333333333333331

为什么乘以 3 舍入到 1

所以 1/3 以二进制表示并受双精度大小的限制: 0.010101010101010101010101010101010101010101010101010101 我并不是说它是这样存储的。就像我说的那样,您存储从 1 开始的位,并为指数和符号使用单独的位。但我认为考虑如何在 base 2 中实际编写它是有用的。

让我们坚持使用这种“数学家的二进制”表示并忽略双精度数的大小限制。你不必这样做,但我觉得很方便。如果我们想将此近似值取为 1/3 并乘以 3,这与移位乘以 2 然后添加您开始的内容相同。这给了我们 1/3 * 3 = 0.1111111111111111111111111111111111111111111111111111

但是双倍存储可以吗?不,请记住,在第一个 1 之后只能有 52 位尾数,而那个数字有 54 个。所以我们知道它会被四舍五入,在这种情况下四舍五入到 1。

为什么十进制你会得到 0.9999999999999999999999999999

使用十进制,您可以获得 96 位来表示一个整数,另外的位表示最多 10 的 28 次幂的指数。所以即使最终它全部存储为二进制,这里我们使用的是 10 的幂,所以考虑一下是有意义的以 10 为基数的数字。 96 位最多可以表示 79,228,162,514,264,337,593,543,950,335,但要表示 1/3,我们将使用所有 3,最多 28 个,我们可以将其移到小数点右侧: 0.3333333333333333333333333333。

将这个 1/3 的近似值乘以 3 可以得到一个可以精确表示的数字。它只有 28 个 9,全部移到小数点右侧:0.9999999999999999999999999999。因此,与双精度数不同,此时没有第二轮舍入。

于 2013-03-11T04:21:39.367 回答
-2

这是针对精度优化的十进制类型的设计,与针对低精度但更高性能进行优化的双精度类型不同。

Decimal值类型表示从正数 79,228,162,514,264,337,593,543,950,335 到负数 79,228,162,514,264,337,593,543,950,335 的十进制数。

Decimal 值类型适用于需要大量有效整数和小数位数且无舍入错误的财务计算。Decimal 类型不会消除舍入的需要。相反,它最大限度地减少了由于四舍五入引起的错误。因此,您的代码产生的结果为 0.9999999999999999999999999999 而不是 1。

无限小数是有限小数的必要扩展的一个原因是表示分数。使用长除法,像 1⁄9 这样的整数的简单除法变成循环小数 0.111…,其中数字重复无止境。这个小数可以快速证明 0.999… = 1。 9 乘以 1 在每个数字中产生 9,所以 9 × 0.111… 等于 0.999… 并且 9 × 1⁄9 等于 1,所以 0.999… = 1:

于 2013-03-11T05:01:10.097 回答