Floating point 用MacLaurin展开法实现Fortran正弦函数的小差异
我正在用Fortran创建一个程序,用x表示sin(x),以弧度表示,然后计算项数 这是我的节目: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(*
! 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 一百
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