python/numpy中添加数字时的浮点精度细分

python/numpy中添加数字时的浮点精度细分,python,numpy,precision,Python,Numpy,Precision,我有一些问题,因为与numpy一起使用的数字非常少。我花了几个星期的时间才把我经常遇到的数值积分问题追溯到这样一个事实:当我在一个函数中加上浮点数时,浮点数64的精度就会丢失。用乘积而不是求和执行数学上相同的计算可以得到正确的值 以下是代码示例和结果图: from matplotlib.pyplot import * from numpy import vectorize, arange import math def func_product(x): return math.exp(

我有一些问题,因为与numpy一起使用的数字非常少。我花了几个星期的时间才把我经常遇到的数值积分问题追溯到这样一个事实:当我在一个函数中加上浮点数时,浮点数64的精度就会丢失。用乘积而不是求和执行数学上相同的计算可以得到正确的值

以下是代码示例和结果图:

from matplotlib.pyplot import *
from numpy import vectorize, arange
import math

def func_product(x):
    return math.exp(-x)/(1+math.exp(x))

def func_sum(x):
    return math.exp(-x)-1/(1+math.exp(x))

#mathematically, both functions are the same

vecfunc_sum = vectorize(func_sum)
vecfunc_product = vectorize(func_product)

x = arange(0.,300.,1.)
y_sum = vecfunc_sum(x)
y_product = vecfunc_product(x)

plot(x,y_sum,    'k.-', label='sum')
plot(x,y_product,'r--',label='product')

yscale('symlog', linthreshy=1E-256)
legend(loc='lower right')
show()

如您所见,非常低的求和值分散在零周围,或者正好为零,而乘法值很好


请问,有人能帮忙/解释一下吗?非常感谢

由于舍入误差,浮点精度对加法/减法非常敏感。最后,
1+exp(x)
变得如此之大,以至于将1添加到exp(x)得到与exp(x)相同的结果。在双精度中,大约是
exp(x)==1e16

>>> (1e16 + 1) == (1e16)
True
>>> (1e15 + 1) == (1e15)
False
请注意,
math.log(1e16)
大约为37——这大概是您的绘图中出现问题的地方

您可能会遇到相同的问题,但程度不同:

>>> (1e-16 + 1.) == (1.)
True
>>> (1e-15 + 1.) == (1.)
False
对于你的制度中的绝大多数观点,你的
func_产品
实际上是在计算:

exp(-x)/exp(x) == exp(-2*x)
这就是为什么你的图有一个很好的斜率-2

另一个极端是,你的另一个版本正在计算(至少大约):

大约是

exp(-x) - exp(-x)

问题在于,您的
函数和
是因为它涉及两个非常接近的值之间的减法

在计算
func_sum(200)
时,例如,
math.exp(-200)
1/(1+math.exp(200))
具有相同的值,因为将
1
添加到
math.exp(200)
没有效果,因为它超出了64位浮点的精度:

math.exp(200).hex()
0x1.73f60ea79f5b9p+288

(math.exp(200) + 1).hex()
0x1.73f60ea79f5b9p+288

(1/(math.exp(200) + 1)).hex()
0x1.6061812054cfap-289

math.exp(-200).hex()
0x1.6061812054cfap-289
这就解释了为什么
func_sum(200)
给出零,但是偏离x轴的点呢?这些也是由浮点不精确引起的;偶尔会发生
math.exp(-x)
不等于
1/math.exp(x)
;理想情况下,
math.exp(x)
是距离
e^x
最近的浮点值,
1/math.exp(x)
是距离
math.exp(x)
计算的浮点数倒数最近的浮点值,不一定是距离
e^-x
。事实上,
math.exp(-100)
1/(1+math.exp(100))
非常接近,事实上只有最后一个单位不同:

math.exp(-100).hex()
0x1.a8c1f14e2af5dp-145

(1/math.exp(100)).hex()
0x1.a8c1f14e2af5cp-145

(1/(1+math.exp(100))).hex()
0x1.a8c1f14e2af5cp-145

func_sum(100).hex()
0x1.0000000000000p-197
因此,您实际计算的是
math.exp(-x)
1/math.exp(x)
之间的差异(如果有)。您可以跟踪函数
math.pow(2,-52)*math.exp(-x)
的行,查看它是否通过
func_sum
的正值(请记住,52是64位浮点中有效位的大小)。

这是一个示例

让我们看看第一个计算出错的点,当
x=36.0

In [42]: np.exp(-x)
Out[42]: 2.3195228302435691e-16

In [43]: - 1/(1+np.exp(x))
Out[43]: -2.3195228302435691e-16

In [44]: np.exp(-x) - 1/(1+np.exp(x))
Out[44]: 0.0
使用func_乘积的计算不会减去几乎相等的数,因此避免了灾难性的相消


顺便说一句,如果您将
math.exp
更改为
np.exp
,您可以摆脱
np.vectorize
(这很慢):


如果
np.float64
还不够,那么就有
np.float128
。它有相同的问题吗?是的,float96也有相同的问题…为什么不在日志域中进行这些求和以避免精度问题?
In [42]: np.exp(-x)
Out[42]: 2.3195228302435691e-16

In [43]: - 1/(1+np.exp(x))
Out[43]: -2.3195228302435691e-16

In [44]: np.exp(-x) - 1/(1+np.exp(x))
Out[44]: 0.0
def func_product(x):
    return np.exp(-x)/(1+np.exp(x))

def func_sum(x):
    return np.exp(-x)-1/(1+np.exp(x))

y_sum = func_sum_sum(x)
y_product = func_product_product(x)