小能豆

Python 浮点数的舍入误差

javascript

我不知道这是否是一个明显的错误,但在运行 Python 脚本来改变模拟参数时,我意识到 delta = 0.29 和 delta = 0.58 的结果缺失了。经过调查,我注意到以下 Python 代码:

for i_delta in range(0, 101, 1):
  delta = float(i_delta) / 100

  (...)

filename = 'foo' + str(int(delta * 100)) + '.dat'

为 delta = 0.28 和 0.29 生成了相同的文件,.57 和 .58 也是如此,原因是 python 将 float(29)/100 返回为 0.289999999999999998。但这不是系统性错误,不是每个整数都会发生的系统性错误。所以我创建了以下 Python 脚本:

import sys

n = int(sys.argv[1])

for i in range(0, n + 1):
  a = int(100 * (float(i) / 100))
  if i != a: print i, a

我无法看出出现舍入误差的数字中存在任何规律。为什么这些特定数字会出现这种情况?


阅读 59

收藏
2024-07-02

共1个答案

小能豆

这个问题的出现是因为计算机中浮点运算的工作原理。浮点数以二进制表示,而许多小数无法用二进制准确表示。因此,会出现小的舍入误差。

解释

当您执行计算时float(29) / 100,结果无法精确表示为二进制浮点数。相反,它会被表示为最接近的二进制分数。这就是为什么float(29) / 100结果是0.28999999999999998而不是0.29

为什么要用具体的数字?

这个问题之所以只发生在某些数字上而不发生在其他数字上,是因为 Python 使用的 IEEE 754 标准中浮点数的内部表示。有些十进制分数在二进制中有精确的表示,而其他则没有。例如:

  • 0.5具有精确的二进制表示形式:0.1二进制。
  • 0.25也具有精确的二进制表示形式:0.01二进制。
  • 0.1没有精确的二进制表示,而是近似于0.0001100110011001100110011001100110011001100110011001101...二进制。

你的代码

代码中的关键问题是从浮点数到整数的转换以及随后的比较。float(29) / 100乘以 100 后转换为整数时,得到的是int(0.28999999999999998 * 100)28而不是29

解决方案

1. 使用 Decimal 进行精确的十进制运算

decimal模块提供了一种执行精确十进制运算的方法:

from decimal import Decimal

for i in range(0, 101, 1):
    delta = Decimal(i) / Decimal(100)
    filename = 'foo' + str(int(delta * 100)) + '.dat'
    print(filename)  # Verify the output

2. 在转换为整数之前对值进行四舍五入

通过将该值转换为整数之前对其进行四舍五入,您可以缓解此问题:

for i in range(0, 101, 1):
    delta = float(i) / 100
    filename = 'foo' + str(int(round(delta * 100))) + '.dat'
    print(filename)  # Verify the output

3. 直接格式化值

您可以在创建文件名时直接格式化浮点值,以确保一致的结果:

for i in range(0, 101, 1):
    delta = float(i) / 100
    filename = 'foo' + f"{delta:.2f}".replace('.', '') + '.dat'
    print(filename)  # Verify the output

此方法使用字符串格式来确保0.29保持290.58保持58

结论

浮点运算问题很常见,源自小数的二进制表示。通过decimal适当使用模数或舍入值,您可以在计算和比较中避免这些陷阱。

2024-07-02