Fortran和Python中的精度和多项式求值
我将在这里给出一个快速的描述和下面的上下文。我在Python和Fortran中计算二元多项式(两个变量的多项式),得到不同的结果。我的测试用例-4.23e-3的相对误差足够大,不会因为精度差异而变得明显。下面的代码段使用了相当原始的类型和相同的算法,试图使事情尽可能具有可比性。关于差异有什么线索吗?我尝试过改变精度(在Fortran中使用Fortran和Python中的精度和多项式求值,python,fortran,precision,floating-accuracy,polynomial-math,Python,Fortran,Precision,Floating Accuracy,Polynomial Math,我将在这里给出一个快速的描述和下面的上下文。我在Python和Fortran中计算二元多项式(两个变量的多项式),得到不同的结果。我的测试用例-4.23e-3的相对误差足够大,不会因为精度差异而变得明显。下面的代码段使用了相当原始的类型和相同的算法,试图使事情尽可能具有可比性。关于差异有什么线索吗?我尝试过改变精度(在Fortran中使用selected\u real\u kind,在Python中使用numpy.float128),Fortran的编译(特别是优化级别)和算法(Horner的方
selected\u real\u kind
,在Python中使用numpy.float128
),Fortran的编译(特别是优化级别)和算法(Horner的方法,numpy
评估)。关于差异有什么线索吗?两个版本的代码中都有错误吗?我已经看过了,但还没有机会完全用不同的编译器来测试它
Python:
#!/usr/bin/env python
""" polytest.py
Test calculation of a bivariate polynomial.
"""
# Define polynomial coefficients
coeffs = (
(101.34274313967416, 100015.695367145, -2544.5765420363),
(5.9057834791235253,-270.983805184062,1455.0364540468),
(-12357.785933039,1455.0364540468,-756.558385769359),
(736.741204151612,-672.50778314507,499.360390819152)
)
nx = len(coeffs)
ny = len(coeffs[0])
# Values of variables
x0 = 0.0002500000000011937
y0 = -0.0010071334522899211
# Calculate polynomial by looping over powers of x, y
z = 0.
xj = 1.
for j in range(nx):
yk = 1.
for k in range(ny):
curr = coeffs[j][k] * xj * yk
z += curr
yk *= y0
xj *= x0
print(z) # 0.611782174444
Fortran:
! polytest.F90
! Test calculation of a bivariate polynomial.
program main
implicit none
integer, parameter :: dbl = kind(1.d0)
integer, parameter :: nx = 3, ny = 2
real(dbl), parameter :: x0 = 0.0002500000000011937, &
y0 = -0.0010071334522899211
real(dbl), dimension(0:nx,0:ny) :: coeffs
real(dbl) :: z, xj, yk, curr
integer :: j, k
! Define polynomial coefficients
coeffs(0,0) = 101.34274313967416d0
coeffs(0,1) = 100015.695367145d0
coeffs(0,2) = -2544.5765420363d0
coeffs(1,0) = 5.9057834791235253d0
coeffs(1,1) = -270.983805184062d0
coeffs(1,2) = 1455.0364540468d0
coeffs(2,0) = -12357.785933039d0
coeffs(2,1) = 1455.0364540468d0
coeffs(2,2) = -756.558385769359d0
coeffs(3,0) = 736.741204151612d0
coeffs(3,1) = -672.50778314507d0
coeffs(3,2) = 499.360390819152d0
! Calculate polynomial by looping over powers of x, y
z = 0d0
xj = 1d0
do j = 0, nx-1
yk = 1d0
do k = 0, ny-1
curr = coeffs(j,k) * xj * yk
z = z + curr
yk = yk * y0
enddo
xj = xj * x0
enddo
! Print result
WRITE(*,*) z ! 0.61436839888538231
end program
编译时使用:gfortran-O0-o polytest.o polytest.F90
上下文:我正在编写一个现有Fortran库的纯Python实现,主要是作为练习,但也增加了一些灵活性。我正在将我的结果与Fortran进行基准测试,并且已经能够在1e-10范围内获得几乎所有的结果,但这一点超出了我的掌握范围。其他函数也要复杂得多,使得对简单多项式的分歧令人困惑
特定的系数和测试变量来自此库。实际的多项式实际上在(x,y)中有次数(7,6),因此这里没有包括更多的系数。该算法直接取自Fortran,因此,如果算法错误,我应该联系原始开发人员。一般函数也可以计算导数,这就是为什么这个实现可能是次优的一部分——我知道我仍然应该编写一个Horner方法版本,但这并没有改变差异。我只是在计算y的大值的导数时才注意到这些错误,但这个错误确实存在于这个更简单的设置中。Fortran代码中的两件事应该得到纠正,以使结果在Python和Fortran版本之间匹配 1。正如您所做的那样,声明一种特定的双精度类型,如下所示:
integer, parameter :: dbl = kind(0.d0)
然后,应通过添加种类指示符来定义变量,如下所示:
real(dbl) :: z
z = 1.0_dbl
例如,在fortran90.org上讨论了这一点。语法可能不方便,但嘿,规则不是我定的
2.Fortran do循环迭代由nx
和ny
控制。您打算访问coefs
的每个元素,但您的索引缩短了迭代。将nx-1
和ny-1
分别更改为nx
和ny
。更好的方法是,使用Fortran内在的ubound
来确定沿所需维度的范围,例如:
do j = 0, ubound(coeffs, dim=1)
下面显示的更新代码纠正了这些问题,并打印出与python代码生成的结果相匹配的结果
program main
implicit none
integer, parameter :: dbl = kind(1.d0)
integer, parameter :: nx = 3, ny = 2
real(dbl), parameter :: x0 = 0.0002500000000011937_dbl, &
y0 = -0.0010071334522899211_dbl
real(dbl), dimension(0:nx,0:ny) :: coeffs
real(dbl) :: z, xj, yk, curr
integer :: j, k
! Define polynomial coefficients
coeffs(0,0) = 101.34274313967416_dbl
coeffs(0,1) = 100015.695367145_dbl
coeffs(0,2) = -2544.5765420363_dbl
coeffs(1,0) = 5.9057834791235253_dbl
coeffs(1,1) = -270.983805184062_dbl
coeffs(1,2) = 1455.0364540468_dbl
coeffs(2,0) = -12357.785933039_dbl
coeffs(2,1) = 1455.0364540468_dbl
coeffs(2,2) = -756.558385769359_dbl
coeffs(3,0) = 736.741204151612_dbl
coeffs(3,1) = -672.50778314507_dbl
coeffs(3,2) = 499.360390819152_dbl
! Calculate polynomial by looping over powers of x, y
z = 0.0_dbl
xj = 1.0_dbl
do j = 0, ubound(coeffs, dim=1)
yk = 1.0_dbl
do k = 0, ubound(coeffs, dim=2)
print "(a,i0,a,i0,a)", "COEFF(",j,",",k,")="
print *, coeffs(j,k)
curr = coeffs(j,k) * xj * yk
z = z + curr
yk = yk * y0
enddo
xj = xj * x0
enddo
! Print result
WRITE(*,*) z ! Result: 0.611782174443735
end program
如果它们都是相同的算法,您应该能够比较两个实现之间的中间值,以查看差异的进展情况,如果存在“罪魁祸首”,请确定它。我已经很久没有看过Fortran代码了,但看起来您的循环边界不同。例如,在fortran代码中,
j
将从0->2(nx-1)
。在python代码中,j
是从0->3(len(coeffs)-1)
开始的,在Fortran中,您只将参数x0和y0设置为单精度。此外,常数的规格说明是非常老式(f77)风格和更现代的使用种类值的奇怪混合。此外,我认为@mgilson发现了错误。感谢您捕捉到这个错误!我一直在多项式求值作为一个单独的函数(读取系数数组的形状,这是一个输入)和这个版本之间来回切换,以使其尽可能简单(其中nx,ny已经给出)。1.0d0
与1.0_dbl
这两个方面可能解释了我在使用更高功率时所遇到的差异,我将在下一步进行测试。无论如何,那将是一个单独的问题。没问题。1.0d0
和1.0_dbl
给出了相同的精度,因为您已将dbl
定义为相同的类型,但只是为了保持一致……很明显,精度损失发生在参数的定义上;iereal(dbl),参数::x0=0.000250000000011937
有效地创建一个精度值。尝试打印该值,并在使用\u dbl
时与进行比较。这里的教训是,为了保持双精度,必须附加描述符\u dbl
,甚至.d0
。我真的不知道这个效果是如何成为一个有用的特性,也许其他人可以给我们一些启示。默认情况下,数字文字为单精度;正如您所解释的,如果您还需要其他内容,您需要附加适当的类型说明符。要清楚:我把d0
从x0
中去掉了,把事情搞砸了。但是原始代码定义了dbl=selected\u real\u kind(p=15,r=307)
,但仍然用d0
而不是\u dbl
附加它们的文本。这是一种糟糕的做法,但它会有精度损失吗?在完整的多项式计算中,我仍然会遇到分歧(即使在将大部分d0
s更改为\u dbl
s之后),但我还没有一个MWE。