Fortran sum函数返回与显式循环不同的答案

Fortran sum函数返回与显式循环不同的答案,fortran,Fortran,我正在将f77代码转换为f90代码,部分代码需要在3d矩阵的元素上求和。在f77中,这是通过使用3个循环(在外部、中间和内部索引上)实现的。我决定使用f90内在和(3次)来实现这一点,令我惊讶的是答案不同。我正在使用ifort编译器,调试、检查边界、无优化都已启用 这是f77样式代码 r1 = 0.0 do k=1,nz do j=1,ny do i=1,nx r1 = r1 + foo(i,j,k) end do end do end do 这是f90代码

我正在将f77代码转换为f90代码,部分代码需要在3d矩阵的元素上求和。在f77中,这是通过使用3个循环(在外部、中间和内部索引上)实现的。我决定使用f90内在和(3次)来实现这一点,令我惊讶的是答案不同。我正在使用ifort编译器,调试、检查边界、无优化都已启用

这是f77样式代码

r1 = 0.0
do k=1,nz
  do j=1,ny
    do i=1,nx
      r1 = r1 + foo(i,j,k)
    end do
  end do
end do
这是f90代码

r = SUM(SUM(SUM(foo, DIM=3), DIM=2), DIM=1)
我尝试过各种变化,比如用f77代码替换循环顺序,或者在使用SUM时创建临时2D矩阵和1D数组以“减少”维度,但是显式f77样式的循环总是给出与f90+SUM函数不同的答案

如果有任何有助于理解差异的建议,我将不胜感激

顺便说一下,这是使用一个串行处理器

编辑下午12:13以显示完整的示例

! ifort -check bounds -extend-source 132 -g -traceback -debug inline-debug-info -mkl -o verify  verify.f90
! ./verify

program verify

implicit none

integer :: nx,ny,nz

parameter(nx=131,ny=131,nz=131)

integer :: i,j,k
real :: foo(nx,ny,nz)
real :: r0,r1,r2
real :: s0,s1,s2
real :: r2Dfooxy(nx,ny),r1Dfoox(nx)

call random_seed
call random_number(foo)

r0 = 0.0
do k=1,nz
  do j=1,ny
    do i=1,nx
      r0 = r0 + foo(i,j,k)
    end do
  end do
end do

r1 = 0.0
do i=1,nx
  do j=1,ny
    do k=1,nz
      r1 = r1 + foo(i,j,k)
    end do
  end do
end do

r2 = 0.0
do j=1,ny
  do i=1,nx
    do k=1,nz
      r2 = r2 + foo(i,j,k)
    end do
  end do
end do

!*************************

s0 = 0.0
s0 = SUM(SUM(SUM(foo, DIM=3), DIM=2), DIM=1)

s1 = 0.0
r2Dfooxy = SUM(foo,   DIM = 3)
r1Dfoox  = SUM(r2Dfooxy, DIM = 2)
s1 = SUM(r1Dfoox)

s2 = SUM(foo)

!*************************

print *,'nx,ny,nz = ',nx,ny,nz
print *,'size(foo) = ',size(foo)

write(*,'(A,4(ES15.8))') 'r0,r1,r2          = ',r0,r1,r2
write(*,'(A,3(ES15.8))') 'r0-r1,r0-r2,r1-r2 = ',r0-r1,r0-r2,r1-r2

write(*,'(A,4(ES15.8))') 's0,s1,s2          = ',s0,s1,s2
write(*,'(A,3(ES15.8))') 's0-s1,s0-s2,s1-s2 = ',s0-s1,s0-s2,s1-s2

write(*,'(A,3(ES15.8))') 'r0-s1,r1-s1,r2-s1    = ',r0-s1,r1-s1,r2-s1

stop
end

!**********************************************


首先,欢迎来到StackOverflow。我们之所以希望有这样的结果,是因为我们查看了您的代码,只能猜测可能出现的情况,这对社区没有太大帮助

我希望下面的建议能帮助你弄清楚到底发生了什么

使用size()函数并打印Fortran认为是尺寸的大小,以及打印nx、ny和nz。据我们所知,数组被声明为大于nx、ny和nz,并且这些变量是根据数据集设置的。Fortran不一定将数组初始化为零,这取决于它是静态数组还是可分配数组

也可以尝试在sum函数中指定数组范围:

r = Sum(foo(1:nx,1:ny,1:nz))
如果这样做的话,至少我们知道sum函数在循环所循环的foo的完全相同的片上工作


如果是这种情况,即使代码没有“错误”,您也会得到错误的答案。这就是为什么给予我现在可以看到差异的信息特别重要的原因。这些是典型的舍入误差,因为将小的数字加到一个大的总数中。处理器可以使用它想要的任何求和顺序。没有“正确”的订单。您可以真正地说,原来的循环给出了“正确”的答案,而其他循环则没有

您所能做的就是使用双精度。在极端情况下,也有像卡汉总结这样的技巧,但很少有人需要

把一个小数字加到一个大数字上是不精确的,尤其是在单精度上。结果中仍有四个有效数字


通常不使用在某些特殊情况下使用的
DIM=
参数

如果要对
foo
的所有元素求和,请使用

s0 = SUM(foo)
这就够了

什么


这样做的目的是,它将创建一个临时2D数组,其中每个元素是z维中相应行的和,然后是一个1D数组,每个元素是2D数组最后一维的和,最后是该1D数组的和。如果做得好,最终的结果将是相同的,但它会消耗大量的CPU周期。

内部函数
sum
返回与处理器相关的数组参数元素和的近似值。这与按顺序添加所有元素不同

很容易找到数组
x
where

summation = x(1) + x(2) + x(3)
(严格从左到右执行)不是将值视为“数学实数”而不是浮点数的和的最佳近似值


作为使用ifort查看近似性质的具体示例,我们可以查看以下程序。我们需要在这里启用优化以查看效果;即使禁用了优化(使用
-O0
-debug
),求和顺序的重要性也是显而易见的

如果按严格的顺序累加,我们得到
1.
,看到任何小于
ε(0.)的量值都不会影响总和


您可以试验数组的大小及其元素的顺序、小数字的缩放以及ifort浮点编译选项(例如
-fp model strict
-mieee fp
-pc32
)。您还可以尝试使用双精度而不是默认的实数来查找上述示例。

您会遇到什么样的问题,并创建一个新的实例。为什么使用嵌套的
sum
而不是一条
sum
命令来澄清albert的注释r=sum(foo)就足够了。但至少你需要告诉我们不同的答案是什么,以及foo的确切维度-你确定你没有对未初始化的数据求和吗?这基本上是一个三维积分。因此,通过对各种元素的简单求和,我就得到了答案(没有深入到复杂的梯形积分等),看到不同的答案并不令人放心。我已经编辑了原始消息,以显示一个完整的最小可验证示例。您将看到循环和调用sum的各种化身。顶行显示了我是如何编译的。有些给出相同的结果,有些给出不同的结果。看起来有点像一个数值问题(有效位数,众所周知,(A+b)+c在计算机上不等于A+(b+c))。如果使用双精度而不是实数,会发生什么情况?同意@albert的说法,您将以单精度累计超过200万个数字,其中只有大约6-7位数的精度。感谢Dan和Vladimir的建议。我编辑了我的原始帖子,向您展示了给出不同结果的代码示例。在发布的代码中,我使用随机生成器初始化foo,在我正在编写的实际代码中,它将被初始化为流体中的正弦波(最终会变得湍流)。使用切片表示法可能会导致编译器生成一个临时值。我
s0 = SUM(SUM(SUM(foo, DIM=3), DIM=2), DIM=1)
summation = x(1) + x(2) + x(3)
  implicit none

  integer i
  real x(50)
  real total

  x = [1.,(EPSILON(0.)/2, i=1, SIZE(x)-1)]
  total = 0
  do i=1, SIZE(x)
     total = total+x(i)
     print '(4F17.14)', total, SUM(x(:i)), SUM(DBLE(x(:i))), REAL(SUM(DBLE(x(:i))))
  end do
end program