主题:[讨论]基本功训练 04】浮点数计算过程中的误差
asymptotic
[专家分:16630] 发布于 2010-10-13 09:10:00
用 kind = 4 的 real、integer 计算如下表达式:
100 * ( (1 + x / ND) ** ND - 1 ) / ( x / ND )
其中,ND = 365, x = 0.06; 精确答案 -- 37614.05。
编写程序,精确到有效数字第六位,第七位可以有区别。
提示:这个问题主要是设计算法,不能考虑更高精度的浮点数、整数,
以减少浮点数计算过程中的误差(当然,若是这样,则要求至少 14 位
有效数字正确)。
回复列表 (共57个回复)
21 楼
jstzhurj [专家分:4680] 发布于 2010-10-13 19:56:00
[quote]把幂运算用x=e^(ln(x))转换运算挺依赖lnx和e^x运算的精度. 我错觉一直认为他们运算的精度不高.
楼主, 你的准确值是什么算法得到的?[/quote]
lnx和e^x的运算误差,相对于(1+x/ND)^ND=e^x 带来的误差,应该可以忽略不计了...
22 楼
jstzhurj [专家分:4680] 发布于 2010-10-13 21:43:00
[quote][quote]把幂运算用x=e^(ln(x))转换运算挺依赖lnx和e^x运算的精度. 我错觉一直认为他们运算的精度不高.
楼主, 你的准确值是什么算法得到的?[/quote]
lnx和e^x的运算误差,相对于(1+x/ND)^ND=e^x 带来的误差,应该可以忽略不计了...[/quote]
空口无凭,为了证实这一点,编写了一个简单的代码,不做任何算法的改进,只是单精度变成双精度去运行:
program main
implicit none
integer(2):: nd=365
real(8)::x=0.06
write(*,*) (1+x/nd)**nd,exp(x) !用exp(x)替代(1+x/nd)**nd
write(*,*) 100.*nd/x*((1+x/nd)**nd-1), 100.*nd/x*(exp(x)-1)
end program
运行结果
1.06183130925409 1.06183654512133
37614.0473036463 37617.2324562839
请按任意键继续. . .
(1+x/nd)**nd=exp(x)带入的误差一目了然。
23 楼
jstzhurj [专家分:4680] 发布于 2010-10-13 22:02:00
对11楼作一点改进,黔驴技穷,就等着学习楼主的方法了。
program main
implicit none
integer :: nd=365,i
real(kind=4) :: x=0.06,y,z,term,esp=1.e-8
y=x/nd
term=100./y
z=0.
i=1
do while(term/z.gt.esp)
term=term*nd*y/i
z=z+term
i=i+1
nd=nd-1
enddo
write(*,*) z
end program
37614.05
请按任意键继续. . .
24 楼
yeg001 [专家分:14390] 发布于 2010-10-13 23:39:00
[quote][quote]把幂运算用x=e^(ln(x))转换运算挺依赖lnx和e^x运算的精度. 我错觉一直认为他们运算的精度不高.
楼主, 你的准确值是什么算法得到的?[/quote]
lnx和e^x的运算误差,相对于(1+x/ND)^ND=e^x 带来的误差,应该可以忽略不计了...[/quote]
可以说那样做的方法的前提是ln和e的运算精度足够高. 确实我不了解这些函数具体算法, 能那么精确.
25 楼
yeg001 [专家分:14390] 发布于 2010-10-13 23:44:00
既然jstzhurj兄那么有热情, 改了那么多个程序, 我也来再献丑抛砖引玉吧.
我直接对 (1+x/nd)**nd 作多项式展开. 显然这样做也是基于x/nd是一个小值. 这样的话多项式只需要求前面几项就能达到楼主的要求.
program main
implicit none
integer, parameter :: SP=4
integer(kind=4) :: nd=365, i
real(kind=SP) :: x=0.06, y, z, temp
y = 1.0_sp + x
i = 1
temp = real(nd, kind=sp)
DO
temp = temp*real((nd-i), kind=sp)/real(i+1, kind=sp)
z = temp*((x/real(nd, kind=sp))**(i+1))
y = y + z
i = i +1
IF((z)<1e-8) EXIT
END DO
write(*, *) i-1
write(*, *) y
write(*, *) 100.*(y-1.0_sp)/(x/real(nd, kind=sp))
end program
运行结果
4
1.061831
37614.00
Press any key to continue
26 楼
jstzhurj [专家分:4680] 发布于 2010-10-13 23:53:00
[quote]既然jstzhurj兄那么有热情, 改了那么多个程序, 我也来再献丑抛砖引玉吧.
我直接对 (1+x/nd)**nd 作多项式展开. 显然这样做也是基于x/nd是一个小值. 这样的话多项式只需要求前面几项就能达到楼主的要求.
program main
implicit none
integer, parameter :: SP=4
integer(kind=4) :: nd=365, i
real(kind=SP) :: x=0.06, y, z, temp
y = 1.0_sp + x
i = 1
temp = real(nd, kind=sp)
DO
temp = temp*real((nd-i), kind=sp)/real(i+1, kind=sp)
z = temp*((x/real(nd, kind=sp))**(i+1))
y = y + z
i = i +1
IF((z)<1e-8) EXIT
END DO
write(*, *) i-1
write(*, *) y
write(*, *) 100.*(y-1.0_sp)/(x/real(nd, kind=sp))
end program
运行结果
4
1.061831
37614.00
Press any key to continue[/quote]
y = 1.0_sp + x换成 y = x;
write(*, *) 100.*(y-1.0_sp)/(x/real(nd, kind=sp)) 换成 write(*, *) 100.*y/(x/real(nd, kind=sp)) 效果立现。
27 楼
yeg001 [专家分:14390] 发布于 2010-10-14 00:04:00
也是, 当时展开指针对那个幂, 如果把 -1 考虑进去应该效果更好.
28 楼
jstzhurj [专家分:4680] 发布于 2010-10-14 00:12:00
[quote]也是, 当时展开指针对那个幂, 如果把 -1 考虑进去应该效果更好.[/quote]
可见,对这个程序,简单的加1,减1带来的误差被放大到0.05之巨!!!!至此,你我殊途同归(也许你没有仔细看我的程序),现在的问题是,楼主办法是什么,咱们也得学习一下,期待中......................[em12][em18][em2][em10][em9]
29 楼
yeg001 [专家分:14390] 发布于 2010-10-14 00:19:00
[quote][quote]也是, 当时展开指针对那个幂, 如果把 -1 考虑进去应该效果更好.[/quote]
可见,对这个程序,简单的加1,减1带来的误差被放大到0.05之巨!!!!至此,你我殊途同归(也许你没有仔细看我的程序),现在的问题是,楼主办法是什么,咱们也得学习一下,期待中......................[em12][em18][em2][em10][em9][/quote]
确实没认真看, 不好意思. 我以为你顺着前面的代码作了误差判断的修正而已.
相差那个1, 有效数字向右就移动, 或者说是有后面的小数被这个1吃了.
30 楼
asymptotic [专家分:16630] 发布于 2010-10-14 00:45:00
yeg001 网友的思想我是看懂了,能很好的解决某在原帖中提出的问题,但若是将 x = 9.06 ,则我电脑 CPU 是无法完成这个任务了。
jstzhurj 网友在 23 楼提供的程序,看来还不错,但我没有看明白算法,请明示。
我更倾向于这样的代码:不仅 x 小的时候可以,比如 x = 0.05, 同时 x 较大的时候亦可。同样,对 ND 也是如此。 另外,代码尽量优化,时间复杂度尽量小。
这样一来,类似 Taylor 展开就不好使了,因为 Taylor 收敛的快慢是对某点而言的,要类似于“一致收敛”的算法来计算,则适用面更广。
感谢 以上两位网友参与讨论,也希望别的网友能提出你们的看法。
我来回复