回 帖 发 新 帖 刷新版面

主题:[讨论]x*log(1.0+x)/((1.0+x)-1.0) 的意义?

有必要开一个新帖专门讨论一下。

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个回复)

沙发

我的理解: x 与1.0+x-1.0中间是会产生累积误差的(这个很小)即这两个数有效位数后面的随机数是不一样的。
我讲不太明白,总之是因为x太小,直接计算后面的这些随机数影响来结果。
在gpu写代码的时候我记得有个例子用到过一些方法来处理这些累加误差。

期待高人解惑~~

板凳


直观上看 x * log(1.0 + x) / ((1.0 + x) - 1.0) 对提高精度没有帮助,不明白为什么要这样做,以我的水平,我还无法理解其中的奥妙,期待高人答疑解惑。

3 楼

我略作解释:

设 .+. 表示 浮点数加法,+ 为数学上定义的普通加法;设 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 楼

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


当 1 .+. x /= 1 时,能直接分析一下 log(1.0 + x) 的误差吗?还是非得用 x * log(1.0 + x) / ((1.0 + x) - 1.0) 这个式子分析误差吗?!

6 楼

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 楼

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

也许这个更直观的解释你的疑问:
      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 楼

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

在计算机计算时,1.0应该是用0.9999999999.....代替的,这样的话,机器算会发现0.99999997与1.0000001一样的接近0.99999999,但笔算,后者更接近1.0。

我来回复

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