为什么这个结果比等效函数的另一个结果更准确
Why is this result more accurate than another result by equivalent functions
我有以下问题,为什么 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 具有更多位数的精度。
Why is this result more accurate than another result by equivalent functions
运气不好。这两个函数都不能可靠地传递 (17-n)/2
个数字。
结果的差异取决于 exp
和 log
的通用实现,但未指定语言。
对于给定的 x
作为 10 的负 n
次方,扩展数学结果为 1.(n zeroes)5(n-1 zeros)166666....
对于exp(x) - 1
,由于exp(small_value)
和1
.
的严格计算,n
变得更大,这两个函数都失去了大约一半的意义
exp(x)-1
保留与典型浮点数一样长的数学值及其典型的 17 位精度数字。
当n
== 7
, exp(x) = 1.00000010000000494...
和 exp(x)-1
为 1.0000000494...e-07
.
n = 8
: 轮子掉了
当n
== 8
, exp(x) = 1.00000000999999994...
和 exp(x)-1
为 9.99999993922529029...e-09
.
9.99999993922529029...e-09
离 1.000000005...e-08
.
不够近
此时,两个函数都在 1.00000000x
处失去了精度。
让n
上升到16左右,然后一切都崩溃了
先从x
和log(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.1
for 和 0.10000000000000007
for log(y)
,仅在 16 位后错误。
到时间 = 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 + ) 其中 非常小。
但是 log(⋅(1 + )) = log() + log(1 + ) = + log(1 + ) = ⋅(1 + log(1 + )/), 所以 = log(1 + )/。
即使 很小,≈ 10− 随着增加越来越接近零,所以误差 log(1 + )/ ≈ / 会随着增加而变得越来越糟,因为 1/ → ∞.
我们说对数函数在 1 附近是 病态的:如果你以接近 1 的输入的近似值来计算它,它可以将一个非常小的输入错误变成任意大的输出错误。 事实上,在 exp(x)
舍入为 1 并因此 log(y)
returns 为零之前,您只能再执行几步正是。
这并不是因为浮点数有什么特别之处——任何一种近似值都会对 log 产生相同的效果!
函数的条件数是数学函数本身的属性,而不是浮点运算系统的。
如果输入来自物理测量,您可以 运行 解决同样的问题。
这与函数expm1
和log1p
存在的原因有关。
虽然函数 log() 在 1 附近是病态的,但函数 log(1 + ) 不是,所以 log1p(y)
比 log(1 + y)
计算它更准确。
类似地,exp(x) - 1
中的减法在 ≈ 1 时服从 catastrophic cancellation,因此 expm1(x)
计算 - 1 比评估 exp(x) - 1
更准确。
当然,expm1
和 log1p
与 exp
和 log
是不同的函数,但有时您可以根据它们重写子表达式以避免错误-条件域。
在这种情况下,例如,如果将 log() 重写为 log(1 + [ − 1]),并使用 expm1
和 log1p
来计算它,往返通常是精确计算的:
>>> 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)
returns ⋅(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) - 1
和 log(exp(x))
都给出零,最终会尝试计算 0/0,这会产生 NaN 和浮点异常。
所以你应该在实践中使用expm1(x)/x
,但是在之外exp(x)
的边缘情况是一个幸运的意外1,即使 (exp(x) - 1)/x
和(出于类似原因)expm1(x)/log(exp(x))
都给出了错误的准确度,(exp(x) - 1)/log(exp(x))
中的两个错误也给出了正确的准确度。
我有以下问题,为什么 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 具有更多位数的精度。
Why is this result more accurate than another result by equivalent functions
运气不好。这两个函数都不能可靠地传递 (17-n)/2
个数字。
结果的差异取决于 exp
和 log
的通用实现,但未指定语言。
对于给定的 x
作为 10 的负 n
次方,扩展数学结果为 1.(n zeroes)5(n-1 zeros)166666....
对于exp(x) - 1
,由于exp(small_value)
和1
.
n
变得更大,这两个函数都失去了大约一半的意义
exp(x)-1
保留与典型浮点数一样长的数学值及其典型的 17 位精度数字。
当n
== 7
, exp(x) = 1.00000010000000494...
和 exp(x)-1
为 1.0000000494...e-07
.
n = 8
: 轮子掉了
当n
== 8
, exp(x) = 1.00000000999999994...
和 exp(x)-1
为 9.99999993922529029...e-09
.
9.99999993922529029...e-09
离 1.000000005...e-08
.
此时,两个函数都在 1.00000000x
处失去了精度。
让n
上升到16左右,然后一切都崩溃了
先从x
和log(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.1
for 和 0.10000000000000007
for log(y)
,仅在 16 位后错误。
到时间 = 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 + ) 其中 非常小。 但是 log(⋅(1 + )) = log() + log(1 + ) = + log(1 + ) = ⋅(1 + log(1 + )/), 所以 = log(1 + )/。 即使 很小,≈ 10− 随着增加越来越接近零,所以误差 log(1 + )/ ≈ / 会随着增加而变得越来越糟,因为 1/ → ∞.
我们说对数函数在 1 附近是 病态的:如果你以接近 1 的输入的近似值来计算它,它可以将一个非常小的输入错误变成任意大的输出错误。 事实上,在 exp(x)
舍入为 1 并因此 log(y)
returns 为零之前,您只能再执行几步正是。
这并不是因为浮点数有什么特别之处——任何一种近似值都会对 log 产生相同的效果! 函数的条件数是数学函数本身的属性,而不是浮点运算系统的。 如果输入来自物理测量,您可以 运行 解决同样的问题。
这与函数expm1
和log1p
存在的原因有关。
虽然函数 log() 在 1 附近是病态的,但函数 log(1 + ) 不是,所以 log1p(y)
比 log(1 + y)
计算它更准确。
类似地,exp(x) - 1
中的减法在 ≈ 1 时服从 catastrophic cancellation,因此 expm1(x)
计算 - 1 比评估 exp(x) - 1
更准确。
expm1
和 log1p
与 exp
和 log
是不同的函数,但有时您可以根据它们重写子表达式以避免错误-条件域。
在这种情况下,例如,如果将 log() 重写为 log(1 + [ − 1]),并使用 expm1
和 log1p
来计算它,往返通常是精确计算的:
>>> 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)
returns ⋅(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) - 1
和 log(exp(x))
都给出零,最终会尝试计算 0/0,这会产生 NaN 和浮点异常。
所以你应该在实践中使用expm1(x)/x
,但是在之外exp(x)
的边缘情况是一个幸运的意外1,即使 (exp(x) - 1)/x
和(出于类似原因)expm1(x)/log(exp(x))
都给出了错误的准确度,(exp(x) - 1)/log(exp(x))
中的两个错误也给出了正确的准确度。