使用MacLaurin扩展的Fortran Sine功能差异很小

时间:2018-05-17 12:39:27

标签: floating-point fortran

我在Fortran中创建一个程序,以弧度为单位输入x(sin),然后计算要计算的项数。

这是我的计划:

! 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

但是,我没有得到我教授输入所需的相同值:5 30

我的代码输出是:

-0.95892427466314001  

但是,所需的输出是:

-0.95892427466313568
                ^^^^

我无法弄清楚错误的位置。

3 个答案:

答案 0 :(得分:4)

  

我无法弄清楚错误的位置。

高精度sine(5.0)-0.95892427466313846889...

OP的结果优于教授的结果。

OP的结果在最佳答案的14 ULP范围内,而教授的结果是25 ULP。

因此OP没有问题。要获得与教授答案的完全匹配,您必须编写一个较差的方法。

教授劣质答案的一个简单可能原因是教授的代码仅在i<=30(15个术语,而不是30个术语)时循环 - 这只能解释差异。尝试使用较少迭代的代码,看看哪个迭代计数最接近教授的答案。

//  sine(5.0)             Delta from correct  Source
//  
//-0.95892427466313846889...    0 000000      calc
//-0.95892427466313823        -23 889...      chux (via some C code)
//-0.95892427466314001       +154 111...      OP
//-0.95892427466313568       -278 889...      Prof
// 0.00000000000000089      +/-89 ...         ulp(5.0)
// 0.00000000000000011      +/-11 ...         ulp(-0.9589)
//   12345678901234567(digit count)

注意:
接近x == 5.0,大约在第17个学期之后,temp项很小,不会对结果产生显着影响。所以肯定会使用足够的术语。

对于常见的FP表示,最后5位的单位是~89e-17。 ULP为-0.9589,如果是~11e-17。

答案 1 :(得分:3)

我将使用一些ArbitraryPrecisionFloat模拟这两个算法来说明因子解决方案在数字上有多糟糕:我将在这里使用Squeak Smalltalk,但语言并不重要,你可以用Maple或Python来做,只要你有一些任意的精确库...

sin(5)的确切结果最接近的binary64 floating point number-0.9589242746631385

我们将看到两种算法在不同精度下接近理想值的程度(从单精度24位到长双精度64位)。

p := 24 to: 64.
e1 := p collect: [:n |
    | x denom temp summ closest |
    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 |
        temp := (((0-temp)*(x**2))/(i*(i-1))).
        summ := summ + temp ].
    (summ asFloat - closest) abs].

然后是因子重写:

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)

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')

您的算法表现更好:一个误差幅度小于因子变量:

enter image description here

因子变体中最糟糕的是它取决于内在幂函数x**power。此函数不一定回答最接近精确结果的浮点,并且可能根据基础数学库实现而变化。因此,要求一个相同的结果,不仅取决于严格的IEEE 754合规性,而且还取决于实现定义的准确性是一个非常愚蠢的事情 - 除非所有学生都有完全相同的硬件和软件 - 但即便如此,它的教训是什么。每个科学家应该了解浮点数?

答案 2 :(得分:1)

我最后和一位得到确切答案的同学交谈。他告诉我他构建了一个阶乘函数,然后他建议我使用if else语句解决这些术语:if(odd),然后添加。否则if(偶数),然后减去。所以我遵循了他的建议并最终获得了正确的输出。

作为参考,这些是我教授的测试用例:

5 30 - &gt; -0.95892427466313568

4 100 - &gt; -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