浮点数计算出错了吗?

发布时间 2023-10-22 22:25:21作者: 小满独家

内容来自 DOC https://q.houxu6.top/?s=浮点数计算出错了吗?

考虑以下代码:

0.1 + 0.2 == 0.3  ->  false

0.1 + 0.2         ->  0.30000000000000004

为什么会出现这些不精确的情况?


二进制浮点数数学是这样的。在大多数编程语言中,它基于IEEE 754标准。问题的关键在于数字以整个数字乘以2的幂的形式表示;分母不是2的幂的有理数(例如0.1,即1/10)无法被精确表示。

对于标准binary64格式中的0.1,其表示可以精确地写成:

  • 十进制形式为0.1000000000000000055511151231257827021181583404541015625,或
  • C99十六进制浮点数表示法中的0x1.999999999999ap-4

相反,有理数0.1(即1/10)可以精确地写成:

  • 十进制形式为0.1,或
  • 类似于C99十六进制浮点数表示法中的0x1.99999999999999...p-4,其中...表示一连串的9。

程序中的常量0.20.3也将是它们真实值的近似值。碰巧最接近0.2double比有理数0.2大,但最接近0.3double比有理数0.3小。0.10.2的和大于有理数0.3,因此与代码中的常量不符。

关于浮点运算问题的比较全面的讨论可以在What Every Computer Scientist Should Know About Floating-Point Arithmetic中找到。为了更容易理解的解释,请参见floating-point-gui.de

备注:所有位置(基数为N)的数字系统都存在精度问题

普通的十进制(基数为10)数字也存在同样的问题,这就是为什么像1/3这样的数字会以0.333333333...的形式出现。

你刚刚发现了一个数字(3/10),它恰好可以用十进制系统表示,但不适合二进制系统。它的情况也是如此(在一定程度上):1/16是一个丑陋的数字(0.0625),但在二进制中看起来非常整洁(0.0001)。如果我们在日常生活中使用基数为2的数字系统,你会看到这个数字,并本能地知道可以通过不断地除以2、再除以2、再除以2来得到它。

当然,这并不完全准确(因为浮点数在内存中是以科学计数法的形式存储的),但它确实说明了为什么二进制浮点数精度错误会出现,因为通常我们感兴趣的“现实世界”数字通常是10的幂——但这只是因为我们日常使用十进制数字系统。这也是为什么我们会说“71%”(而不是“5除以7”)的原因,因为71%是一个近似值,因为5/7不能用任何十进制数精确表示。

所以答案是:二进制浮点数没有错,只是它们和其他所有基数为N的数字系统一样不完美而已 ?

备注:在编程中使用浮点数

在实践中,这个精度问题意味着在显示之前,你需要使用四舍五入函数将你的浮点数四舍五入到你感兴趣的小数位数。

你还需要在等式测试中使用允许一定容差的比较,这意味着:

不要使用if (x == y) { ... }

而应该使用if (abs(x - y) < myToleranceValue) { ... }
abs 是绝对值。 myToleranceValue 需要根据你的具体应用进行选择 - 它与你准备允许的“摆动空间”有多大有关,以及你将要比较的最大数字是多少(由于精度丢失问题)。在你的选择的语言中要小心“epsilon”风格的常数。这些 可以 用作公差值,但它们的效果取决于你处理的数字的数量(大小),因为大数字的计算可能会超过 epsilon 阈值。