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

我有以下问题,为什么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.10000000000000007for log(y),只有在 16 位后才出错。到 time = 9 时,log(y)log()。

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

>>> 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**-i, exp(x), 和log(y)仅在结果中引入了一个很小的相对误差,小于 10 ?15

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

我们可能希望得到 ?(1 + ) 哪里非常小。但是登录(?(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)。同样,在减法exp(x) - 1受到灾难性的取消时? 1,所以expm1(x)计算? 1 比评估更准确exp(x) - 1

expm1log1p不是相同的功能,exp并且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)/xexpm1(x)/x.
如果你不这样做,那么什么时候exp(x)回来?(1 + ) 而不是 , 你最终会得到 (?(1 + ) ? 1)/ = (? 1 +)/ = (? 1)?[1 +/(? 1)]/,这又可能会爆炸,因为错误是/(? 1) ? /.


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

特别是,当 ? 0,我们有

日志(?(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,其产生楠浮点例外。

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


以上是为什么这个结果比等效函数的另一个结果更准确的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>