Floating point 用MacLaurin展开法实现Fortran正弦函数的小差异

Floating point 用MacLaurin展开法实现Fortran正弦函数的小差异,floating-point,fortran,Floating Point,Fortran,我正在用Fortran创建一个程序,用x表示sin(x),以弧度表示,然后计算项数 这是我的节目: ! Sine value using MacLaurin series program SineApprox implicit none integer :: numTerms, denom, i double precision :: x, temp, summ ! Read Angle in Radians, and numTerms read(*

我正在用Fortran创建一个程序,用x表示sin(x),以弧度表示,然后计算项数

这是我的节目:

! Sine value using MacLaurin series 

program SineApprox
    implicit none
    integer :: numTerms, denom, i
    double precision :: x, temp, summ

    ! Read Angle in Radians, and numTerms
    read(*,*) x, numTerms

    ! Computing MacLaurin series
    denom = (2*numTerms)-1  !Gives denominator
    temp = x                !Temp calculates each term
    summ = x

    do i = 3, denom, 2
        temp = (((-temp)*(x**2))/(i*(i-1)))
        summ = summ + temp 
    end do
    print *, summ

end program SineApprox
然而,我并没有得到教授要求的输入值:530

我的代码的输出是:

-0.95892427466314001  
但是,所需的输出是:

-0.95892427466313568
                ^^^^

我想不出错误在哪里

最后,我和一位得到确切答案的同学交谈。他告诉我他建立了一个阶乘函数,然后他建议我用if-else语句来求解这些项:if(奇数),然后add。否则如果(偶数),则减去。所以我按照他的建议,最终得到了正确的结果

以下是我的教授的测试案例供参考:

5 30->-0.95892427466313568

4100->-0.75680249530792754

这是我的代码:

! Sine value using MacLaurin series 

recursive function factorial(n) result (f)
    double precision :: f
    double precision, intent(in) :: n
    if (n <= 0) then
        f = 1
    else
        f = n * factorial(n-1)
    end if
end function factorial

program SineApprox
    implicit none
    integer :: numTerms, i, oddeven
    double precision :: x, summ, factorial, odd, even, power

    ! Read Angle in Radians, and numTerms
    read(*,*) x, numTerms

    ! Computing MacLaurin series
    summ = 0
    power = 1
    do i = 1, numTerms, 1
        oddeven = modulo(i,2)

        if(oddeven == 1) then
            odd = (x**power)/factorial(power)
            summ = summ + odd
        else if (oddeven == 0) then
            even = (x**(power))/factorial(power)
            summ = summ - even
        end if
        power = power + 2
    end do

    print *, summ

end program SineApprox
!使用MacLaurin级数的正弦值
递归函数阶乘(n)结果(f)
双精度::f
双精度,意图(in)::n
如果(n)
我想不出错误在哪里

高精度的正弦(5.0)
-0.95892427466313846889…

OP的结果比教授的好

OP的结果在最佳答案的14以内,而Prof的结果是25 ULP

所以OP方面没有问题。为了得到与教授的答案完全匹配的答案,你必须编写一个较差的方法


教授回答不好的一个简单原因是,如果教授的代码仅在
i时循环,我将使用一些任意精度浮点来模拟这两种算法,以说明阶乘解在数值上有多糟糕:我将在这里使用Squeak Smalltalk,但语言并不真正重要,您可以使用Maple或Python,如图所示只要你有一些任意精度的库可用

与sin(5)
的精确结果最接近的是
-0.9589242746631385

我们将看到这两种算法对于不同精度(从单精度24位到长双精度64位)的理想值的逼近程度

然后进行阶乘重写:

e2 := p collect: [:n |
    | x denom temp summ closest fact |
    closest := (5 asArbitraryPrecisionFloatNumBits: 100) sin asFloat.
    x := 5 asArbitraryPrecisionFloatNumBits: n.
    numTerms := 30.
    denom := (2*numTerms)-1.
    temp := x.
    summ := x.
    3 to: denom by: 2 do: [:i |
        fact := ((1 to: i) collect: [:k | k asArbitraryPrecisionFloatNumBits: n]) product.
        temp := ((x ** i)*(-1 ** (i//2)))/fact.
        summ := summ + temp ].
    (summ asFloat - closest) abs].
然后我们可以用任何语言绘制结果(这里是Matlab)

您的算法执行得更好:误差比阶乘变量小一个量级:


阶乘变量中更糟糕的事情是,它依赖于内在幂函数
x**power
。此函数不一定回答精确结果的最近浮点,并且可能会根据底层数学库实现而有所不同。因此,要求得到一个稍微相同的结果,而不仅仅取决于严格的符合IEEE 754标准,但在实现定义的精度上也是一件非常愚蠢的事情——除非所有学生都有完全相同的硬件和软件——但即便如此,这是一个什么样的教训。每个科学家都应该了解浮点吗?

与和 他们的答案和在各种评论中提到的,我 相信我们只关注一次计算就得出了结论, i、 e.
sin(5)
。即使OP的答案更接近真实值, 必须指出,OP算法和阶乘 算法对于计算sin(5)同样糟糕,但最终 阶乘算法是较好的一种

这个答案不会详细介绍浮点运算 算术。一本关于这个主题的优秀专著可以在中找到。我也不会打开蠕虫的罐子

免责声明:我不想声称我在这里写的是100%正确的,我肯定不是这个领域的权威。我只是觉得这个问题非常有趣,并试图尽可能多地从中获得信息。我显然欢迎任何评论

一些有趣的阅读导致了这一点:

一些初步概念
  • 浮点表示法(FPR):在有限浮点中 表示,数字写为±0.d1d2 …dp x be。它有一个精度p和一个偶数 基数b,其中所有数字di都是带0的整数≤ di
  • 绝对误差(AE):如果实数x的AE为 Δx,那么我们知道x的期望真值x̃ 位于x-Δx之间≤ x̃≤ x+Δx。如果x为 最接近真值x̃的FPR,那么我们知道它的AE 是((b/2)b-p-1)x be。例如,如果 =10且p=3,则数字1/3近似为0.333 x 100,绝对误差为0.0005 x 100 表示0.3325 x 100≤ 1/3 ≤ 0.3335 x 一百

这对泰勒-麦克劳林系列意味着什么? 对于标准计算 sin(5)one的泰勒-麦克劳林级数的(b=2和p=53) 很快就发现马已经在第二和第三节跑了 这里,FPR仅精确到
2^-49
,如图所示 下表(表示最接近的二进制64表示形式) 真分数):

精度高达
2^-49
可通过以下方式理解。如果 你看一下术语
5^3/3!
那么这个数字最近的FPR是 分数
(5864062014805333/9007199254740992)*2^5
。如您所愿 看,我们这里缺少一部分,即

5^3/3! - 5864062014805333 / 9007199254740992) * 2^5
    = 1/844424930131968
    ~ 1.1842378929335002E-15 < 2^-49
然而,这一数字过于理想化,因为这会造成更多的损失 精确性是由于

哪种算法更好? 提出的两种算法是(假设所有变量都是变量,除了
i
):

  • 算法1:这是一个略为采用的 提出的算法

  • 算法2:这是一个稍微有些麻烦的问题
    e2 := p collect: [:n |
        | x denom temp summ closest fact |
        closest := (5 asArbitraryPrecisionFloatNumBits: 100) sin asFloat.
        x := 5 asArbitraryPrecisionFloatNumBits: n.
        numTerms := 30.
        denom := (2*numTerms)-1.
        temp := x.
        summ := x.
        3 to: denom by: 2 do: [:i |
            fact := ((1 to: i) collect: [:k | k asArbitraryPrecisionFloatNumBits: n]) product.
            temp := ((x ** i)*(-1 ** (i//2)))/fact.
            summ := summ + temp ].
        (summ asFloat - closest) abs].
    
    p=24:64;
    e1=[1.8854927952283163e-8 4.8657250339978475e-8 2.5848555629259806e-8 6.355841153382613e-8 3.953766758435506e-9 2.071757310151412e-8 2.0911216092045493e-9 6.941377472813315e-10 4.700154709880167e-10 9.269683909352011e-10 6.256184459374481e-11 3.1578795134379334e-10 2.4749646776456302e-11 3.202560439063973e-11 1.526812010155254e-11 8.378742144543594e-12 3.444688978504473e-12 6.105005390111273e-12 9.435785486289205e-13 7.617240171953199e-13 2.275957200481571e-14 1.6486811915683575e-13 2.275957200481571e-14 5.1181281435219717e-14 1.27675647831893e-14 1.2101430968414206e-14 1.2212453270876722e-15 2.7755575615628914e-15 5.551115123125783e-16 1.5543122344752192e-15 1.1102230246251565e-16 1.1102230246251565e-16 0.0 1.1102230246251565e-16 0.0 0.0 0.0 0.0 0.0 0.0 0.0];
    e2=[9.725292443585332e-7 4.281799078631465e-7 2.721746682476933e-7 1.823107481646602e-7 9.336073392152144e-8 5.1925587718493205e-8 1.6992282803052206e-8 6.756442849642497e-9 5.1179199767048544e-9 3.0311525511805826e-9 1.2180066955025382e-9 6.155346232716852e-10 2.8668412088705963e-10 6.983780220792823e-11 6.476741365446514e-11 3.8914982347648674e-11 1.7473689162272876e-11 1.2084888645347291e-11 4.513389662008649e-12 1.7393864126802328e-12 1.273314786942592e-12 5.172529071728604e-13 2.5013324744804777e-13 1.6198153929281034e-13 6.894484982922222e-14 2.8754776337791554e-14 1.6542323066914832e-14 8.770761894538737e-15 4.773959005888173e-15 2.7755575615628914e-15 7.771561172376096e-16 3.3306690738754696e-16 3.3306690738754696e-16 1.1102230246251565e-16 1.1102230246251565e-16 0.0 0.0 0.0 0.0 0.0 0.0];
    figure; semilogy(p,e1,p,e2,'-.'); legend('your algorithm','factorial algorithm'); xlabel('float precision'); ylabel('error')
    
    j term        FPR                     e AE
                  0.12345678901234567    
    1  5^1/1!     5.00000000000000000E+00 3 2^-51 4.4E-16
    3 -5^3/3!    -2.08333333333333321E+01 5 2^-49 1.8E-15
    5  5^5/5!     2.60416666666666679E+01 5 2^-49 1.8E-15
    7 -5^7/7!    -1.55009920634920633E+01 4 2^-50 8.9E-16
    
    j  sum        FPR                      e AE
                  0.12345678901234567    
    1     5       5.00000000000000000E+00  3 2^-51 4.4E-16
    3   -95/6    -1.58333333333333339E+01  4 2^-50 8.9E-16
    5   245/24    1.02083333333333339E+01  4 2^-50 8.9E-16
    7 -5335/1008 -5.29265873015873023E+00  3 2^-51 4.4E-16
    
    5^3/3! - 5864062014805333 / 9007199254740992) * 2^5
        = 1/844424930131968
        ~ 1.1842378929335002E-15 < 2^-49
    
    2^-51 + 2^-49 + 2^-49 + 2^50 + ... = 5.45594...E-15
    
    fact = 1.0_dp; pow  = x; sum = x; xx = x*x
    do i=2,30
       j=2*i-1
       fact = fact*j*(j-1)
       pow  = -pow * xx
       term = pow/fact
       sum = sum + term
    end do
    
    term = x; sum = x; xx = x*x
    do i=2,30
      j=2*i-1
       term = (-term*xx)/(j*(j-1))
       sum = sum + term
    end do
    
     j  term(alg-1)              term(alg-2)
        0.12345678901234567      0.12345678901234567
     1  5.00000000000000000E+00  5.00000000000000000E+00
     3 -2.08333333333333321E+01 -2.08333333333333321E+01
     5  2.60416666666666679E+01  2.60416666666666643E+01
     7 -1.55009920634920633E+01 -1.55009920634920633E+01
     9  5.38228891093474449E+00  5.38228891093474360E+00
    11 -1.22324747975789649E+00 -1.22324747975789627E+00
    13  1.96033249961201361E-01  1.96033249961201306E-01
    15 -2.33372916620477808E-02 -2.33372916620477738E-02
    17  2.14497166011468543E-03  2.14497166011468499E-03
    
    AE(term(j-2)) * xx / (j*(j-1))
    
    f = (a/n) * b^(e-p)
    q = (q + mod(a,n) * b^(e-p))/n
    
    f = 1.0_dp; q = 0.0_dp
    do i = 2,10
       ! retrieving the integer from 1/(i-1)!
       a = int(scale(fraction(f),digits(f)),kind=INT64)
       ! computing the error on f while keeping track of the
       ! previous error
       q = (q + scale(real(mod(a,i),kind=dp),exponent(f)-digits(f)))/i
       ! computing f/i resembling 1/i!
       f =      scale(real(a/i     ,kind=dp),exponent(f)-digits(f))
       ! rebalancing the terms
       t = f; f = f + q; q = q - (f - t)
    end do
    
    pow = x; sum = x; xx = x*x; err = 0.0_dp;
    f = 1.0_dp; q = 0.0_dp
    do i=2,30
       j=2*i-1
       ! The factorial part
       a = int(scale(fraction(f),digits(f)),kind=INT64)  
       q = (q + scale(real(mod(a,j*(j-1)),kind=dp),exponent(f)-digits(f)))/(j*(j-1))
       f =      scale(real(a/(j*(j-1))   ,kind=dp),exponent(f)-digits(f))
       t = f; f = f + q; q = q - (f - t)
    
       pow = -pow*xx  ! computes x^j
       ! Kahan summation
       t = pow*f; err = err + (t - ((sum + t) - sum)); sum = sum + t
       t = pow*q; err = err + (t - ((sum + t) - sum)); sum = sum + t
       t = sum; sum = sum + err; err = err - (sum - t)
    end do