There are in this post some useful codes converting string to double fast and correctly. I’d like to pack everything in one module. Here I provide the module, testing program for
- direct method
- strtod (from C, namely c_st_to_r8)
- f_strtod (namely f_st_to_r8)
- str2real (but this one does not work. I put it here for a clear check. No. The new version works excellently. Read UPDATE 1)
Herein we can check the elemental attribution, which allows calling routine with array.
Result:
1. with Ifort (2021.6.0 20220226):
N = 1000000 , i0 = 353352
Write: time consumed = 0.3052 seconds
BLOCK 1: formatted read toward string to double
Read: time consumed = 0.3692 seconds
A = Max |rval(:)-rref(:)| = 5.551E-17, B = epsilon(1.0d0) = 2.220E-16, B-A = 1.665E-16
BLOCK 2: C st_to_r8
Read: time consumed = 0.1046 seconds
Read: time consumed = 0.1037 seconds (array)
A = Max |rval(:)-rref(:)| = 5.551E-17, B = epsilon(1.0d0) = 2.220E-16, B-A = 1.665E-16
BLOCK 3: F st_to_r8
Read: time consumed = 0.0868 seconds
Read: time consumed = 0.0861 seconds (array)
A = Max |rval(:)-rref(:)| = 2.220E-16, B = epsilon(1.0d0) = 2.220E-16, B-A = 0.000E+00
BLOCK 4: F str2real
Read: time consumed = 0.0395 seconds
Read: time consumed = 0.0390 seconds (array)
A = Max |rval(:)-rref(:)| = 4.424E+06, B = epsilon(1.0d0) = 2.220E-16, B-A = -4.424E+06
*** WARNING: A > B, (A-B)/epsilon = 1.992E+22
2. with Gfortran (8.3.0)
N = 1000000 , i0 = 727798
Write: time consumed = 0.8102 seconds
BLOCK 1: formatted read toward string to double
Read: time consumed = 0.6475 seconds
A = Max |rval(:)-rref(:)| = 5.551E-17, B = epsilon(1.0d0) = 2.220E-16, B-A = 1.665E-16
BLOCK 2: C st_to_r8
Read: time consumed = 0.1046 seconds
Read: time consumed = 0.1038 seconds (array)
A = Max |rval(:)-rref(:)| = 5.551E-17, B = epsilon(1.0d0) = 2.220E-16, B-A = 1.665E-16
BLOCK 3: F st_to_r8
Read: time consumed = 0.0790 seconds
Read: time consumed = 0.0797 seconds (array)
A = Max |rval(:)-rref(:)| = 1.887E-15, B = epsilon(1.0d0) = 2.220E-16, B-A = -1.665E-15
*** WARNING: A > B, (A-B)/epsilon = 7.500E+00
BLOCK 4: F str2real
Read: time consumed = 0.0416 seconds
Read: time consumed = 0.0412 seconds (array)
A = Max |rval(:)-rref(:)| = 9.649E+05, B = epsilon(1.0d0) = 2.220E-16, B-A = -9.649E+05
*** WARNING: A > B, (A-B)/epsilon = 4.345E+21
Source code:
!------------------------------------------------------------------------------
!
module mod_st_to_dp
!
use, intrinsic :: iso_c_binding, only: c_double, c_char, c_ptr, &
c_null_ptr, c_long
!
use, intrinsic :: iso_fortran_env, only: dp => real64, i8 => int64
!
implicit none
!
private
!
public :: f_st_to_r8
public :: c_st_to_r8
public :: str2real
!
!
interface
pure &
function c_strtod ( str, endptr ) result(d) bind(C, name="strtod")
!
!! <stdlib.h> :: double strtod(const char *str, char **endptr)
!
import :: c_char, c_ptr, c_double
!! Argument list
character(kind=c_char,len=1), intent(in) :: str(*)
!! type(c_ptr), intent(inout) :: endptr !<- since we need pure attribution.
type(c_ptr), intent(in) :: endptr
!! function result
real(c_double) :: d
end function
end interface
!
!
! integer, parameter :: dp = selected_real_kind( p=12 )
! FP constants
real(dp), parameter :: ten = 10.0_dp
! Integer constants
integer, parameter :: ascii_negative = 45
integer, parameter :: ascii_period = 46
integer, parameter :: ascii_0 = 48
integer, parameter :: ascii_9 = 57
integer(i8), parameter :: ten_int = 10_i8
!
type(c_ptr),parameter :: endptr = c_null_ptr
!
!------------------------------------------------------------------------------
contains
!------------------------------------------------------------------------------
elemental &
subroutine c_st_to_r8 ( str, r )
character(len=*), intent(in) :: str
real(dp), intent(out) :: r
!
r = c_strtod ( str, endptr )
return
end subroutine
!------------------------------------------------------------------------------
!
elemental &
subroutine f_st_to_r8 ( str, r )
character(len=*), intent(in) :: str
real(dp), intent(out) :: r
! local variables
integer(i8) :: n, n_exp
integer :: lens, pos_exp, expnt
!
r = 0.0_dp
n_exp = 0
lens = len_trim( str )
pos_exp = index( str, "E", back=.true. ) ! <-- find the capital 'E'
if ( pos_exp == 0 ) then
pos_exp = index( str, "e", back=.true. )
endif
if ( pos_exp > 0 ) then
call str2dec( str(pos_exp+1:lens), n_exp )
else
pos_exp = lens + 1
endif
call str2dec( str(1:pos_exp-1), n, expnt )
r = real( n, kind=dp ) * ( ten**(n_exp + expnt) )
return
end subroutine
!
elemental &
subroutine str2dec( s, n, expnt )
character(len=*), intent(in) :: s
integer(i8), intent(out) :: n
integer, intent(out), optional :: expnt
integer :: ipos, ic, pos_period, iexponent
!
n = 0_i8
pos_period = 0
iexponent = -1
if ( present(expnt) ) iexponent = 0
do ipos = len(s), 1, -1
ic = ichar( s(ipos:ipos) )
if ( present(expnt) ) then
if ( ic == ascii_period ) then
pos_period = ipos
cycle
endif
endif
if ( ic == ascii_negative ) then
n = -n
exit
endif
if ( (ic < ascii_0) .or. (ic > ascii_9) ) exit
iexponent = iexponent + 1
n = n + (ten_int**iexponent)*(ic - 48)
enddo
if ( present(expnt) ) expnt = -len(s) + pos_period - 1
return
end subroutine
!------------------------------------------------------------------------------
!
elemental &
function str2real ( s ) result ( r )
!
character(*), target, intent(in) :: s
real(dp) :: r
!
integer, parameter :: N = 32
real(dp), parameter :: expo(N) = &
[1e8, 1e7, 1e6, 1e5, 1e4, 1e3, 1e2, 1e1, 1e0, &
1e0, 1e-1, 1e-2, 1e-3, 1e-4, 1e-5, 1e-6, 1e-7, 1e-8, &
1e-9, 1e-10, 1e-11, 1e-12, 1e-13, 1e-14, 1e-15, 1e-16, &
1e-17, 1e-18, 0., 0., 0., 0.]
character(N) :: equ_s
integer*1 :: equ_i(N)
integer*1 :: mask(N)
equivalence(equ_i, equ_s)
equ_s = s
equ_i = equ_i - 48
where (0 <= equ_i .and. equ_i <= 9)
mask = 1
elsewhere
mask = 0
end where
equ_i = equ_i * mask
r = sum(equ_i * expo) * 10**(equ_i(29)*10+equ_i(30))
!
return
end function str2real
!------------------------------------------------------------------------------
end module
!------------------------------------------------------------------------------
!======================================================================
!
program main
!
use, intrinsic :: iso_fortran_env, only: dp => real64, i8 => int64
use mod_st_to_dp
!
implicit none
!
integer,parameter :: n = 1000000 !! number of values
!
character(len=30),dimension(:),allocatable :: strs
real(dp),dimension(:),allocatable :: rval, rref
!
integer :: i, i0
integer :: ierr
real(dp) :: r
integer(i8) :: start, finish, count_rate
real(dp),parameter :: eps = epsilon(1.0_dp)
!
!
! create a list of values to parse
!
allocate( strs(n), rval(n), rref(n) )
!
! Initializing RNG: with a dummy i
r = util_unirand (i)
i0 = int(n*r) + 1
write(*,*) 'N =', n, ', i0 =', i0
do i = 1, n
rref(i) = util_unirand()
enddo
call system_clock(start, count_rate)
do i = 1, n
write(strs(i), '(E30.16)') rref(i)
enddo
call system_clock(finish)
write(*,'(A30,1X,F7.4,1X,A)') 'Write: time consumed =', &
(finish-start)/real(count_rate,dp), ' seconds'
blk_io: block
write(*,11) "BLOCK 1: formatted read toward string to double"
!
call system_clock(start, count_rate)
do i = 1, n
read(strs(i),fmt=*,iostat=ierr) rval(i)
enddo
call system_clock(finish)
write(*,'(A30,1X,F7.4,1X,A)') 'Read: time consumed =', &
(finish-start)/real(count_rate,dp), ' seconds'
!
!! if ( abs(rval(i0)-rref(i0)) > eps ) then
!! print *, "Warning: mismatch during formatted read."
!! write(*,10) i0, trim(strs(i0)), rval(i0)
! exit blk_io
!! endif
!
call show_err ( maxval(abs(rval-rref)), eps )
!
end block blk_io
blk_c: block
write(*,11) "BLOCK 2: C st_to_r8"
!
call system_clock(start, count_rate)
do i = 1, n
call c_st_to_r8 ( strs(i), rval(i) )
enddo
call system_clock(finish)
write(*,'(A30,1X,F7.4,1X,A)') 'Read: time consumed =', &
(finish-start)/real(count_rate,dp), ' seconds'
!
!! if ( abs(rval(i0)-rref(i0)) > eps ) then
!! print *, "Warning: mismatch during st_to_r8."
!! write(*,10) i0, trim(strs(i0)), rval(i0)
! exit blk_c
!! endif
!
call system_clock(start, count_rate)
call c_st_to_r8 ( strs, rval )
call system_clock(finish)
write(*,'(A30,1X,F7.4,1X,A)') 'Read: time consumed =', &
(finish-start)/real(count_rate,dp), ' seconds (array)'
!
call show_err ( maxval(abs(rval-rref)), eps )
!
end block blk_c
blk_f: block
write(*,11) "BLOCK 3: F st_to_r8"
!
call system_clock (start, count_rate)
do i = 1, n
call f_st_to_r8 ( strs(i), rval(i) )
enddo
call system_clock(finish)
write(*,'(A30,1X,F7.4,1X,A)') 'Read: time consumed =', &
(finish-start)/real(count_rate,dp), ' seconds'
!
!! if ( abs(rval(i0)-rref(i0)) > eps ) then
!! print *, "Warning: mismatch during st_to_r8."
!! write(*,10) i0, trim(strs(i0)), rval(i0)
! exit blk_f
!! endif
!
call system_clock(start, count_rate)
call f_st_to_r8 ( strs, rval )
call system_clock(finish)
write(*,'(A30,1X,F7.4,1X,A)') 'Read: time consumed =', &
(finish-start)/real(count_rate,dp), ' seconds (array)'
!
call show_err ( maxval(abs(rval-rref)), eps )
!
end block blk_f
blk_4: block
write(*,11) "BLOCK 4: F str2real "
!
call system_clock (start, count_rate)
do i = 1, n
rval(i) = str2real ( strs(i) )
enddo
call system_clock(finish)
write(*,'(A30,1X,F7.4,1X,A)') 'Read: time consumed =', &
(finish-start)/real(count_rate,dp), ' seconds'
!
call system_clock(start, count_rate)
rval = str2real ( strs )
call system_clock(finish)
write(*,'(A30,1X,F7.4,1X,A)') 'Read: time consumed =', &
(finish-start)/real(count_rate,dp), ' seconds (array)'
!
call show_err ( maxval(abs(rval-rref)), eps )
!
end block blk_4
!
10 format ( 'i =', i9, ', string(i) =', a, ', zval(i) =', e25.17 )
11 format ( /, 80('-'), /, a )
!
deallocate ( strs, rval, rref )
!
STOP
!
contains
!======================================================================
!
! This is the ran0 in Numerical Rrecipes in Fortran.
! To initialize,
! call system_clock( idummy )
! Do not alter idummy between successive deviates.
!
function util_unirand ( idum ) result( ran0 )
!
implicit none
integer,parameter :: rk = kind(1.0D0), ik = 4
!
! ARGUMENTS:
!
real(rk) :: ran0
integer(ik),intent(in),optional :: idum
!
! local constants and variable
!
integer(ik) :: k
integer(ik),save :: idummy
integer(ik),parameter :: &
ia=16807, im=2147483647, iq=127773, ir=2836, mask=123459876
real(rk),parameter :: am = 1.0_rk / im
!
!
if ( present(idum) ) call system_clock( idummy )
!
idummy = ieor( idummy, mask )
k = idummy / iq
idummy = ia*( idummy - k*iq ) - ir*k
if ( idummy .lt. 0 ) idummy = idummy + im
ran0 = am*idummy
idummy = ieor( idummy, mask )
return
end function
!
!=====
subroutine show_err ( er, ep )
real(dp),intent(in) :: er, ep
real(dp) :: tmp
!
tmp = ep - er
write(*,10) er, ep, tmp
if ( tmp .lt. 0.0_dp ) write(*,11) -tmp/ep
return
10 format( 'A = Max |rval(:)-rref(:)| =', 1pe10.3,',', 3x, &
'B = epsilon(1.0d0) =', 1pe10.3,',', 3x, &
'B-A =', 1pe11.3 )
11 format( '*** WARNING: A > B, (A-B)/epsilon =', 1pe11.3 )
end subroutine
!
!======================================================================
end program
!======================================================================
Conclusion: (obsolete)
- direct method: slow
- strtod (namely c_st_to_r8): fast
- f_strtod (namely f_st_to_r8): fastest. But the accuracy may be lost, and this issue depends on compiler. For instance, the error is about <= 7.5*epsilon with Gfortran, while with Ifort the error is <= epsilon.
- str2real: does not work.
==============================================================
UPDATE 1
Based on Carltoffel’s comment, I can check his current version only with Ifort. My gfortran can not compile the code since it needs to be upgraded to the 9.x versions.
Results (with Ifort)
N = 1000000 , i0 = 84194
Write: time consumed = 0.3079 seconds
BLOCK 1: formatted read toward string to double
Read: time consumed = 0.3865 seconds
A = Max |rval(:)-rref(:)| = 5.551E-17, B = epsilon(1.0d0) = 2.220E-16, B-A = 1.665E-16
BLOCK 2: C st_to_r8
Read: time consumed = 0.1036 seconds
Read: time consumed = 0.1049 seconds (array)
A = Max |rval(:)-rref(:)| = 5.551E-17, B = epsilon(1.0d0) = 2.220E-16, B-A = 1.665E-16
BLOCK 3: F st_to_r8
Read: time consumed = 0.0943 seconds
Read: time consumed = 0.0970 seconds (array)
A = Max |rval(:)-rref(:)| = 2.220E-16, B = epsilon(1.0d0) = 2.220E-16, B-A = 0.000E+00
BLOCK 4: F str2real
Read: time consumed = 0.0609 seconds
Read: time consumed = 0.0605 seconds (array)
A = Max |rval(:)-rref(:)| = 3.331E-16, B = epsilon(1.0d0) = 2.220E-16, B-A = -1.110E-16
*** WARNING: A > B, A/B = 1.500E+00
Conclusion: (only from my side)
- direct method: slow
- strtod (namely c_st_to_r8): fast
- f_strtod (namely f_st_to_r8): faster.
- str2real: fastest. But the accuracy may be lost, and this issue may depend only on compiler. For instance, with Ifort the error is about 1.5*epsilon.
Source code of str2real_m.f90, in which I added the elemental attribution to call the routine with array, as in the test program. To use the new str2real, we must add the line use str2real_m to the test program.
module str2real_m
implicit none
private
public :: str2real
integer, parameter :: wp = kind(1.0d0)
integer, parameter :: ikind = selected_int_kind(2)
integer(kind=ikind), parameter :: digit_0 = ichar('0')
integer(kind=ikind), parameter :: period = ichar('.') - digit_0
integer(kind=ikind), parameter :: minus_sign = ichar('-') - digit_0
real(wp), parameter :: whole_number_base(*) = &
[1d15, 1d14, 1d13, 1d12, 1d11, 1d10, 1d9, 1d8, &
1d7, 1d6, 1d5, 1d4, 1d3, 1d2, 1d1, 1d0]
real(wp), parameter :: fractional_base(*) = &
[1d-1, 1d-2, 1d-3, 1d-4, 1d-5, 1d-6, 1d-7, 1d-8, &
1d-9, 1d-10, 1d-11, 1d-12, 1d-13, 1d-14, 1d-15, 1d-16, &
1d-17, 1d-18, 1d-19, 1d-20, 1d-21, 1d-22, 1d-23, 1d-24]
real(wp), parameter :: period_skip = 0d0
real(wp), parameter :: base(*) = &
[whole_number_base, period_skip, fractional_base]
contains
elemental &
function str2real(s) result(r)
character(*), intent(in) :: s
real(wp) :: r
real(wp) :: r_coefficient
real(wp) :: r_exponent
integer, parameter :: N = 32
character(N) :: factors_char
integer(kind=ikind) :: factors(N)
integer(kind=ikind) :: mask(N)
integer :: period_loc
integer :: exponent_loc
integer :: mask_from
integer :: mask_till
intrinsic :: findloc, scan
integer :: findloc, scan
equivalence(factors, factors_char)
factors_char = s
factors = factors - digit_0
period_loc = findloc(factors, period, 1)
exponent_loc = scan(s, 'eE', back=.true.)
if(exponent_loc == 0) exponent_loc = len(s) + 1
if(period_loc == 0) period_loc = exponent_loc
where (0 <= factors .and. factors <= 9)
mask = 1
elsewhere
mask = 0
end where
mask_from = 18 - period_loc
mask_till = mask_from + exponent_loc - 2
r_coefficient = sum( &
factors(:exponent_loc - 1) * &
base(mask_from:mask_till) * &
mask(:exponent_loc - 1))
r_exponent = sum( &
factors(exponent_loc+1:len(s)) * &
mask(exponent_loc+1:len(s)) * &
base(17-(len(s)-exponent_loc):16))
if(factors(exponent_loc+1) == minus_sign) r_exponent = -r_exponent
if(factors(1) == minus_sign) r_coefficient = -r_coefficient
r = r_coefficient * 10 ** r_exponent
end function str2real
end module mod_str2real
Test program:
program main
!
use, intrinsic :: iso_fortran_env, only: dp => real64, i8 => int64
use mod_st_to_dp
use str2real_m
!
implicit none
!
integer,parameter :: n = 1000000 !! number of values
!
character(len=30),dimension(:),allocatable :: strs
real(dp),dimension(:),allocatable :: rval, rref
!
integer :: i, i0
integer :: ierr
real(dp) :: r
integer(i8) :: start, finish, count_rate
real(dp),parameter :: eps = epsilon(1.0_dp)
!
!
! create a list of values to parse
!
allocate( strs(n), rval(n), rref(n) )
!
! Initializing RNG: with a dummy i
r = util_unirand (i)
i0 = int(n*r) + 1
write(*,*) 'N =', n, ', i0 =', i0
do i = 1, n
rref(i) = util_unirand()
enddo
call system_clock(start, count_rate)
do i = 1, n
write(strs(i), '(E30.16)') rref(i)
enddo
call system_clock(finish)
write(*,'(A30,1X,F7.4,1X,A)') 'Write: time consumed =', &
(finish-start)/real(count_rate,dp), ' seconds'
blk_io: block
write(*,11) "BLOCK 1: formatted read toward string to double"
!
call system_clock(start, count_rate)
do i = 1, n
read(strs(i),fmt=*,iostat=ierr) rval(i)
enddo
call system_clock(finish)
write(*,'(A30,1X,F7.4,1X,A)') 'Read: time consumed =', &
(finish-start)/real(count_rate,dp), ' seconds'
!
!! if ( abs(rval(i0)-rref(i0)) > eps ) then
!! print *, "Warning: mismatch during formatted read."
!! write(*,10) i0, trim(strs(i0)), rval(i0)
! exit blk_io
!! endif
!
call show_err ( maxval(abs(rval-rref)), eps )
!
end block blk_io
blk_c: block
write(*,11) "BLOCK 2: C st_to_r8"
!
call system_clock(start, count_rate)
do i = 1, n
call c_st_to_r8 ( strs(i), rval(i) )
enddo
call system_clock(finish)
write(*,'(A30,1X,F7.4,1X,A)') 'Read: time consumed =', &
(finish-start)/real(count_rate,dp), ' seconds'
!
!! if ( abs(rval(i0)-rref(i0)) > eps ) then
!! print *, "Warning: mismatch during st_to_r8."
!! write(*,10) i0, trim(strs(i0)), rval(i0)
! exit blk_c
!! endif
!
call system_clock(start, count_rate)
call c_st_to_r8 ( strs, rval )
call system_clock(finish)
write(*,'(A30,1X,F7.4,1X,A)') 'Read: time consumed =', &
(finish-start)/real(count_rate,dp), ' seconds (array)'
!
call show_err ( maxval(abs(rval-rref)), eps )
!
end block blk_c
blk_f: block
write(*,11) "BLOCK 3: F st_to_r8"
!
call system_clock (start, count_rate)
do i = 1, n
call f_st_to_r8 ( strs(i), rval(i) )
enddo
call system_clock(finish)
write(*,'(A30,1X,F7.4,1X,A)') 'Read: time consumed =', &
(finish-start)/real(count_rate,dp), ' seconds'
!
!! if ( abs(rval(i0)-rref(i0)) > eps ) then
!! print *, "Warning: mismatch during st_to_r8."
!! write(*,10) i0, trim(strs(i0)), rval(i0)
! exit blk_f
!! endif
!
call system_clock(start, count_rate)
call f_st_to_r8 ( strs, rval )
call system_clock(finish)
write(*,'(A30,1X,F7.4,1X,A)') 'Read: time consumed =', &
(finish-start)/real(count_rate,dp), ' seconds (array)'
!
call show_err ( maxval(abs(rval-rref)), eps )
!
end block blk_f
blk_4: block
write(*,11) "BLOCK 4: F str2real "
!
call system_clock (start, count_rate)
do i = 1, n
rval(i) = str2real ( strs(i) )
enddo
call system_clock(finish)
write(*,'(A30,1X,F7.4,1X,A)') 'Read: time consumed =', &
(finish-start)/real(count_rate,dp), ' seconds'
!
call system_clock(start, count_rate)
rval = str2real ( strs )
call system_clock(finish)
write(*,'(A30,1X,F7.4,1X,A)') 'Read: time consumed =', &
(finish-start)/real(count_rate,dp), ' seconds (array)'
!
call show_err ( maxval(abs(rval-rref)), eps )
!
end block blk_4
!
10 format ( 'i =', i9, ', string(i) =', a, ', zval(i) =', e25.17 )
11 format ( /, 80('-'), /, a )
!
deallocate ( strs, rval, rref )
!
STOP
!
contains
!======================================================================
!
! This is the ran0 in Numerical Rrecipes in Fortran.
! To initialize,
! call system_clock( idummy )
! Do not alter idummy between successive deviates.
!
function util_unirand ( idum ) result( ran0 )
!
implicit none
integer,parameter :: rk = kind(1.0D0), ik = 4
!
! ARGUMENTS:
!
real(rk) :: ran0
integer(ik),intent(in),optional :: idum
!
! local constants and variable
!
integer(ik) :: k
integer(ik),save :: idummy
integer(ik),parameter :: &
ia=16807, im=2147483647, iq=127773, ir=2836, mask=123459876
real(rk),parameter :: am = 1.0_rk / im
!
!
if ( present(idum) ) call system_clock( idummy )
!
idummy = ieor( idummy, mask )
k = idummy / iq
idummy = ia*( idummy - k*iq ) - ir*k
if ( idummy .lt. 0 ) idummy = idummy + im
ran0 = am*idummy
idummy = ieor( idummy, mask )
return
end function
!
!=====
subroutine show_err ( er, ep )
real(dp),intent(in) :: er, ep
real(dp) :: tmp
!
tmp = ep - er
write(*,10) er, ep, tmp
if ( tmp .lt. 0.0_dp ) write(*,11) er/ep
return
10 format( 'A = Max |rval(:)-rref(:)| =', 1pe10.3,',', 3x, &
'B = epsilon(1.0d0) =', 1pe10.3,',', 3x, &
'B-A =', 1pe11.3 )
11 format( '*** WARNING: A > B, A/B =', 1pe11.3 )
end subroutine
!
!======================================================================
end program
!======================================================================