C#中的双倍与十进制舍入

为什么:

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?

为什么1/3作为双倍是0.33333333333333331

在二进制中表示1/3的最接近的方式是这样的:0.0101010101 …这与系列1/4 +(1/4)^ 2 +(1/4)^ 3 +(1/4)^相同4 …

当然,这受到可以存储在双精度中的位数的限制。 双精度是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.11111111111111111111111111111111111111111111111111111111

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

为什么小数点得到0.9999999999999999999999999999

使用十进制,你得到96位代表一个整数,其他位代表指数高达28的10次幂。所以即使最终它都存储为二进制,这里我们使用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.999999999999999999999999999999。 因此与双重不同的是,此时不会进行第二轮舍入。

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

十进制值类型表示十进制数,范围从正数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的快速certificate。乘法9乘1每个数字产生9,所以9×0.111 …等于0.999 …和9×1/9等于1,所以0.999 … = 1: