浮点数计算中的误差及解决方法

在进行计算时,经常需要对数字进行截断。不同精度的混合计算也会导致截断,例如float32单精度浮点数和float64双精度浮点数。由于精度不同,转换时会导致有效数字的丢失,可能在特定计算场景中累积误差,从而导致结果错误。

为了展示误差问题,我们使用Python和Numpy进行示例编程:

import numpy as np
np.random.seed(1)
sum_1 = np.array([0.], np.float64)
sum_2 = np.array([0.], np.float32)
for _ in range(100000):
    x = np.random.random(1000)
    sum_1 += x.sum()
    sum_2 += x.astype(np.float32).sum()
print (sum_1)
print (sum_2)

上述代码的输出结果分别为:

[50003352.04503618]
[50003708.]

尽管进行了相同的累加操作,但得到的结果却不同。这种误差是否可接受取决于具体应用场景,对精度要求较高的计算场景可能直接导致结果错误。

在数字间的运算中,较大的数可能吸收较小的数的有效数字。我们可以通过Python和Numpy演示这一场景:

import numpy as np
x = np.array([1000000.], np.float32)
y = np.array([0.01], np.float32)
print (x+y)

上述代码的执行输出为:

[1000000.]

我们发现y的贡献在这里就完全不体现,但如果使用双精度浮点数进行计算,得到的结果将是:

import numpy as np
x = np.array([1000000.], np.float64)
y = np.array([0.01], np.float64)
print (x+y)

输出结果为:

[1000000.01]

可见,如果在一个大数的基础上不断迭代一些小的数字,最终结果会产生较大误差,甚至导致错误。

为解决误差问题,除了使用双精度浮点数外,还可以采用Kahan求和公式。该公式通过保存误差计算结果,并在下一步计算时将其纳入,以达到提高精度的目的。接下来,我们将分别实现前述两个案例,首先是累加误差问题:

import numpy as np
np.random.seed(1)
sum_1 = np.array([0.], np.float64)
sum_2 = np.array([0.], np.float32)
sum_3 = np.array([0.], np.float32)
tmp_1 = np.array([0.], np.float32)
for _ in range(100000):
    x = np.random.random(1000)
    sum_1 += x.sum()
    sum_2 += x.astype(np.float32).sum()
    tmp_2 = x.astype(np.float32).sum() - tmp_1
    tmp_3 = sum_3 + tmp_2
    tmp_1 = (tmp_3 - sum_3) - tmp_2
    sum_3 = tmp_3
print (sum_1)
print (sum_2)
print (sum_3)

该程序输出结果为:

[50003352.04503618]
[50003708.]
[50003352.]

可以看到,在使用Kahan求和公式后,尽管仍然使用float32单精度浮点数,但结果精度已经相当于普通单精度计算的两倍。

再测试大数加小数的问题,同样使用累加的形式测试,结果展示更加明显:

import numpy as np
np.random.seed(1)
sum_1 = np.array([1000000.], np.float64)
sum_2 = np.array([1000000.], np.float32)
sum_3 = np.array([1000000.], np.float32)
tmp_1 = np.array([0.], np.float32)
for _ in range(100000):
    x = np.random.random(1000) * 1e-05
    sum_1 += x.sum()
    sum_2 += x.astype(np.float32).sum()
    tmp_2 = x.astype(np.float32).sum() - tmp_1
    tmp_3 = sum_3 + tmp_2
    tmp_1 = (tmp_3 - sum_3) - tmp_2
    sum_3 = tmp_3
print (sum_1)
print (sum_2)
print (sum_3)

输出结果为:

[1000500.03352045]
[1000000.]
[1000500.06]

可以看到,如果不使用Kahan求和公式,即使小数被迭代了100000次,也会被忽略。而使用了Kahan求和公式后,尽管仍有误差,但误差已经超过了float32单精度浮点数的第7位有效数字范围,因此Kahan求和公式的精度非常高。

在浮点数计算中,尤其在使用AI框架时,常使用float32单精度浮点数,这与GPU硬件架构有关。然而,在使用单精度浮点数时,必须考虑累积误差和大数吃小数的问题。这两个问题在长时间的迭代计算中,可能导致计算结果错误。使用Kahan求和公式可以避免大数吃小数的问题。Kahan求和公式本质上是将大数和小数分开进行计算,可以在一定程度上接近float64双精度浮点数的运算精度。

原文链接:
https://www.cnblogs.com/dechinphy/p/float32_error.html

作者ID:DechinPhy

更多原著文章:
https://www.cnblogs.com/dechinphy/

请博主喝咖啡:
https://www.cnblogs.com/dechinphy/gallery/image/379634.html

未经允许不得转载:大白鲨游戏网 » 浮点数计算中的误差及解决方法