回 帖 发 新 帖 刷新版面

主题:[讨论]基本功训练 04】浮点数计算过程中的误差

用 kind = 4 的 real、integer 计算如下表达式:
100 * ( (1 + x / ND) ** ND - 1 ) / ( x / ND )
其中,ND = 365, x = 0.06; 精确答案 -- 37614.05。
编写程序,精确到有效数字第六位,第七位可以有区别。 

提示:这个问题主要是设计算法,不能考虑更高精度的浮点数、整数,
以减少浮点数计算过程中的误差(当然,若是这样,则要求至少 14 位 
有效数字正确)。

回复列表 (共57个回复)

21 楼

[quote]把幂运算用x=e^(ln(x))转换运算挺依赖lnx和e^x运算的精度.  我错觉一直认为他们运算的精度不高.
楼主, 你的准确值是什么算法得到的?[/quote]

lnx和e^x的运算误差,相对于(1+x/ND)^ND=e^x 带来的误差,应该可以忽略不计了...

22 楼

[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 楼


对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 楼

[quote][quote]把幂运算用x=e^(ln(x))转换运算挺依赖lnx和e^x运算的精度.  我错觉一直认为他们运算的精度不高.
楼主, 你的准确值是什么算法得到的?[/quote]

lnx和e^x的运算误差,相对于(1+x/ND)^ND=e^x 带来的误差,应该可以忽略不计了...[/quote]
可以说那样做的方法的前提是ln和e的运算精度足够高. 确实我不了解这些函数具体算法, 能那么精确.

25 楼

既然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 楼

[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 楼

也是, 当时展开指针对那个幂, 如果把 -1 考虑进去应该效果更好.

28 楼

[quote]也是, 当时展开指针对那个幂, 如果把 -1 考虑进去应该效果更好.[/quote]

可见,对这个程序,简单的加1,减1带来的误差被放大到0.05之巨!!!!至此,你我殊途同归(也许你没有仔细看我的程序),现在的问题是,楼主办法是什么,咱们也得学习一下,期待中......................[em12][em18][em2][em10][em9]

29 楼

[quote][quote]也是, 当时展开指针对那个幂, 如果把 -1 考虑进去应该效果更好.[/quote]

可见,对这个程序,简单的加1,减1带来的误差被放大到0.05之巨!!!!至此,你我殊途同归(也许你没有仔细看我的程序),现在的问题是,楼主办法是什么,咱们也得学习一下,期待中......................[em12][em18][em2][em10][em9][/quote]
确实没认真看, 不好意思. 我以为你顺着前面的代码作了误差判断的修正而已.
相差那个1, 有效数字向右就移动, 或者说是有后面的小数被这个1吃了.

30 楼

yeg001  网友的思想我是看懂了,能很好的解决某在原帖中提出的问题,但若是将 x = 9.06 ,则我电脑 CPU 是无法完成这个任务了。
jstzhurj 网友在 23 楼提供的程序,看来还不错,但我没有看明白算法,请明示。

我更倾向于这样的代码:不仅 x 小的时候可以,比如 x = 0.05, 同时 x 较大的时候亦可。同样,对 ND 也是如此。 另外,代码尽量优化,时间复杂度尽量小。

这样一来,类似 Taylor 展开就不好使了,因为 Taylor 收敛的快慢是对某点而言的,要类似于“一致收敛”的算法来计算,则适用面更广。

感谢 以上两位网友参与讨论,也希望别的网友能提出你们的看法。

我来回复

您尚未登录,请登录后再回复。点此登录或注册