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

时间:2013-03-11 04:00:13

标签: c# double decimal rounding

为什么:

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?

2 个答案:

答案 0 :(得分:14)

为什么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以二进制表示并受到double的大小限制: 0.010101010101010101010101010101010101010101010101010101 我不是说它是如何存储的。就像我说的,你存储从1开始的位,你使用单独的位作为指数和符号。但我认为考虑如何在基础2中实际编写它是有用的。

让我们坚持使用这个“数学家的二进制”表示并忽略双精度的大小限制。你不必这样做,但我发现它很方便。如果我们想要将此近似值用于1/3并乘以3,则与将位移位乘以2然后添加开始时的值相同。这给了我们1/3 * 3 = 0.111111111111111111111111111111111111111111111111111111

但可以双重存储吗?不,请记住,在第一个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。因此,与双重不同的是,此时不会进行第二轮舍入。

答案 1 :(得分:-2)

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

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

十进制值类型适用于需要大量有效积分和小数位且没有舍入误差的财务计算。 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: