主题:[讨论]x*log(1.0+x)/((1.0+x)-1.0) 的意义?
jstzhurj
[专家分:4680] 发布于 2010-10-14 22:08:00
有必要开一个新帖专门讨论一下。
asymptotic老师在他的帖子《基本功训练 04】浮点数计算过程中的误差》中给出了计算ln(1+x)代码,如下:
! compute ln(1 + x)
function Log_1Px(x)
implicit none
real, intent(in):: x
real:: Log_1Px
if ( 1.0 + x == 1.0 ) then
Log_1Px = x !* (1.0 - 0.5 * x)
else
! the parenthesis is necessary, must not be omitted.
Log_1Px = x * log(1.0 + x) / ((1.0 + x) - 1.0)
end if
return
end function
代码中等式 Log_1Px = x * log(1.0 + x) / ((1.0 + x) - 1.0)的意义何在? 与直接用 Log_1Px = log(1.0 + x) 有什么区别?在什么情况下必须用Log_1Px = x * log(1.0 + x) / ((1.0 + x) - 1.0) ?
回复列表 (共19个回复)
沙发
aliouying [专家分:1150] 发布于 2010-10-15 00:06:00
我的理解: x 与1.0+x-1.0中间是会产生累积误差的(这个很小)即这两个数有效位数后面的随机数是不一样的。
我讲不太明白,总之是因为x太小,直接计算后面的这些随机数影响来结果。
在gpu写代码的时候我记得有个例子用到过一些方法来处理这些累加误差。
期待高人解惑~~
板凳
jstzhurj [专家分:4680] 发布于 2010-10-15 00:38:00
直观上看 x * log(1.0 + x) / ((1.0 + x) - 1.0) 对提高精度没有帮助,不明白为什么要这样做,以我的水平,我还无法理解其中的奥妙,期待高人答疑解惑。
3 楼
asymptotic [专家分:16630] 发布于 2010-10-15 08:17:00
我略作解释:
设 .+. 表示 浮点数加法,+ 为数学上定义的普通加法;设 0 =< x < 0.75 可以用二进制 “精确”表示,epsilon = 2 ** (-23)。 下面我们来分析误差:
1 -- 1 .+. x == 1 时,误差为 0.5 epsilon
2 -- 当 1 .+. x /= 1 时,设 1.0 .+. x = 1.0 + xa,相对误差小于 2 epsilon;
3 -- (1.0 .+. x) - 1.0 = xa
4 -- 函数 log(1 + x) / x 对 x 的变化不敏感,误差可控,
log(1 + xa) / xa 可以很好的近似 log(1 + x) / x,相对误差小于 epsilon
5 -- 若 log 和 / 舍入误差 为 0.5 epsilon, 则表达式
x * log(1.0 + x) / ((1.0 + x) - 1.0) 的相对误差小于 5 epsilon
4 楼
jstzhurj [专家分:4680] 发布于 2010-10-15 08:26:00
[quote]我略作解释:
设 .+. 表示 浮点数加法,+ 为数学上定义的普通加法;设 0 =< x < 0.75 可以用二进制 “精确”表示,epsilon = 2 ** (-23)。 下面我们来分析误差:
1 -- 1 .+. x == 1 时,误差为 0.5 epsilon
2 -- 当 1 .+. x /= 1 时,设 1.0 .+. x = 1.0 + xa,相对误差小于 2 epsilon;
3 -- (1.0 .+. x) - 1.0 = xa
4 -- 函数 log(1 + x) / x 对 x 的变化不敏感,误差可控,
log(1 + xa) / xa 可以很好的近似 log(1 + x) / x,相对误差小于 epsilon
5 -- 若 log 和 / 舍入误差 为 0.5 epsilon, 则表达式
x * log(1.0 + x) / ((1.0 + x) - 1.0) 的相对误差小于 5 epsilon[/quote]
理论上的东西看起来很费劲,能否编个小程序解释其差异呢?
5 楼
jstzhurj [专家分:4680] 发布于 2010-10-15 09:06:00
当 1 .+. x /= 1 时,能直接分析一下 log(1.0 + x) 的误差吗?还是非得用 x * log(1.0 + x) / ((1.0 + x) - 1.0) 这个式子分析误差吗?!
6 楼
asymptotic [专家分:16630] 发布于 2010-10-15 11:48:00
log(1.0 + x) 在 x == 0 附近,相对误差有含义吗?所以,库函数对这种情况只是用了 绝对误差,但我们有时确实需要相对误差,所以,设计 Log_1Px 函数是有必要的,IMSL 库也有类似的函数 ALNREL。
再举一个粒子: EXM1(x) = (exp(x) - 1) / x 在 x = 0 附近,调用库函数 exp 直接计算的话,相对误差达不到要求的。
原来写的文字未动,仅修改如下:
再举一例,EXM1(x) = exp(x) - 1 在 x = 0 附近,调用库函数 exp 直接计算的话,相对误差达不到要求的。需要先为函数
EXM1DX(x) = (exp(x) - 1) / x
设计算法,然后再用 EXM1 = x * EXM1DX(x)
7 楼
jstzhurj [专家分:4680] 发布于 2010-10-15 12:28:00
[quote]log(1.0 + x) 在 x == 0 附近,相对误差有含义吗?所以,库函数对这种情况只是用了 绝对误差,但我们有时确实需要相对误差,所以,设计 Log_1Px 函数是有必要的,IMSL 库也有类似的函数 ALNREL。
再举一个粒子: EXM1(x) = (exp(x) - 1) / x 在 x = 0 附近,调用库函数 exp 直接计算的话,相对误差达不到要求的。[/quote]
处理 x==0 附近,我同意Log_1Px 是必要的!问题在else后面的语句, 有必要用 Log_1Px = x * log(1.0 + x) / ((1.0 + x) - 1.0) 代替Log_1Px = log(1.0 + x)吗?现在讨论的是这个是否必要!还请麻烦编个小程序说明其必要性!否则我只能认为多此一举。
8 楼
doctorlive [专家分:800] 发布于 2010-10-15 15:33:00
也许这个更直观的解释你的疑问:
program Log1
implicit none
integer::i
real:: x
real:: Log_1Px
do i=1,6
read(*,*) x
if ( 1.0 + x == 1.0 ) then
Log_1Px = x !* (1.0 - 0.5 * x)
else
! the parenthesis is necessary, must not be omitted.
Log_1Px = x * log(1.0 + x) / ((1.0 + x) - 1.0)
write(*,*) x,(1.0 + x) - 1.0
end if
write(*,*) Log_1Px
enddo
end program
结果:
0.0001
9.9999997E-05 9.9999997E-05
9.9994999E-05
0.000001
1.0000000E-06 1.0000000E-06
9.9999954E-07
0.000000001
9.9999997E-10 1.0000001E-09
9.9999997E-10
0.000000000001
1.0000000E-12 1.0000889E-12
1.0000000E-12
0.000000000000001
1.0000000E-15 1.1102230E-15
1.0000000E-15
0.000000000000000001
1.0000000E-18
总结:从以上运行结果可以看出:当x=0.000000001时,x与(1.0 + x) - 1.0的结果就出现了不同,当x=0.000000000000000001时,1.0 + x == 1.0
9 楼
jstzhurj [专家分:4680] 发布于 2010-10-15 15:58:00
[quote]也许这个更直观的解释你的疑问:
program Log1
implicit none
integer::i
real:: x
real:: Log_1Px
do i=1,6
read(*,*) x
if ( 1.0 + x == 1.0 ) then
Log_1Px = x !* (1.0 - 0.5 * x)
else
! the parenthesis is necessary, must not be omitted.
Log_1Px = x * log(1.0 + x) / ((1.0 + x) - 1.0)
write(*,*) x,(1.0 + x) - 1.0
end if
write(*,*) Log_1Px
enddo
end program
结果:
0.0001
9.9999997E-05 9.9999997E-05
9.9994999E-05
0.000001
1.0000000E-06 1.0000000E-06
9.9999954E-07
0.000000001
9.9999997E-10 1.0000001E-09
9.9999997E-10
0.000000000001
1.0000000E-12 1.0000889E-12
1.0000000E-12
0.000000000000001
1.0000000E-15 1.1102230E-15
1.0000000E-15
0.000000000000000001
1.0000000E-18
总结:从以上运行结果可以看出:当x=0.000000001时,x与(1.0 + x) - 1.0的结果就出现了不同,当x=0.000000000000000001时,1.0 + x == 1.0 [/quote]
你的程序不仅没有消除我的疑问,反而进一步更加深了我的疑问!
当x=0.000000001时, 两者是出现了预料中的不同,x=0.99999997E-09 ,(1.0 + x) - 1.0=1.0000001E-09,请问0.99999997与1.0000001谁更接近1?谁的误差更大?
10 楼
doctorlive [专家分:800] 发布于 2010-10-15 21:34:00
在计算机计算时,1.0应该是用0.9999999999.....代替的,这样的话,机器算会发现0.99999997与1.0000001一样的接近0.99999999,但笔算,后者更接近1.0。
我来回复