让我们说我想在数值上计算f(x)= x(pi)处的x = pi / 2的一阶导数。 df / dx = -sin(x),因此是df / dx = -1。为此,我使用最简单的公式:
df / dx =(f(x + h)-f(x))/ h + O(h)。
这里O(h)是误差,它与h成比例,因此,当h变为零时,数学上O(h)变为零。我知道在电脑里事情是不同的。
我是否应该期望如果我使用双精度,这应该给我15到17十进制有效数字,我应该能够达到精确的结果df / dx = -1.0〜10 ^ -15?即我应该找(df / dx)_numerical + 1.0~10 ^ -15?
这是我发现的不同h的值:
h值:
[0.0001, 1e-05, 1e-06, 1e-07, 1e-08, 1e-09, 1e-10]
(DF / DX)_numerical:
[-0.9999999983332231,
-0.9999999999898844,
-0.9999999999175667,
-1.0000000005838656,
-0.999999993922529,
-1.000000082740371,
-1.000000082740371]
这是预期的吗?为什么?为什么h = 10 ^ -5?
获得最佳结果答案 0 :(得分:3)
使用有限差分计算导数很容易loss of significance。问题在于您减去的背景,而不是您正在进行的分工。基本上,当cos(x)-cos(x+delta)
和cos(x)
几乎为零时,cos(x+delta)
会正常工作......但是当他们离开零时(但仍然接近彼此),结果的精度会急剧下降。在某一点上,使用较小的增量的准确度的增加将被重要性损失导致的精度降低所抵消。对于你来说,它看起来像是在1e-5左右发生的,但这不是基本的(在那个区域,错误往往会反弹很多)。
关于如何进行数值稳定的有限差分已经写了很多,但是最重要的规则是“不要因为你的差异很小而过于贪婪”。对于更深入(和更少模糊)的信息,我可以推荐Forman Acton的数值方法(通常)工作。
对不起,我忘了提到最重要的一点。分子中重要性的丧失很重要,但x+h
中重要性的丧失更是如此。而这部分实际上很容易修复。
随着x
越来越大,x+h
的近似值会越来越差(再次失去意义)。基本上,您用于评估的步长与分母中的步长不匹配。但!由于您并不真正关心h
的完全值,因此您可以在h
得到x+h
之后找出最终使用的x2=x+h
值四舍五入,并在分母中使用它。基本上,您计算h'=x2-x
,然后计算h'
。在x >> h
(Sterbenz' s定理)时,import math
def calcDerivAt_orig(f, x, h):
x1 = x
x2 = x+h
y1 = f(x1)
y2 = f(x2)
return (y2-y1)/h
def calcDerivAt_fixed(f, x, h):
x1 = x
x2 = x+h
y1 = f(x1)
y2 = f(x2)
return (y2-y1)/(x2-x1)
for h in [0.0001, 1e-05, 1e-06, 1e-07, 1e-08, 1e-09, 1e-10]:
dOrig = calcDerivAt_orig(math.cos, math.pi/2, h)
origErr = abs(-1 - dOrig)
dFixed = calcDerivAt_fixed(math.cos, math.pi/2, h)
fixedErr = abs(-1 - dFixed)
print("h = {}: origErr = {}, fixedErr = {}".format(h, origErr, fixedErr))
的计算是精确的,消除了特定的重要性损失。
示例代码:
h = 0.0001: origErr = 1.66677693869e-09, fixedErr = 1.66666680457e-09
h = 1e-05: origErr = 1.01155750443e-11, fixedErr = 1.6666779068e-11
h = 1e-06: origErr = 8.24332824223e-11, fixedErr = 1.66644475996e-13
h = 1e-07: origErr = 5.83865622517e-10, fixedErr = 1.55431223448e-15
h = 1e-08: origErr = 6.07747097092e-09, fixedErr = 0.0
h = 1e-09: origErr = 8.27403709991e-08, fixedErr = 0.0
h = 1e-10: origErr = 8.27403709991e-08, fixedErr = 0.0
产生:
x=pi/2
一点也不差。当然,我们选择cos(x)
作弊,因为x=1.5
在那附近接近零;在h
或者其他什么时候,当h = 0.0001: origErr = 3.53519750529e-06, fixedErr = 3.5351976152e-06
h = 1e-05: origErr = 3.53675653653e-07, fixedErr = 3.53669118991e-07
h = 1e-06: origErr = 3.52831192041e-08, fixedErr = 3.53651797846e-08
h = 1e-07: origErr = 4.03034106089e-09, fixedErr = 3.44793649187e-09
h = 1e-08: origErr = 5.5453325265e-09, fixedErr = 5.1691428915e-10
h = 1e-09: origErr = 7.21702790862e-08, fixedErr = 1.03628251535e-08
h = 1e-10: origErr = 1.6659127966e-08, fixedErr = 6.58739718329e-08
缩小时,你仍会看到错误的膨胀,这是因为我最初描述的重要性已经消失了:
const unfreezeObject = JSON.parse(JSON.stringify(freezeObject))