sum(array,mask=...) 问题
Issue with sum(array,mask=...)
我的程序有问题。使用掩码调用的内在函数 sum 会导致可疑结果:当我执行平均时,我获得了数组边界之外的值。我怀疑这与舍入误差有关。我正在处理大型数组,舍入误差导致较大偏差(与 40,000 个元素大小的预期值相比,差异约为 40%)。
下面是重现它的最小示例,以及相关的输出。
program main
implicit none
integer :: nelem
real, allocatable, dimension(:) :: real_array
logical, allocatable, dimension(:) :: log_array
! init
nelem=40000
allocate(real_array(nelem))
allocate(log_array(nelem))
real_array=0.
log_array=.true.
! Fill arrays
real_array=1./2000.
log_array = real_array.le.(0.5)
! Test
print *, ' test : ', &
count(log_array)+sum(real_array, mask=log_array), &
sum(1.+real_array,mask=log_array)
end program main
输出为:
test : 40019.9961 40011.9961
理论结果为40020。
运行 GNU Fortran (GCC) 4.9.0
您正在使用单精度数组。计算机基本上将实数存储为 2 的幂的扩展。这适用于 2、4 和 8 等数字,它们可以很容易地写成具有整数系数的 2 的整数幂,但对于某些实数则不太好(比如 1.d0/2000.d0)。
单精度
real, allocatable, dimension(:)
分配了 4 个字节。这将为您提供 8 位数的精度。这就是你观察到的。第二个总和
sum(1.+real_array,mask=log_array)
只有四位数的精度,但是,好吧,你要加上 1.0 和小 1000 倍的东西。这进一步缩小到有效的四位数(这是您在第二种情况下观察到的)。
您可以通过声明所有双精度(也就是具有 16 位精度的 8 字节变量)来改进这一点,并且您必须编写 1.d0 而不是 1.0,或者添加一个编译器标志,如 -fdefault-real -8 -fdefault-double-8.
如果你的舍入误差在你的操作过程中积累了那么多,我建议你重新考虑做事的顺序。添加范围大不相同的变量会显着降低精度。
如果这不是一个选项并且双精度不够,我可以向您指出四倍精度
quad precision in gfortran
但我个人没有使用过,因为这通常通过软件解决
层预计会有巨大的性能损失。
编辑:尝试双精度:
变化:
double precision, allocatable, dimension(:) :: real_array
保留其余部分并使用提到的编译器选项进行编译。我获得
test : 40020.000000000000 40019.999999987354
第一个结果很好,第二个是 12 位精度(最初是 16 位加上 1.0 和 1.0/2000.0 相加后丢失的四位),这也是您所期望的。
我的程序有问题。使用掩码调用的内在函数 sum 会导致可疑结果:当我执行平均时,我获得了数组边界之外的值。我怀疑这与舍入误差有关。我正在处理大型数组,舍入误差导致较大偏差(与 40,000 个元素大小的预期值相比,差异约为 40%)。
下面是重现它的最小示例,以及相关的输出。
program main
implicit none
integer :: nelem
real, allocatable, dimension(:) :: real_array
logical, allocatable, dimension(:) :: log_array
! init
nelem=40000
allocate(real_array(nelem))
allocate(log_array(nelem))
real_array=0.
log_array=.true.
! Fill arrays
real_array=1./2000.
log_array = real_array.le.(0.5)
! Test
print *, ' test : ', &
count(log_array)+sum(real_array, mask=log_array), &
sum(1.+real_array,mask=log_array)
end program main
输出为:
test : 40019.9961 40011.9961
理论结果为40020。
运行 GNU Fortran (GCC) 4.9.0
您正在使用单精度数组。计算机基本上将实数存储为 2 的幂的扩展。这适用于 2、4 和 8 等数字,它们可以很容易地写成具有整数系数的 2 的整数幂,但对于某些实数则不太好(比如 1.d0/2000.d0)。
单精度
real, allocatable, dimension(:)
分配了 4 个字节。这将为您提供 8 位数的精度。这就是你观察到的。第二个总和
sum(1.+real_array,mask=log_array)
只有四位数的精度,但是,好吧,你要加上 1.0 和小 1000 倍的东西。这进一步缩小到有效的四位数(这是您在第二种情况下观察到的)。
您可以通过声明所有双精度(也就是具有 16 位精度的 8 字节变量)来改进这一点,并且您必须编写 1.d0 而不是 1.0,或者添加一个编译器标志,如 -fdefault-real -8 -fdefault-double-8.
如果你的舍入误差在你的操作过程中积累了那么多,我建议你重新考虑做事的顺序。添加范围大不相同的变量会显着降低精度。
如果这不是一个选项并且双精度不够,我可以向您指出四倍精度
quad precision in gfortran
但我个人没有使用过,因为这通常通过软件解决 层预计会有巨大的性能损失。
编辑:尝试双精度:
变化:
double precision, allocatable, dimension(:) :: real_array
保留其余部分并使用提到的编译器选项进行编译。我获得
test : 40020.000000000000 40019.999999987354
第一个结果很好,第二个是 12 位精度(最初是 16 位加上 1.0 和 1.0/2000.0 相加后丢失的四位),这也是您所期望的。