为什么这个结果比等效函数的另一个结果更准确



我有以下问题,为什么myf(x)给出的结果不如myf2(x)准确。这是我的python代码:

from math import e, log
def Q1():
n = 15
for i in range(1, n):
#print(myf(10**(-i)))
#print(myf2(10**(-i)))
return
def myf(x):
return ((e**x - 1)/x)
def myf2(x):
return ((e**x - 1)/(log(e**x)))

下面是 myf(x) 的输出:

1.0517091807564771
1.005016708416795
1.0005001667083846
1.000050001667141
1.000005000006965
1.0000004999621837
1.0000000494336803
0.999999993922529
1.000000082740371
1.000000082740371
1.000000082740371
1.000088900582341
0.9992007221626409
0.9992007221626409

myf2(x):

1.0517091807564762
1.0050167084168058
1.0005001667083415
1.0000500016667082
1.0000050000166667
1.0000005000001666
1.0000000500000017
1.000000005
1.0000000005
1.00000000005
1.000000000005
1.0000000000005
1.00000000000005
1.000000000000005

我相信这与python中的浮点数系统与我的机器相结合有关。欧拉数的自然对数产生的数字的精度比其等效数字 x 作为整数。

让我们从xlog(exp(x))之间的区别开始,因为其余的计算是相同的。

>>> for i in range(10):
...     x = 10**-i
...     y = exp(x)
...     print(x, log(y))
... 
1 1.0
0.1 0.10000000000000007
0.01 0.009999999999999893
0.001 0.001000000000000043
0.0001 0.00010000000000004326
9.999999999999999e-06 9.999999999902983e-06
1e-06 9.99999999962017e-07
1e-07 9.999999994336786e-08
1e-08 9.999999889225291e-09
1e-09 1.000000082240371e-09

如果您仔细观察,您可能会注意到有错误悄悄潜入。 当 = 0 时,没有错误。 当 = 1 时,它打印0.1for 和0.10000000000000007forlog(y),这仅在 16 位数字后是错误的。 到 time = 9 时,log()中的一半数字是错误的log(y)

由于我们知道真正的答案是什么( ),我们可以很容易地计算出近似的相对误差是多少:

>>> for i in range(10):
...     x = 10**-i
...     y = exp(x)
...     z = log(y)
...     print(i, abs((x - z)/z))
... 
0 0.0
1 6.938893903907223e-16
2 1.0755285551056319e-14
3 4.293440603042413e-14
4 4.325966668215291e-13
5 9.701576564765975e-12
6 3.798286318045685e-11
7 5.663213319457187e-10
8 1.1077471033430869e-08
9 8.224036409872509e-08

每一步都会让我们失去大约一位数的准确性! 为什么?

每个运算10**-iexp(x)log(y)只在结果中引入一个很小的相对误差,小于10?15

假设exp(x)引入了一个相对误差 ,返回数字⋅(1 + ) 而不是(毕竟,这是一个不能用有限数字字符串表示的超越数字)。 我们知道 | |<10−15,但是当我们尝试计算 log(⋅(1 + )) 作为 log() = 的近似值时会发生什么?

我们可能希望得到 ⋅(1 + ) 非常小的地方。 但是 log(⋅(1 + )) = log() + log(1 + ) = + log(1 + ) = ⋅(1 + log(1 + )/),所以 = log(1 + )/。 即使很小,≈ 10也会随着增加而越来越接近零,因此错误日志 (1 + )/≈/会随着增加而变得越来越严重,因为 1/→ ∞。

我们说对数函数在 1 附近是有条件的:如果你以接近 1 的输入近似值来评估它,它可以将非常小的输入误差变成任意大的输出误差。事实上,在exp(x)舍入到 1 之前,您只能再走几步,因此log(y)完全返回零。

这并不是因为任何关于浮点的特别之处——任何类型的近似都会对 log 产生相同的效果! 函数的条件数是数学函数本身的属性,而不是浮点算术系统的属性。 如果输入来自物理测量,您可能会遇到同样的问题。


这与函数expm1log1p存在的原因有关。 尽管函数 log( ) 在 1 附近是有条件的,但函数 log(1 + ) 不是,所以log1p(y)计算它比log(1 + y)计算它更准确。 同样,当≈ 1 时exp(x) - 1中的减法会受到灾难性的抵消,因此expm1(x)计算− 1 比评估exp(x) - 1更准确。

当然,expm1log1pexplog不是相同的函数,但有时您可以根据它们重写子表达式以避免条件不佳的域。 在这种情况下,例如,如果您将 log()重写为 log(1 + [− 1]),并使用expm1log1p来计算它,则往返行程通常精确计算:

>>> for i in range(10):
...     x = 10**-i
...     y = expm1(x)
...     z = log1p(y)
...     print(i, x, z, abs((x - z)/z))
... 
0 1 1.0 0.0
1 0.1 0.1 0.0
2 0.01 0.01 0.0
3 0.001 0.001 0.0
4 0.0001 0.0001 0.0
5 9.999999999999999e-06 9.999999999999999e-06 0.0
6 1e-06 1e-06 0.0
7 1e-07 1e-07 0.0
8 1e-08 1e-08 0.0
9 1e-09 1e-09 0.0

出于类似的原因,您可能希望将(exp(x) - 1)/x重写为expm1(x)/x如果你不这样做,那么当exp(x)返回⋅(1 + ) 而不是时,你最终会得到 (⋅(1 + ) − 1)/= (− 1 + )/= ( − 1)⋅[1 +/( − 1)]/,它可能会再次爆炸,因为错误是/(− 1) ≈/。


然而,第二个定义似乎产生了正确的结果,这不仅仅是运气!发生这种情况是因为复合误差(/来自分子中的exp(x) - 1,/来自分母中的log(exp(x)))相互抵消。 第一个定义计算分子和分母都很准确,但第二个定义计算它们同样糟糕

特别是,当≈ 0 时,我们有

log(⋅(1 + )) = + log(1 + ) ≈ +

⋅(1 + ) − 1 = + − 1≈ 1 + + − 1 =+.

请注意,这两种情况都是相同的,因为这是使用exp(x)近似的错误

您可以通过与expm1(x)/x进行比较来实验测试这一点(这是一个保证具有低相对误差的表达式,因为除法永远不会使错误变得更糟):

>>> for i in range(10):
...     x = 10**-i
...     u = (exp(x) - 1)/log(exp(x))
...     v = expm1(x)/x
...     print(u, v, abs((u - v)/v))
... 
1.718281828459045 1.718281828459045 0.0
1.0517091807564762 1.0517091807564762 0.0
1.0050167084168058 1.0050167084168058 0.0
1.0005001667083415 1.0005001667083417 2.2193360112628554e-16
1.0000500016667082 1.0000500016667084 2.220335028798222e-16
1.0000050000166667 1.0000050000166667 0.0
1.0000005000001666 1.0000005000001666 0.0
1.0000000500000017 1.0000000500000017 0.0
1.000000005 1.0000000050000002 2.2204460381480824e-16
1.0000000005 1.0000000005 0.0

分子和分母的这个近似值 + 对于最接近零是最好的,对于离零最远的是最差的——但随着离零越来越远,exp(x) - 1(来自灾难性消除)和log(exp(x))(来自接近 1 的对数的不良调节)的误差放大倍数无论如何都会减小,所以答案仍然是准确的。


但是,第二个定义中的良性取消仅在接近零时才有效,以至于exp(x)只是四舍五入为 1 — 此时,exp(x) - 1log(exp(x))都给出零,最终将尝试计算 0/0,从而产生 NaN 和浮点异常。

所以你应该在实践中使用expm1(x)/x,但这是一个令人高兴的意外,在exp(x)为 1 的边缘情况之外,两个错误在(exp(x) - 1)/log(exp(x))给出良好的准确性方面是正确的,即使(exp(x) - 1)/x和(出于类似的原因)expm1(x)/log(exp(x))都给出了不好的准确性。

为什么这个结果比等效函数的另一个结果更准确

运气 不好。 这两个函数都不可靠传递大约(17-n)/2位。

结果的差异依赖于explog的共同实现,但没有指定的语言。


对于负n幂为 10 的给定x,扩展数学结果为1.(n zeroes)5(n-1 zeros)166666....

随着exp(x) - 1,这两个函数都失去了大约一半的显著性,因为n由于exp(small_value)1的严格计算而变得更大。

exp(x)-1保留了数学值的外观,只要典型浮点数具有典型的 17 位左右的精度。

n==7时,exp(x) = 1.00000010000000494...exp(x)-11.0000000494...e-07

n = 8:车轮掉了下来。

n==8时,exp(x) = 1.00000000999999994...exp(x)-19.99999993922529029...e-09

9.99999993922529029...e-09

不够近,无法1.000000005...e-08

此时,两个函数都会在1.00000000x位置遭受精度损失。

<小时 />

n上升到16左右,然后一切都崩溃了

最新更新