C# · 12月 31, 2021

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开始的位,你使用单独的位作为指数和符号.但我认为考虑如何在基础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.因此,与双重不同的是,此时不会进行第二轮舍入.