diff --git a/ci/fpm-deployment.sh b/ci/fpm-deployment.sh index fd81d1258..8cc1b245c 100644 --- a/ci/fpm-deployment.sh +++ b/ci/fpm-deployment.sh @@ -9,7 +9,8 @@ destdir="${DESTDIR:-stdlib-fpm}" fypp="${FYPP:-$(which fypp)}" # Arguments for the fypp preprocessor -fyflags="${FYFLAGS:--DMAXRANK=4}" +maxrank=4 +fyflags="${FYFLAGS:--DMAXRANK=$maxrank}" # Number of parallel jobs for preprocessing if [ $(uname) = "Darwin" ]; then @@ -44,6 +45,12 @@ mkdir -p "$destdir/src" "$destdir/test" "$destdir/example" find src -maxdepth 1 -iname "*.fypp" \ | cut -f1 -d. | xargs -P "$njob" -I{} "$fypp" "{}.fypp" "$destdir/{}.f90" $fyflags +find test -name "test_*.fypp" -exec cp {} "$destdir/test/" \; +fyflags="${fyflags} -I src" +find $destdir/test -maxdepth 1 -iname "*.fypp" \ + | cut -f1 -d. | xargs -P "$njob" -I{} "$fypp" "{}.fypp" "{}.f90" $fyflags +find $destdir/test -name "test_*.fypp" -exec rm {} \; + # Collect stdlib source files find src -maxdepth 1 -iname "*.f90" -exec cp {} "$destdir/src/" \; find test -name "test_*.f90" -exec cp {} "$destdir/test/" \; diff --git a/doc/specs/stdlib_str2num.md b/doc/specs/stdlib_str2num.md new file mode 100644 index 000000000..47b783cc5 --- /dev/null +++ b/doc/specs/stdlib_str2num.md @@ -0,0 +1,82 @@ +--- +title: str2num +--- + +# The `stdlib_str2num` module + +This module proposes a function-style interface for string-to-number conversion. It also profits from Fortran's interfaces to implement precision-dependant algorithms to maximize runtime efficiency. + +[TOC] + +## `to_num` - conversion of strings to numbers + +### Status + +Experimental + +### Description + +Convert a string or an array of strings to numerical types. + +### Syntax + +`number = [[stdlib_str2num(module):to_num(interface)]](string, mold)` + +### Arguments + +`string`: argument has `intent(in)` and is of type `character(*)`. + +`mold`: argument has `intent(in)` and is of numerical type (that is of `integer` or of `real`). **Note**: The type of the `mold` argument defines the type of the result. + +### Return value + +Return a scalar of numerical type (i.e., `integer`, or `real`). + +### Example + +```fortran +{!example/strings/example_string_to_number.f90!} +``` + +## `to_num_from_stream` - conversion of a stream of values in a string to numbers + +### Status + +Experimental + +### Description + +Convert a stream of values in a string to an array of values. + +### Syntax + +`number = [[stdlib_str2num(module):to_num_from_stream(interface)]](string, mold)` + +### Arguments + +`string`: argument has `intent(in)` and is of type `character(:), pointer`. + +`mold`: argument has `intent(in)` and is of numerical type (currently of `integer` or `real`). **Note**: The type of the `mold` argument defines the type of the result. + +### Return value + +Return a scalar of numerical type (i.e., `integer` or `real`). + +### Example + +```fortran +{!example/strings/example_stream_of_strings_to_numbers.f90!} +``` + +## Note +The accuracy of the conversion is implementation dependent; it is recommended that implementers guarantee precision down to the last 3 bits. + +**The current implementation has been tested to provide for** : + +`sp` : exact match + +`dp` : precision up-to epsilon(0.0_dp) + +`qp` : precision around 200*epsilon(0.0_qp) + +Where precision refers to the relative difference between `to_num` and `read`. On the other hand, `to_num` provides speed-ups ranging from 4x to >10x compared to the intrinsic `read`. diff --git a/example/strings/CMakeLists.txt b/example/strings/CMakeLists.txt index 086140bcb..cbaf4f0f3 100644 --- a/example/strings/CMakeLists.txt +++ b/example/strings/CMakeLists.txt @@ -10,3 +10,5 @@ ADD_EXAMPLE(starts_with) ADD_EXAMPLE(strip) ADD_EXAMPLE(to_string) ADD_EXAMPLE(zfill) +ADD_EXAMPLE(string_to_number) +ADD_EXAMPLE(stream_of_strings_to_numbers) \ No newline at end of file diff --git a/example/strings/example_stream_of_strings_to_numbers.f90 b/example/strings/example_stream_of_strings_to_numbers.f90 new file mode 100644 index 000000000..40845beab --- /dev/null +++ b/example/strings/example_stream_of_strings_to_numbers.f90 @@ -0,0 +1,22 @@ +program example_stream_of_strings_to_numbers + use stdlib_kinds, only: dp + use stdlib_str2num, only: to_num_from_stream + character(:), allocatable, target :: chain + character(len=:), pointer :: cptr + real(dp), allocatable :: r(:), p(:) + integer :: i + + chain = " 1.234 1.E1 1e0 0.1234E0 12.21e+001 -34.5E1" + allocate( r(6), p(6) ) + !> Example for streamline conversion using `to_num_from_stream` + cptr => chain + do i =1, 6 + r(i) = to_num_from_stream( cptr , r(i) ) !> the pointer is shifted within the function + end do + read(chain,*) p + print *, "Reading with to_num_from_stream" + print *, r + print *, "Reading with formatted read" + print *, p + +end program example_stream_of_strings_to_numbers \ No newline at end of file diff --git a/example/strings/example_string_to_number.f90 b/example/strings/example_string_to_number.f90 new file mode 100644 index 000000000..4781a9c6e --- /dev/null +++ b/example/strings/example_string_to_number.f90 @@ -0,0 +1,10 @@ +program example_string_to_number + use stdlib_kinds, only: dp + use stdlib_str2num, only: to_num + implicit none + character(:), allocatable :: txt + real(dp) :: x + + txt = ' 8.8541878128e−12 ' + x = to_num( txt , x ) + end program example_string_to_number \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8a6fe66cc..0c2f76c8d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -55,6 +55,7 @@ set(fppFiles stdlib_math_is_close.fypp stdlib_math_all_close.fypp stdlib_math_diff.fypp + stdlib_str2num.fypp stdlib_string_type.fypp stdlib_string_type_constructor.fypp stdlib_strings_to_string.fypp diff --git a/src/stdlib_str2num.fypp b/src/stdlib_str2num.fypp new file mode 100644 index 000000000..d7deac90c --- /dev/null +++ b/src/stdlib_str2num.fypp @@ -0,0 +1,594 @@ +#:include "common.fypp" +!> The `stdlib_str2num` module provides procedures and interfaces for conversion +!> of characters to numerical types. Currently supported: `integer` and `real`. +!! ([Specification](../page/specs/stdlib_str2num.html) +!> +!> This code was modified from https://github.com/jalvesz/Fortran-String-to-Num by Alves Jose +!> And was possible thanks to all the discussions in this thread https://fortran-lang.discourse.group/t/faster-string-to-double/ +!> +!> Known precisions limits of current proposal : +!> Conversion to double precision is exact up to epsilon(0.0_dp) +!> example: +!> input : 123456.78901234567890123456789012345678901234567890+2 +!> formatted read : 12345678.90123457 +!> to_num : 12345678.90123457 +!> difference abs : 0.1862645149230957E-08 +!> difference rel : 0.1508742584455759E-13% +!> +!> Conversion to quadruple precision can deviate at about 200*epsilon(0.0_qp) +!> example: +!> input : 0.140129846432481707092372958328991613128026194187651577175706828388979108268586060148663818836212158203125E-443 +!> formatted read : 0.140129846432481707092372958328991608E-443 +!> to_num : 0.140129846432481707092372958328996233E-443 +!> difference abs : 0.4625E-475 +!> difference rel : 0.3300E-029% + +module stdlib_str2num + use stdlib_kinds, only: sp, dp, xdp, qp, int8, int16, int32, int64 + use, intrinsic:: ieee_arithmetic, only: ieee_value, ieee_positive_inf, ieee_quiet_nan + implicit none + private + public :: to_num, to_num_from_stream + + integer(int8), parameter :: digit_0 = ichar('0',int8) + integer(int8), parameter :: period = ichar('.',int8) - digit_0 + integer(int8), parameter :: comma = ichar(',',int8) - digit_0 + integer(int8), parameter :: minus_sign = ichar('-',int8) - digit_0 + integer(int8), parameter :: plus_sign = ichar('+',int8) - digit_0 + integer(int8), parameter :: Inf = ichar('I',int8) + integer(int8), parameter :: NaN = ichar('N',int8) + integer(int8), parameter :: le = ichar('e',int8) - digit_0 + integer(int8), parameter :: BE = ichar('E',int8) - digit_0 + integer(int8), parameter :: ld = ichar('d',int8) - digit_0 + integer(int8), parameter :: BD = ichar('D',int8) - digit_0 + integer(int8), parameter :: LF = 10, CR = 13, WS = 32 + + interface to_num + !! version: experimental + !! + !! Conversion of strings to numbers + !! ([Specification](../page/specs/stdlib_str2num.html#to-num-conversion-of-strings-to-numbers)) + #:for k1, t1 in (INT_KINDS_TYPES + REAL_KINDS_TYPES) + module procedure to_${k1}$ + #:endfor + end interface + + interface to_num_from_stream + !! version: experimental + !! + !! Conversion of a stream of values in a string to numbers + !! ([Specification](../page/specs/stdlib_str2num.html#to-num-p-conversion-of-a-stream-of-values-in-a-strings-to-numbers)) + #:for k1, t1 in (INT_KINDS_TYPES + REAL_KINDS_TYPES) + module procedure to_${k1}$_from_stream + #:endfor + end interface + + interface to_num_base + #:for k1, t1 in (INT_KINDS_TYPES + REAL_KINDS_TYPES) + module procedure to_${k1}$_base + #:endfor + end interface + + contains + + !--------------------------------------------- + ! String To Number interfaces + !--------------------------------------------- + + #:for k1, t1 in (INT_KINDS_TYPES + REAL_KINDS_TYPES) + elemental function to_${k1}$(s,mold) result(v) + ! -- In/out Variables + character(*), intent(in) :: s !> input string + ${t1}$, intent(in) :: mold !> dummy argument to disambiguate at compile time the generic interface + ${t1}$ :: v !> Output ${t1}$ value + ! -- Internal Variables + integer(int8) :: p !> position within the number + integer(int8) :: stat !> error status + !---------------------------------------------- + call to_num_base(s,v,p,stat) + end function + + function to_${k1}$_from_stream(s,mold,stat) result(v) + ! -- In/out Variables + character(len=:), pointer :: s !> input string + ${t1}$, intent(in) :: mold !> dummy argument to disambiguate at compile time the generic interface + ${t1}$ :: v !> Output ${t1}$ value + integer(int8),intent(inout), optional :: stat + ! -- Internal Variables + integer(int8) :: p !> position within the number + integer(int8) :: err + !---------------------------------------------- + call to_num_base(s,v,p,err) + p = min( p , len(s) ) + s => s(p:) + if(present(stat)) stat = err + end function + + #:endfor + !--------------------------------------------- + ! String To Number Implementations + !--------------------------------------------- + + #:for k1, t1 in INT_KINDS_TYPES + elemental subroutine to_${k1}$_base(s,v,p,stat) + !> Return an ${k1}$ integer + ! -- In/out Variables + character(*), intent(in) :: s !> input string + ${t1}$, intent(out) :: v !> Output real value + integer(int8), intent(out) :: p !> position within the number + integer(int8), intent(out) :: stat !> status upon succes or failure to read + ! -- Internal Variables + integer(int8) :: val + !---------------------------------------------- + stat = 23 !> initialize error status with any number > 0 + !---------------------------------------------- + ! Find first non white space + p = shift_to_nonwhitespace(s) + !---------------------------------------------- + v = 0 + do while( p<=len(s) ) + val = iachar(s(p:p))-digit_0 + if( val >= 0 .and. val <= 9 ) then + v = v*10 + val + p = p + 1 + else + exit + end if + end do + stat = 0 + end subroutine + + #:endfor + + elemental subroutine to_sp_base(s,v,p,stat) + integer, parameter :: wp = sp + !> Sequentially unroll the character and get the sub integers composing the whole number, fraction and exponent + ! -- In/out Variables + character(*), intent(in) :: s !> input string + real(wp), intent(inout) :: v !> Output real value + integer(int8), intent(out) :: p !> last position within the string + integer(int8), intent(out) :: stat !> status upon success or failure to read + + ! -- Internal Variables + integer(int8), parameter :: nwnb = 39 !> number of whole number factors + integer(int8), parameter :: nfnb = 37 !> number of fractional number factors + integer :: e + ! Notice: We use dp here to obtain exact precision for sp. + ! Otherwise errors may appear in comparison to formatted read. + ! See https://github.com/fortran-lang/stdlib/pull/743#issuecomment-1791953430 for more details + real(dp), parameter :: whole_number_base(nwnb) = [(10._dp**(nwnb-e),e=1,nwnb)] + real(dp), parameter :: fractional_base(nfnb) = [(10._dp**(-e),e=1,nfnb)] + real(dp), parameter :: expbase(nwnb+nfnb) = [whole_number_base, fractional_base] + + integer(int8) :: sign, sige !> sign of integer number and exponential + integer, parameter :: maxdpt = 11 !> Maximum depth to read values on int_wp + integer(dp) :: int_wp !> long integer to capture fractional part + integer :: i_exp !> integer to capture whole number part + integer :: exp_aux + integer(int8) :: i, pP, pE, val , resp + !---------------------------------------------- + stat = 23 !> initialize error status with any number > 0 + !---------------------------------------------- + ! Find first non white space + p = shift_to_nonwhitespace(s) + !---------------------------------------------- + ! Verify leading negative + sign = 1 + if( iachar(s(p:p)) == minus_sign+digit_0 ) then + sign = -1 + p = p + 1 + end if + if( iachar(s(p:p)) == Inf ) then + v = sign*ieee_value(v, ieee_positive_inf) + return + else if( iachar(s(p:p)) == NaN ) then + v = ieee_value(v, ieee_quiet_nan) + return + end if + !---------------------------------------------- + ! read whole and fractional number in a single integer + pP = 127 + int_wp = 0 + do i = p, min(maxdpt+p-1,len(s)) + val = iachar(s(i:i))-digit_0 + if( val >= 0 .and. val <= 9 ) then + int_wp = int_wp*10 + val + else if( val == period ) then + pP = i + else + exit + end if + end do + pE = i ! Fix the exponent position + do while( i<=len(s) ) + val = iachar(s(i:i))-digit_0 + if( val < 0 .or. val > 9 ) exit + i = i + 1 + end do + p = i + resp = pE-min(pP,p) ! If no decimal indicator found it is taken as being in the current p position + if( resp <= 0 ) resp = resp+1 + !---------------------------------------------- + ! Get exponential + sige = 1 + if( p= 0 .and. val <= 9 ) then + i_exp = i_exp*10_int8 + val + p = p + 1 + else + exit + end if + end do + + exp_aux = nwnb-1+resp-sige*i_exp + if( exp_aux>0 .and. exp_aux<=nwnb+nfnb ) then + v = sign*int_wp*expbase(exp_aux) + else + v = sign*int_wp*10._dp**(sige*i_exp-resp+1) + end if + stat = 0 + end subroutine + + elemental subroutine to_dp_base(s,v,p,stat) + integer, parameter :: wp = dp + !> Sequentially unroll the character and get the sub integers composing the whole number, fraction and exponent + ! -- In/out Variables + character(*), intent(in) :: s !> input string + real(wp), intent(inout) :: v !> Output real value + integer(int8), intent(out) :: p !> last position within the string + integer(int8), intent(out) :: stat !> status upon success or failure to read + + ! -- Internal Variables + integer(int8), parameter :: nwnb = 40 !> number of whole number factors + integer(int8), parameter :: nfnb = 64 !> number of fractional number factors + integer :: e + real(wp), parameter :: whole_number_base(nwnb) = [(10._wp**(nwnb-e),e=1,nwnb)] + real(wp), parameter :: fractional_base(nfnb) = [(10._wp**(-e),e=1,nfnb)] + real(wp), parameter :: expbase(nwnb+nfnb) = [whole_number_base, fractional_base] + + integer(int8) :: sign, sige !> sign of integer number and exponential + integer, parameter :: maxdpt = 19 !> Maximum depth to read values on int_wp + integer(wp) :: int_wp !> long integer to capture fractional part + integer :: i_exp !> integer to capture whole number part + integer :: exp_aux + integer(int8) :: i, pP, pE, val , resp + !---------------------------------------------- + stat = 23 !> initialize error status with any number > 0 + !---------------------------------------------- + ! Find first non white space + p = shift_to_nonwhitespace(s) + !---------------------------------------------- + ! Verify leading negative + sign = 1 + if( iachar(s(p:p)) == minus_sign+digit_0 ) then + sign = -1 + p = p + 1 + end if + if( iachar(s(p:p)) == Inf ) then + v = sign*ieee_value(v, ieee_positive_inf) + return + else if( iachar(s(p:p)) == NaN ) then + v = ieee_value(v, ieee_quiet_nan) + return + end if + !---------------------------------------------- + ! read whole and fractional number in a single integer + pP = 127 + int_wp = 0 + do i = p, min(maxdpt+p-1,len(s)) + val = iachar(s(i:i))-digit_0 + if( val >= 0 .and. val <= 9 ) then + int_wp = int_wp*10 + val + else if( val == period ) then + pP = i + else + exit + end if + end do + pE = i ! Fix the exponent position + do while( i<=len(s) ) + val = iachar(s(i:i))-digit_0 + if( val < 0 .or. val > 9 ) exit + i = i + 1 + end do + p = i + resp = pE-min(pP,p) ! If no decimal indicator found it is taken as being in the current p position + if( resp <= 0 ) resp = resp+1 + !---------------------------------------------- + ! Get exponential + sige = 1 + if( p= 0 .and. val <= 9 ) then + i_exp = i_exp*10_int8 + val + p = p + 1 + else + exit + end if + end do + + exp_aux = nwnb-1+resp-sige*i_exp + if( exp_aux>0 .and. exp_aux<=nwnb+nfnb ) then + v = sign*int_wp*expbase(exp_aux) + else + v = sign*int_wp*10._wp**(sige*i_exp-resp+1) + end if + stat = 0 + end subroutine + +#:if WITH_XDP + elemental subroutine to_xdp_base(s,v,p,stat) + integer, parameter :: wp = xdp + !> Sequentially unroll the character and get the sub integers composing the whole number, fraction and exponent + ! -- In/out Variables + character(*), intent(in) :: s !> input string + real(wp), intent(inout) :: v !> Output real value + integer(int8), intent(out) :: p !> last position within the string + integer(int8), intent(out) :: stat !> status upon success or failure to read + + ! -- Internal Variables + integer(int8), parameter :: nwnb = 50 !> number of whole number factors + integer(int8), parameter :: nfnb = 64 !> number of fractional number factors + integer :: e + real(wp), parameter :: whole_number_base(nwnb) = [(10._wp**(nwnb-e),e=1,nwnb)] + real(wp), parameter :: fractional_base(nfnb) = [(10._wp**(-e),e=1,nfnb)] + real(wp), parameter :: expbase(nwnb+nfnb) = [whole_number_base, fractional_base] + + integer(int8) :: sign, sige !> sign of integer number and exponential + integer, parameter :: maxdpt = 19 !> Maximum depth to read values on int_dp + integer(dp) :: int_dp1, int_dp2 !> long integers to capture whole and fractional part + integer :: i_exp !> integer to capture exponent number + integer :: exp_aux + integer(int8) :: i, pP, pE, val , resp, icount, aux + !---------------------------------------------- + stat = 23 !> initialize error status with any number > 0 + !---------------------------------------------- + ! Find first non white space + p = shift_to_nonwhitespace(s) + !---------------------------------------------- + ! Verify leading negative + sign = 1 + if( iachar(s(p:p)) == minus_sign+digit_0 ) then + sign = -1 + p = p + 1 + end if + if( iachar(s(p:p)) == Inf ) then + v = sign*ieee_value(v, ieee_positive_inf) + return + else if( iachar(s(p:p)) == NaN ) then + v = ieee_value(v, ieee_quiet_nan) + return + end if + !---------------------------------------------- + ! read whole and fractional number using two int64 values + pP = 127 + int_dp1 = 0 + int_dp2 = 0 + icount = 0 + aux = 1 + do i = p, min(2*maxdpt+p-1,len(s)) + val = iachar(s(i:i))-digit_0 + if( val >= 0 .and. val <= 9 ) then + icount = icount + 1 + if( icount<=maxdpt ) then + int_dp1 = int_dp1*10 + val + else if( icount<2*maxdpt ) then + int_dp2 = int_dp2*10 + val + end if + else if( val == period ) then + pP = i + aux = 0 + else + exit + end if + end do + pE = i ! Fix the exponent position + do while( i<=len(s) ) + val = iachar(s(i:i))-digit_0 + if( val < 0 .or. val > 9 ) exit + i = i + 1 + end do + p = i + resp = pE-min(pP,p) ! If no decimal indicator found it is taken as being in the current p position + if( resp <= 0 ) resp = resp+1 + !---------------------------------------------- + ! Get exponential + sige = 1 + if( p= 0 .and. val <= 9 ) then + i_exp = i_exp*10_int8 + val + p = p + 1 + else + exit + end if + end do + + exp_aux = nwnb-1+resp-sige*i_exp + if( exp_aux>0 .and. exp_aux<=nwnb+nfnb ) then + if( icount<=maxdpt ) then + v = sign*int_dp1*expbase(exp_aux) + else + v = sign*(int_dp1 + int_dp2*fractional_base(maxdpt-1))*expbase(exp_aux-icount+maxdpt) + end if + else + v = sign*(int_dp1 + int_dp2*fractional_base(maxdpt-1))*10._wp**(sige*i_exp-resp+maxdpt+aux) + end if + stat = 0 + end subroutine +#:endif + +#:if WITH_QP + elemental subroutine to_qp_base(s,v,p,stat) + integer, parameter :: wp = qp + !> Sequentially unroll the character and get the sub integers composing the whole number, fraction and exponent + ! -- In/out Variables + character(*), intent(in) :: s !> input string + real(wp), intent(inout) :: v !> Output real value + integer(int8), intent(out) :: p !> last position within the string + integer(int8), intent(out) :: stat !> status upon success or failure to read + + ! -- Internal Variables + integer(int8), parameter :: nwnb = 50 !> number of whole number factors + integer(int8), parameter :: nfnb = 64 !> number of fractional number factors + integer :: e + real(wp), parameter :: whole_number_base(nwnb) = [(10._wp**(nwnb-e),e=1,nwnb)] + real(wp), parameter :: fractional_base(nfnb) = [(10._wp**(-e),e=1,nfnb)] + real(wp), parameter :: expbase(nwnb+nfnb) = [whole_number_base, fractional_base] + + integer(int8) :: sign, sige !> sign of integer number and exponential + integer, parameter :: maxdpt = 19 !> Maximum depth to read values on int_dp + integer(dp) :: int_dp1, int_dp2 !> long integers to capture whole and fractional part + integer :: i_exp !> integer to capture exponent number + integer :: exp_aux + integer(int8) :: i, pP, pE, val , resp, icount, aux + !---------------------------------------------- + stat = 23 !> initialize error status with any number > 0 + !---------------------------------------------- + ! Find first non white space + p = shift_to_nonwhitespace(s) + !---------------------------------------------- + ! Verify leading negative + sign = 1 + if( iachar(s(p:p)) == minus_sign+digit_0 ) then + sign = -1 + p = p + 1 + end if + if( iachar(s(p:p)) == Inf ) then + v = sign*ieee_value(v, ieee_positive_inf) + return + else if( iachar(s(p:p)) == NaN ) then + v = ieee_value(v, ieee_quiet_nan) + return + end if + !---------------------------------------------- + ! read whole and fractional number using two int64 values + pP = 127 + int_dp1 = 0 + int_dp2 = 0 + icount = 0 + aux = 1 + do i = p, min(2*maxdpt+p-1,len(s)) + val = iachar(s(i:i))-digit_0 + if( val >= 0 .and. val <= 9 ) then + icount = icount + 1 + if( icount<=maxdpt ) then + int_dp1 = int_dp1*10 + val + else if( icount<2*maxdpt ) then + int_dp2 = int_dp2*10 + val + end if + else if( val == period ) then + pP = i + aux = 0 + else + exit + end if + end do + pE = i ! Fix the exponent position + do while( i<=len(s) ) + val = iachar(s(i:i))-digit_0 + if( val < 0 .or. val > 9 ) exit + i = i + 1 + end do + p = i + resp = pE-min(pP,p) ! If no decimal indicator found it is taken as being in the current p position + if( resp <= 0 ) resp = resp+1 + !---------------------------------------------- + ! Get exponential + sige = 1 + if( p= 0 .and. val <= 9 ) then + i_exp = i_exp*10_int8 + val + p = p + 1 + else + exit + end if + end do + + exp_aux = nwnb-1+resp-sige*i_exp + if( exp_aux>0 .and. exp_aux<=nwnb+nfnb ) then + if( icount<=maxdpt ) then + v = sign*int_dp1*expbase(exp_aux) + else + v = sign*(int_dp1 + int_dp2*fractional_base(maxdpt-1))*expbase(exp_aux-icount+maxdpt) + end if + else + v = sign*(int_dp1 + int_dp2*fractional_base(maxdpt-1))*10._wp**(sige*i_exp-resp+maxdpt+aux) + end if + stat = 0 + end subroutine +#:endif + + !--------------------------------------------- + ! Internal Utility functions + !--------------------------------------------- + + elemental function shift_to_nonwhitespace(s) result(p) + !> move string to position of the next non white space character + character(*),intent(in) :: s !> character chain + integer(int8) :: p !> position + !---------------------------------------------- + p = 1 + do while( p move string to position of the next white space character + character(*),intent(in) :: s !> character chain + integer(int8) :: p !> position + !---------------------------------------------- + p = 1 + do while( p Collect all exported unit tests + subroutine collect_string_to_number(testsuite) + !> Collection of tests + type(unittest_type), allocatable, intent(out) :: testsuite(:) + + testsuite = [ & + new_unittest("to_sp", test_to_sp), & + new_unittest("to_dp", test_to_dp) & +#:if WITH_QP + , new_unittest("to_qp", test_to_qp) & +#:endif +#:if WITH_XDP + , new_unittest("to_xdp", test_to_xdp) & +#:endif + ] + end subroutine collect_string_to_number + + #:for k1, t1 in REAL_KINDS_TYPES + subroutine test_to_${k1}$(error) + type(error_type), allocatable, intent(out) :: error + integer, parameter :: wp = ${k1}$ + + call check(error, ucheck("1.234")) + if (allocated(error)) return + + call check(error, ucheck("1.E1")) + if (allocated(error)) return + + call check(error, ucheck("1e0")) + if (allocated(error)) return + + call check(error, ucheck("0.1234E0")) + if (allocated(error)) return + + call check(error, ucheck("12.34E0")) + if (allocated(error)) return + + call check(error, ucheck("0.34E2")) + if (allocated(error)) return + + call check(error, ucheck(".34e0")) + if (allocated(error)) return + + call check(error, ucheck("34.E1")) + if (allocated(error)) return + + call check(error, ucheck("-34.5E1")) + if (allocated(error)) return + + call check(error, ucheck("0.0021E10")) + if (allocated(error)) return + + call check(error, ucheck("12.21e-1")) + if (allocated(error)) return + + call check(error, ucheck("12.21e+001 ")) + if (allocated(error)) return + + call check(error, ucheck("-1")) + if (allocated(error)) return + + call check(error, ucheck(" -0.23317260678539647E-01 ")) + if (allocated(error)) return + + call check(error, ucheck(" 2.5647869e-003 "//char(13)//char(10))) + if (allocated(error)) return + + call check(error, ucheck("1.-3")) + if (allocated(error)) return + + call check(error, ucheck("Inf")) + if (allocated(error)) return + + call check(error, ucheck("-Inf")) + if (allocated(error)) return + + call check(error, ucheck("NaN")) + if (allocated(error)) return + + call check(error, ucheck("0.123456789123456789123456789123456789")) + if (allocated(error)) return + + call check(error, ucheck("1234567890123456789012345678901234567890-9") ) + if (allocated(error)) return + + call check(error, ucheck("123456.78901234567890123456789012345678901234567890+2") ) + if (allocated(error)) return + + call check(error, ucheck("0.140129846432481707092372958328991613128026194187651577"//& + & "175706828388979108268586060148663818836212158203125E-44")) + if (allocated(error)) return + + contains + logical function ucheck(s) + character(*), intent(in) :: s + real(wp) :: formatted_read_out + real(wp) :: to_num_out + real(wp) :: abs_err + real(wp) :: rel_err + + ucheck = .true. + read(s,*) formatted_read_out + to_num_out = to_num(s, to_num_out) + abs_err = to_num_out - formatted_read_out + rel_err = abs_err / formatted_read_out + + #:if k1 == "sp" + if(abs(rel_err) > 0.0_wp) then + #:elif k1 == "dp" + if(abs(rel_err) > epsilon(0.0_wp)) then + #:elif k1 == "xdp" + if(abs(rel_err) > 200*epsilon(0.0_wp)) then + #:elif k1 == "qp" + if(abs(rel_err) > 200*epsilon(0.0_wp)) then + #:endif + write(*,"('formatted read : ' g0)") formatted_read_out + write(*,"('to_num : ' g0)") to_num_out + write(*,"('difference abs : ' g0)") abs_err + write(*,"('difference rel : ' g0 '%')") rel_err * 100 + ucheck = .false. + end if + end function + end subroutine + + #:endfor + +end module test_string_to_number + +program tester + use, intrinsic :: iso_fortran_env, only : error_unit + use testdrive, only : run_testsuite, new_testsuite, testsuite_type + use test_string_to_number, only : collect_string_to_number + implicit none + integer :: stat, is + type(testsuite_type), allocatable :: testsuites(:) + character(len=*), parameter :: fmt = '("#", *(1x, a))' + + stat = 0 + + testsuites = [ & + new_testsuite("string_to_number", collect_string_to_number) & + ] + + do is = 1, size(testsuites) + write(error_unit, fmt) "Testing:", testsuites(is)%name + call run_testsuite(testsuites(is)%collect, error_unit, stat) + end do + + if (stat > 0) then + write(error_unit, '(i0, 1x, a)') stat, "test(s) failed!" + error stop + end if +end program