Open
Description
Rosetta Code has a section on Kahan summation, which "is a method of summing a series of numbers represented in a limited precision in a way that minimises the loss of precision in the result." I have extracted and slightly modified the sumc function from the code there. Here is an example of how it can produce a more accurate result than the intrinsic SUM. I suggest that compensated summation be added to stdlib.
module m
implicit none
private
public :: sumc,wp
integer, parameter :: wp = kind(1.0d0)
contains
function sumc(a) result(asum) !add elements of the array, using limited precision.
real(kind=wp) , intent(in) :: a(:)
real(kind=wp) :: asum
real(kind=wp) :: s,c,y,t ! assistants.
integer :: i ! stepper
integer :: n
n = size(a)
if (n < 1) then
asum = 0.0_wp
return
end if
s = a(1) ! start with the first element.
c = 0.0_wp ! no previous omissions to carry forward.
do i = 2,n ! step through the remainder of the array.
y = a(i) - c ! combine the next value with the compensation.
t = s + y ! augment the sum, temporarily in t.
c = (t - s) - y ! catch what part of y didn't get added to t.
s = t ! place the sum.
end do ! on to the next element.
asum = s ! c will no longer be zero.
end function sumc
end module m
program main
! 04/26/2021 11:39 AM driver for sumc
use m, only: sumc,wp
implicit none
integer, parameter :: n = 100000000
real(kind=wp) :: x(n)
real(kind=wp), parameter :: xadd = 10.0_wp**15
call random_seed()
call random_number(x)
x = x + xadd
print*,([sumc(x),sum(x)] - n*xadd)/n
end program main
output:
0.50331647999999996 2207252.1444556802
If you use wp = kind(1.0)
(single precision) in the code above, you can see the difference even with xadd = 0.0. The output of
module m
implicit none
private
public :: sumc,wp
integer, parameter :: wp = kind(1.0)
contains
function sumc(a) result(asum) !add elements of the array, using limited precision.
! adapted from Rosetta Code https://rosettacode.org/wiki/Kahan_summation
real(kind=wp) , intent(in) :: a(:)
real(kind=wp) :: asum
real(kind=wp) :: s,c,y,t ! assistants.
integer :: i ! stepper
integer :: n
n = size(a)
if (n < 1) then
asum = 0.0_wp
return
end if
s = a(1) ! start with the first element.
c = 0.0_wp ! no previous omissions to carry forward.
do i = 2,n ! step through the remainder of the array.
y = a(i) - c ! combine the next value with the compensation.
t = s + y ! augment the sum, temporarily in t.
c = (t - s) - y ! catch what part of y didn't get added to t.
s = t ! place the sum.
end do ! on to the next element.
asum = s ! c will no longer be zero.
end function sumc
end module m
!
program main
! 04/26/2021 11:39 AM driver for sumc
use m, only: sumc,wp
implicit none
integer, parameter :: n = 100000000
real(kind=wp) :: x(n)
call random_seed()
call random_number(x)
print*,[sumc(x),sum(x)]/n
end program main
is
0.499940693 0.167772159