存储四舍五入的双精度会导致不直观的结果

本文关键字:直观 结果 四舍五入 双精度 存储 | 更新日期: 2023-09-27 18:26:02

可能重复:
为什么C#中的浮点运算不精确?

如果我循环遍历许多随机双,并将它们"四舍五入"到两个小数位数,则每个循环看起来都是正确的(0.02、0.01、0.00等)。

然而,似乎有一个非常小的分数部分,与圆形一起保留。

double total = 0;
for (int i = 0; i < 10000; i++)
{
    total += Math.Round(random.NextDouble() * 0.02, 2);
}
Console.WriteLine(total);

样本输出:

100.600000000006

99.7400000000059

有人想用更直观的方式解释为什么会发生这种情况吗?

存储四舍五入的双精度会导致不直观的结果

System.Double和System.Float是基于2的浮点类型。有许多有限的十进制值以2为基数具有无限表示,就像1/3以10为基数具有无穷表示一样。因此,当您四舍五入到这样一个值时,二进制表示是近似的。要避免此问题,请使用十进制类型,它是以10为基数的浮点类型。

stackoverflow上肯定有100个这个问题的副本,但我在打电话,这让我很难找到它们并链接到它们。

有关更多信息,请参阅IEEE double的维基百科文章。

许多人会说替身"不准确",这是错误的。每个双值都代表一个精确的值,可以精确地以10为基数表示(当然,除了NaN和无穷大)。这是因为2是10的素数之一。唯一的近似是当你试图表示某些十进制分数(或其他分母至少有一个除2以外的素数的有理数)时。

至少对我来说,理解这一点的最好方法是在纸上计算出几个分数的二进制表示。例如,尝试0.5、0.625、3.25、5/16、1/3、0.2和0.3。

Double不存储基数为10的数字,它们存储基数为2的值,因此在存储小数时,它们可能与预期的十进制值相差很小。就其价值而言,这并不是基地2独有的。基本10(实际上是所有的基本N系统)也有同样的问题——以1/3为例。在基数10中,你最终将其表示为0.3333333(…),但没有办法完美地表示基数10中的1/3。

在您的示例中,在表示数字的小数部分时可能会遇到小错误,因为您将这些错误相加,您可能会看到这些小错误不断累积。使用我上面的例子,如果你把.333333(…)四舍五入到小数点后2位,你会得到.33,但相对于1/3的实际值,这有相当大的不准确性。在做浮点运算时,积累这些不准确的地方是一个常见的错误。

正如@Phoog所写,SO上对此有很多解释。这里有一个:为什么C#中的浮点运算不精确?