fnNumberStringUtil.f90 Source File


Contents


Source Code

! Copyright 2019 Khang Hoang Nguyen
!
! Permission is hereby granted, free of charge, to any person obtaining
! a copy of this software and associated documentation files
! (the "Software"), to deal in the Software without restriction,
! including without limitation the rights to use, copy, modify, merge,
! publish, distribute, sublicense, and/or sell copies of the Software,
! and to permit persons to whom the Software is furnished to do so,
! subject to the following conditions
!
! The above copyright notice and this permission notice shall be
! included in all copies or substantial portions of the Software.
!
! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
! EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
! MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
! NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
! BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
! ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
! CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
! SOFTWARE.

!| author: Khang Hoang Nguyen
!  license: <a href="https://github.com/kevinhng86/faiNumber-Fortran/blob/master/LICENSE">MIT</a>
!  since: 1.0.0.f
! 
!  <p>This module, <code>fnNumberStringUtil</code> contains procedures
!  for validating and comparing numerical strings.
!  
!  For comparing numerical strings, this module has some very powerful
!  and flexible procedures that can compare numerical strings of
!  unlimited length. These procedures, [[assumeCompare]],
!  [[assumeCompareAllBase]], [[fnStringCompare]], and
!  [[fnStringCompareAsBase]] can compare numerical strings without
!  needing to parse the strings to values of a data type. Under the best
!  case scenario, the mentioned procedures can be extremely fast.
!  </p>
!
!  @note  Unless stated otherwise, procedures of this module are pure
!         procedures.
module fnNumberStringUtil
    use fnConsts
    implicit none
contains
    !| author: Khang Hoang Nguyen
    !  since: 1.0.0.f
    !
    !  <p>Check if the `input` string is a valid signed decimal integer
    !  string.
    !
    !  <p>This function ignores leading and trailing whitespaces.
    !
    !  <p>This function returns a logical value of `.TRUE.` if the `input`
    !  string is a valid signed decimal integer string.
    !
    !  <p>A logical value of `.FALSE.` will be returned where the `input`
    !  string is empty(only spaces), 0 length, or is not a valid
    !  signed decimal integer string.
    !  </p>
    pure logical function isInteger(input) result(lOut)
        implicit none
        character(len=*), intent(in)  ::  input                         !! A string.
        integer(k_int32)              ::  length, start
        lOut = .FALSE.

        length = len(input) ; start = 1
        
        do while ( length > 0 )
            if ( input(length:length) /= charspace ) exit
            length = length - 1
        end do       
        do while ( start <= length )
            if ( input(start:start) /= charspace ) exit
            start = start + 1
        end do
        if ( start > length ) return
        
        if ( input(start:start) == charneg .OR. input(start:start) == charpos ) then
            start = start + 1
            if ( start > length ) return
        else
        end if

        do while( start <= length )
            if ( IEOR(ICHAR(input(start:start)), czero32) > 9) return
            start = start + 1
        end do

        lOut = .TRUE.
    end function isInteger

    !| author: Khang Hoang Nguyen
    !  since: 1.0.0.f
    !
    !  <p>Check if the `input` string is a valid unsigned decimal integer
    !  string.
    !
    !  <p>This function ignores leading and trailing whitespaces.
    !
    !  <p>This function returns a logical value of `.TRUE.` if the `input`
    !  string is a valid unsigned decimal integer string.
    !
    !  <p>A logical value of `.FALSE.` will be returned where the `input`
    !  string is empty(only spaces), 0 length, or is not a valid
    !  unsigned decimal integer string.
    pure logical function isUnsignedInteger(input) result(lOut)
        implicit none
        character(len=*), intent(in)  ::  input                         !! A string.
        integer(k_int32)              ::  length, start 
        lOut = .FALSE.

        length = len(input) ; start = 1
        
        do while ( length > 0 )
            if ( input(length:length) /= charspace ) exit
            length = length - 1
        end do
        if ( length == 0 ) return
        
        do while ( start <= length )
            if ( input(start:start) /= charspace ) exit
            start = start + 1
        end do
        if ( start > length ) return

        do while( start <= length )
            if ( IEOR(ICHAR(input(start:start)), czero32) > 9 ) return
            start = start + 1
        end do

        lOut = .TRUE.
    end function isUnsignedInteger
    
    !| author: Khang Hoang Nguyen
    !  since: 1.0.0.f
    !
    !  <p>Check if the `input` string is a valid signed binary integer
    !  string.
    !
    !  <p>This function ignores leading and trailing whitespaces.
    !
    !  <p>This function returns a logical value of `.TRUE.` if the `input`
    !  string is a valid signed binary integer string.
    !
    !  <p>A logical value of `.FALSE.` will be returned where the `input`
    !  string is empty(only spaces), 0 length, or is not a valid
    !  signed binary integer string.
    !  </p>    
    pure logical function isBinary(input) result(lOut)
        implicit none
        character(len=*), intent(in)  ::  input                         !! A string.
        integer(k_int32)              ::  length, start
        lOut = .FALSE.

        length = len(input) ; start = 1

        do while ( length > 0 )
            if ( input(length:length) /= charspace ) exit
            length = length - 1
        end do       
        do while ( start <= length )
            if ( input(start:start) /= charspace ) exit
            start = start + 1
        end do
        if ( start > length ) return
        
        if ( input(start:start) == charneg .OR. input(start:start) == charpos ) then 
            start = start + 1
            if ( start > length ) return
        end if

        do while( start <= length )
            if ( input(start:start) /= '0' .AND. input(start:start) /= '1' ) return
            start = start + 1
        end do

        lOut = .TRUE.
    end function isBinary
    
    !| author: Khang Hoang Nguyen
    !  since: 1.0.0.f
    !
    !  <p>Check if the `input` string is a valid unsigned binary integer
    !  string.
    !
    !  <p>This function ignores leading and trailing whitespaces.
    !
    !  <p>This function returns a logical value of `.TRUE.` if the `input`
    !  string is a valid unsigned binary integer string.
    !
    !  <p>A logical value of `.FALSE.` will be returned where the `input`
    !  string is empty(only spaces), 0 length, or is not a valid
    !  unsigned binary integer string.
    !  </p>
    pure logical function isUnsignedBinary(input) result(lOut)
        implicit none
        character(len=*), intent(in)  ::  input                         !! A string.
        integer(k_int32)              ::  length, start
        lOut = .FALSE.

        length = len(input) ; start = 1

        do while ( length > 0 )
            if ( input(length:length) /= charspace ) exit
            length = length - 1
        end do       
        do while ( start <= length )
            if ( input(start:start) /= charspace ) exit
            start = start + 1
        end do
        if ( start > length ) return

        do while( start <= length )
            if ( input(start:start) /= '0' .AND. input(start:start) /= '1' ) return
            start = start + 1
        end do

        lOut = .TRUE.
    end function isUnsignedBinary
    
    !| author: Khang Hoang Nguyen
    !  since: 1.0.0.f
    !
    !  <p>Check if the `input` string is a valid signed octal integer
    !  string.
    !
    !  <p>This function ignores leading and trailing whitespaces.
    !
    !  <p>This function returns a logical value of `.TRUE.` if the `input`
    !  string is a valid signed octal integer string.
    !
    !  <p>A logical value of `.FALSE.` will be returned where the `input`
    !  string is empty(only spaces), 0 length, or is not a valid
    !  signed octal integer string.
    !  </p>
    pure logical function isOctal(input) result(lOut)
        implicit none
        character(len=*), intent(in)  ::  input                         !! A string.
        integer(k_int32)              ::  length, start
        lOut = .FALSE.
        
        length = len(input) ; start = 1
        
        do while ( length > 0 )
            if ( input(length:length) /= charspace ) exit
            length = length - 1
        end do
        do while ( start <= length )
            if ( input(start:start) /= charspace ) exit
            start = start + 1
        end do
        if ( start > length ) return
        
        if ( input(start:start) == charneg .OR. input(start:start) == charpos ) then 
            start = start + 1
            if ( start > length ) return 
        end if 
        
        do while( start <= length )
            if ( IEOR(ICHAR(input(start:start)), czero32) > 7) return 
            start = start + 1
        end do

        lOut = .TRUE.
    end function isOctal

    !| author: Khang Hoang Nguyen
    !  since: 1.0.0.f
    !
    !  <p>Check if the `input` string is a valid unsigned octal integer
    !  string.
    !
    !  <p>This function ignores leading and trailing whitespaces.
    !
    !  <p>This function returns a logical value of `.TRUE.` if the `input`
    !  string is a valid unsigned octal integer string.
    !
    !  <p>A logical value of `.FALSE.` will be returned where the `input`
    !  string is empty(only spaces), 0 length, or is not a valid
    !  unsigned octal integer string.
    !  </p>
    pure logical function isUnsignedOctal(input) result(lOut)
        implicit none
        character(len=*), intent(in)  ::  input                         !! A string.
        integer(k_int32)              ::  length, start
        lOut = .FALSE.
        
        length = len(input) ; start = 1
        
        do while ( length > 0 )
            if ( input(length:length) /= charspace ) exit
            length = length - 1
        end do       
        do while ( start <= length )
            if ( input(start:start) /= charspace ) exit
            start = start + 1
        end do
        if ( start > length ) return

        do while( start <= length )
            if ( IEOR(ICHAR(input(start:start)), czero32) > 7 ) return
            start = start + 1
        end do 

        lOut = .TRUE.
    end function isUnsignedOctal

    !| author: Khang Hoang Nguyen
    !  since: 1.0.0.f
    !
    !  <p>Check if the `input` string is a valid signed hexadecimal
    !  integer string.
    !
    !  <p>This function ignores leading and trailing whitespaces.
    !
    !  <p>This function returns a logical value of `.TRUE.` if the `input`
    !  string is a valid signed hexadecimal integer string.
    !
    !  <p>A logical value of `.FALSE.` will be returned where the `input`
    !  string is empty(only spaces), 0 length, or is not a valid
    !  signed hexadecimal integer string.
    !  </p>
    pure logical function isHex(input) result(lOut)
        implicit none
        character(len=*), intent(in)  ::  input                         !! A string.
        integer(k_int32)              ::  length, start, c
        lOut = .FALSE.
        
        length = len(input) ; start = 1
        
        do while ( length > 0 )
            if ( input(length:length) /= charspace ) exit
            length = length - 1
        end do       
        do while ( start <= length )
            if ( input(start:start) /= charspace ) exit
            start = start + 1
        end do
        if ( start > length ) return
        
        if ( input(start:start) == charneg .OR. input(start:start) == charpos ) then
            start = start + 1
            if ( start > length ) return
        end if
        
        do while( start <= length )
            c = ICHAR(input(start:start))

            if ( c > 96 .AND. c < 103 ) then
            else if ( c > 64 .AND. c < 71 ) then
            else 
                c = IEOR(c, czero32)
                if ( c > 9 ) return
            end if
            
            start = start + 1
        end do

        lOut = .TRUE.
    end function isHex

    !| author: Khang Hoang Nguyen
    !  since: 1.0.0.f
    !
    !  <p>Check if the `input` string is a valid unsigned hexadecimal
    !  integer string.
    !
    !  <p>This function ignores leading and trailing whitespaces.
    !
    !  <p>This function returns a logical value of `.TRUE.` if the `input`
    !  string is a valid unsigned hexadecimal integer string.
    !
    !  <p>A logical value of `.FALSE.` will be returned where the `input`
    !  string is empty(only spaces), 0 length, or is not a valid
    !  unsigned hexadecimal integer string.
    !  </p>
    pure logical function isUnsignedHex(input) result(lOut)
        implicit none
        character(len=*), intent(in)  ::  input                         !! A string.
        integer(k_int32)              ::  length, c, start
        lOut = .FALSE.

        length = len(input) ; start = 1
        
        do while ( length > 0 )
            if ( input(length:length) /= charspace ) exit
            length = length - 1
        end do
        do while ( start <= length )
            if ( input(start:start) /= charspace ) exit
            start = start + 1
        end do
        if ( start > length ) return

        do while( start <= length )
            c = ICHAR(input(start:start))

            if ( c > 96 .AND. c < 103 ) then
            else if ( c > 64 .AND. c < 71 ) then
            else 
                c = IEOR(c, czero32)
                if ( c > 9 ) return
            end if
            
            start = start + 1            
        end do 

        lOut = .TRUE.
    end function isUnsignedHex

    !| author: Khang Hoang Nguyen
    !  since: 1.0.0.f
    !
    !  <p>Check if the `input` string is a valid signed integer string
    !  of the numbering system with the radix that is defined by the 
    !  value of the argument `base`.
    !
    !  <p>This subroutine ignores leading and trailing whitespaces.
    !  
    !  <p>This subroutine support numbering system with radix from 2 to 36.
    !
    !  <p>This subroutine considers an error to has occurred when the 
    !  value of the argument `base` is smaller than 2 or larger than 36.
    !  </p>
    pure subroutine isBase(input, base, output, error)
        implicit none
        character(len=*), intent(in)   ::  input                        !! A string.
        integer(k_int32), intent(in)   ::  base                         !! An int32 value that define the radix.
        logical         , intent(out)  ::  output                       !! `.TRUE.` if the `input` string is a valid signed integer string of the defined numbering system.
                                                                        !! `.FALSE.` if the `input` string is empty(only spaces), 0 length, or is not a valid signed integer string of the defined numbering system.
        logical         , intent(out)  ::  error                        !! A value of `.TRUE.` on error. Otherwise, a value of `.FALSE.`.
        integer(k_int32)               ::  length, start, c
        output = .FALSE. ; error = .FALSE.
        
        if ( base < 2 .OR. base > 36 ) then 
            error = .TRUE.
            return
        end if

        length = len(input) ; start = 1

        do while ( length > 0 )
            if ( input(length:length) /= charspace ) exit
            length = length - 1
        end do
        do while ( start <= length )
            if ( input(start:start) /= charspace ) exit
            start = start + 1
        end do
        if ( start > length ) return

        if ( input(start:start) == charneg .OR. input(start:start) == charpos ) then 
            start = start + 1
            if ( start > length ) return 
        end if 

        do while (start <= length)
            c = ICHAR(input(start:start))

            if ( c > 96 ) then
                c = c - 87
            else if ( c > 64 ) then
                c = c - 55
            else 
                c = IEOR(c, czero32)
                if ( c > 9 ) return
            end if

            if ( .NOT. (c < base)  ) return
            start = start + 1
        end do
        
        output = .TRUE.
    end subroutine isBase

    !| author: Khang Hoang Nguyen
    !  since: 1.0.0.f
    !
    !  <p>Check if the `input` string is a valid unsigned integer string
    !  of the numbering system with the radix that is defined by the
    !  value of the argument `base`.
    !
    !  <p>This subroutine ignores leading and trailing whitespaces.
    !  
    !  <p>This subroutine support numbering system with radix from 2 to 36.
    ! 
    !  <p>This subroutine considers an error to has occurred when the 
    !  value of the argument `base` is smaller than 2 or larger than 36.
    !  </p>
    pure subroutine isUnsignedBase(input, base, output, error)
        implicit none
        character(len=*), intent(in)   ::  input                        !! A string.
        integer(k_int32), intent(in)   ::  base                         !! An int32 value that define the radix.
        logical         , intent(out)  ::  output                       !! `.TRUE.` if the `input` string is a valid unsigned integer string of the defined numbering system.
                                                                        !! `.FALSE.` if the `input` string is empty(only spaces), 0 length, or is not a valid unsigned integer string of the defined numbering system.
        logical         , intent(out)  ::  error                        !! A value of `.TRUE.` on error. Otherwise, a value of `.FALSE.`.
        integer(k_int32)               ::  length, c, start
        output = .FALSE. ; error = .FALSE.
        
        if ( base < 2 .OR. base > 36 ) then 
            error = .TRUE.
            return
        end if

        length = len(input) ; start = 1
        
        do while ( length > 0 )
            if ( input(length:length) /= charspace ) exit
            length = length - 1
        end do
        do while ( start <= length )
            if ( input(start:start) /= charspace ) exit
            start = start + 1
        end do
        if ( start > length ) return
        
        do while (start <= length)
            c = ICHAR(input(start:start))

            if ( c > 96 ) then
                c = c - 87
            else if ( c > 64 ) then
                c = c - 55
            else 
                c = IEOR(c, czero32)
                if ( c > 9 ) return
            end if 

            if ( .NOT. (c < base) ) return
            start = start + 1
        end do
        
        output = .TRUE.
    end subroutine isUnsignedBase

    !| author: Khang Hoang Nguyen
    !  since: 1.0.0.f
    !
    !  <p>Check the `input` string to see if it holds an odd value with
    !  the assumption that the string is a valid decimal integer string.
    !
    !  <p>This subroutine ignores leading and trailing whitespaces.
    !  
    !  <p>This subroutine considers an error to has occurred if the `input`
    !  string is empty(only spaces) or is 0 length.
    !  </p>
    pure subroutine assumeIsOdd(input, output, error)
        implicit none
        character(len=*), intent(in)   ::  input                        !! A string.
        logical         , intent(out)  ::  output                       !! `.TRUE.` if the rightmost character(exclude trailing spaces) is an odd value, or otherwise, `.FALSE.`.
        logical         , intent(out)  ::  error                        !! A value of `.TRUE.` on error. Otherwise, a value of `.FALSE.`.
        integer(k_int32)               ::  length
        output = .FALSE. ; error = .FALSE.
        
        length = len(input)

        do while ( length > 0 )
            if ( input(length:length) /= charspace ) exit
            length = length - 1
        end do
        if ( length == 0 ) then
            error = .TRUE.
            return
        end if
        
        output = BTEST(ICHAR(input(length:length)), 0)
    end subroutine assumeIsOdd

    !| author: Khang Hoang Nguyen
    !  since: 1.0.0.f
    !
    !  <p>Check the `input` string to see if it holds an even value with
    !  the assumption that the string is a valid decimal integer string.
    !
    !  <p>This subroutine ignores leading and trailing whitespaces.
    !
    !  <p>This subroutine considers an error to has occurred if the
    !  `input` string is empty(only spaces) or is 0 length.
    !  </p>
    pure subroutine assumeIsEven(input, output, error)
        character(len=*), intent(in)   ::  input                        !! A string.
        logical         , intent(out)  ::  output                       !! `.TRUE.` if the rightmost character(exclude trailing spaces) is an even value, or otherwise, `.FALSE.`.
        logical         , intent(out)  ::  error                        !! A value of `.TRUE.` on error. Otherwise, a value of `.FALSE.`.
        integer(k_int32)               ::  length
        output = .FALSE. ; error = .FALSE.

        length = len(input)

        do while ( length > 0 )
            if ( input(length:length) /= charspace ) exit
            length = length - 1
        end do
        if ( length == 0 ) then 
            error = .TRUE.
            return
        end if
        
        output = .NOT. BTEST(ICHAR(input(length:length)), 0)
    end subroutine assumeIsEven

    !| author: Khang Hoang Nguyen
    !  since: 1.0.0.f
    !
    !  <p>Compare two strings with the assumption that both strings are
    !  valid decimal integers.
    !
    !  <p>This function ignores leading and trailing whitespaces.
    !
    !  <p>This function evaluates both, negative and positive values. This
    !  function may produce unwanted result if the previous is not met or
    !  if the number formatting is incorrect.
    !
    !  <p>This function considers empty strings as smallest. This function
    !  disregards leading zeroes. This function considers a string with
    !  only a '-' or a '+' sign as 0.
    !    
    !  <p>This function return an int32 value of 1 if the `firstString`
    !  is larger than the `secondString`, 0 if they are both equal, or
    !  -1 if the `firstString` is smaller than the `secondString`.
    !  </p>
    pure integer(k_int32) function assumeCompare(firstString, secondString) result(int32Out)
        implicit none
        character(len=*), intent(in)  ::  firstString                   !! A string to be compared to the string `secondString`.
        character(len=*), intent(in)  ::  secondString                  !! A string to be compared to the string `firstString`.
        integer(k_int32)              ::  str1length, str2length
        integer(k_int32)              ::  start1, start2, runlen1, runlen2, c1, c2
        logical                       ::  neg1, neg2
        int32Out = 0

        neg1 = .FALSE. ; neg2 = .FALSE.
        start1 = 1 ; start2 = 1
        str1length = len(firstString) ; str2length = len(secondString) 

        do while ( str1length > 0 )
            if ( firstString(str1length:str1length) /= charspace ) exit
            str1length = str1length - 1
        end do        
        do while ( start1 <= str1length )
            if ( firstString(start1:start1) /= charspace ) exit
            start1 = start1 + 1
        end do
        
        do while ( str2length > 0 )
            if ( secondString(str2length:str2length) /= charspace ) exit
            str2length = str2length - 1
        end do
        do while ( start2 <= str2length )
            if ( secondString(start2:start2) /= charspace ) exit
            start2 = start2 + 1
        end do

        runlen1 = str1length - start1
        runlen2 = str2length - start2 
        if ( runlen1 == -1 ) then
            if ( runlen2 == -1 ) return
            int32Out = -1
            return
        else if ( runlen2 == -1 ) then
            int32Out = 1
            return
        end if

        c1 = ICHAR(firstString(start1:start1))
        if( c1 == cneg32 ) then
            start1 = start1 + 1
            neg1 = .TRUE.
        else if ( c1 == cpos32 ) then
            start1 = start1 + 1
        end if
            
        c2 = ICHAR(secondString(start2:start2))
        if( c2 == cneg32 ) then
            start2 = start2 + 1
            neg2 = .TRUE.
        else if ( c2 == cpos32 ) then
            start2 = start2 + 1
        end if

        do while( start1 <= str1length )
            if ( firstString(start1:start1) /= charzero ) exit
            start1 = start1 + 1
        end do
        do while( start2 <= str2length )
            if ( secondString(start2:start2) /= charzero ) exit
            start2 = start2 + 1
        end do

        runlen1 = str1length - start1
        runlen2 = str2length - start2
        if ( runlen1 == -1 ) then
            if ( runlen2 == -1 ) return

            if ( neg2 .eqv. .TRUE. ) then
                int32Out = 1
            else
                int32Out = -1
            end if

            return
        else if ( runlen2 == -1 ) then
            if ( neg1 .eqv. .TRUE. ) then
                int32Out = -1
            else
                int32Out = 1
            end if

            return
        end if

        if ( neg1 .neqv. neg2 ) then
            if ( neg1 .eqv. .FALSE. ) then
                int32Out = 1
            else
                int32Out = -1
            end if

            return
        end if

        if ( neg1 .eqv. .TRUE. ) then
            if ( runlen1 /= runlen2 ) then
                if ( runlen1 > runlen2 ) then
                    int32Out = -1
                else
                    int32Out = 1
                end if
                
                return
            end if

            do while ( runlen1 > -1 )
                c1 = ICHAR(firstString(start1:start1))
                c2 = ICHAR(secondString(start2:start2))

                if ( c1 < c2 ) then
                    int32Out = 1
                    return
                end if
                if ( c1 > c2 ) then
                    int32Out = -1
                    return
                end if

                start1 = start1 + 1 ; start2 = start2 + 1
                runlen1 = runlen1 - 1
            end do
        else
            if ( runlen1 /= runlen2 ) then
                if ( runlen1 > runlen2 ) then
                    int32Out = 1
                else
                    int32Out = -1
                end if

                return
            end if

            do while( runlen1 > -1 )
                c1 = ICHAR(firstString(start1:start1))
                c2 = ICHAR(secondString(start2:start2))
                
                if ( c1 > c2 ) then
                    int32Out = 1
                    return
                end if
                if ( c1 < c2 ) then
                    int32Out = -1
                    return
                end if

                start1 = start1 + 1 ; start2 = start2 + 1
                runlen1 = runlen1 - 1
            end do
        end if
    end function assumeCompare

    !| author: Khang Hoang Nguyen
    !  since: 1.0.0.f
    !
    !  <p>Compare two strings with the assumption that both strings are
    !  valid integers of any radix between 2 and 36.
    !
    !  <p>This function ignores leading and trailing whitespaces.
    !
    !  <p>This function evaluates both, negative and positive values.
    !  This function will work, if both strings are integers of the same
    !  radix and the radix is from 2 to 36. This function may produce
    !  unwanted result if the previous are not met or if the formatting
    !  is incorrect.
    !
    !  <p>Digits of radix larger than 10 are 'a' to 'z' with
    !  'a'  being 10, and 'z' being 35. A is equal to a, Z is equal to
    !  z, and so forth.
    !
    !  <p>This function considers empty strings as smallest. This function
    !  disregards leading zeroes. This function considers a string with
    !  only a '-' or a '+' sign as 0.
    !  
    !  <p>This function return an int32 value of 1 if the `firstString`
    !  is larger than the `secondString`, 0 if they are both equal, or
    !  -1 if the `firstString` is smaller than the `secondString`.
    !  </p>
    pure integer(k_int32) function assumeCompareAllBase(firstString, secondString) result(int32Out)
        implicit none
        character(len=*), intent(in)  ::  firstString                   !! A string to be compared to the string `secondString`.
        character(len=*), intent(in)  ::  secondString                  !! A string to be compared to the string `firstString`.
        integer(k_int32)              ::  str1length, str2length
        integer(k_int32)              ::  start1, start2, runlen1, runlen2, c1, c2
        logical                       ::  neg1, neg2
        int32Out = 0

        neg1 = .FALSE. ; neg2 = .FALSE.
        start1 = 1 ; start2 = 1
        str1length = len(firstString) ; str2length = len(secondString)
        
        do while ( str1length > 0 )
            if ( firstString(str1length:str1length) /= charspace ) exit
            str1length = str1length - 1
        end do
        do while ( start1 <= str1length )
            if ( firstString(start1:start1) /= charspace ) exit
            start1 = start1 + 1
        end do
        
        do while ( str2length > 0 )
            if ( secondString(str2length:str2length) /= charspace ) exit
            str2length = str2length - 1
        end do
        do while ( start2 <= str2length )
            if ( secondString(start2:start2) /= charspace ) exit
            start2 = start2 + 1
        end do

        runlen1 = str1length - start1
        runlen2 = str2length - start2
        if ( runlen1 == -1 ) then
            if ( runlen2 == -1 ) return
            int32Out = -1
            return
        else if ( runlen2 == -1 ) then
            int32Out = 1
            return
        end if

        c1 = ICHAR(firstString(start1:start1))
        if( c1 == cneg32 ) then
            start1 = start1 + 1
            neg1 = .TRUE.
        else if ( c1 == cpos32 ) then
            start1 = start1 + 1
        end if
            
        c2 = ICHAR(secondString(start2:start2))
        if( c2 == cneg32 ) then
            start2 = start2 + 1
            neg2 = .TRUE.
        else if ( c2 == cpos32 ) then
            start2 = start2 + 1
        end if

        do while( start1 <= str1length )
            if ( firstString(start1:start1) /= charzero  ) exit
            start1 = start1 + 1
        end do 
        do while( start2 <= str2length ) 
            if ( secondString(start2:start2) /= charzero ) exit
            start2 = start2 + 1
        end do  
                      
        runlen1 = str1length - start1
        runlen2 = str2length - start2
        if ( runlen1 == -1 ) then
            if ( runlen2 == -1 ) return
            
            if ( neg2 .eqv. .TRUE. ) then
                int32Out = 1
            else
                int32Out = -1
            end if
            
            return
        else if ( runlen2 == -1 ) then
            if ( neg1 .eqv. .TRUE. ) then
                int32Out = -1
            else
                int32Out = 1
            end if

            return
        end if 

        if ( neg1 .neqv. neg2 ) then
            if ( neg1 .eqv. .FALSE. ) then
                int32Out = 1
            else 
                int32Out = -1
            end if

            return
        end if

        if ( neg1 .eqv. .TRUE. ) then
            if ( runlen1 /= runlen2 ) then
                if ( runlen1 > runlen2 ) then
                    int32Out = -1
                else
                    int32Out = 1
                end if

                return
            end if

            do while ( runlen1 > -1 ) 
                c1 = ICHAR(firstString(start1:start1))
                c2 = ICHAR(secondString(start2:start2))

                if ( c1 > 96) then
                    c1 = c1 - 87
                else if ( c1 > 64) then
                    c1 = c1 - 55
                else
                    c1 = IEOR(c1, czero32)
                end if
                
                if ( c2 > 96) then
                    c2 = c2 - 87
                else if ( c2 > 64) then
                    c2 = c2 - 55
                else 
                    c2 = IEOR(c2, czero32)
                end if

                if ( c1 < c2 ) then 
                    int32Out = 1
                    return 
                end if 
                if ( c1 > c2 ) then 
                    int32Out = -1
                    return
                end if
                
                start1 = start1 + 1 ; start2 = start2 + 1
                runlen1 = runlen1 - 1
            end do
        else 
            if ( runlen1 /= runlen2 ) then
                if ( runlen1 > runlen2 ) then
                    int32Out = 1
                else
                    int32Out = -1
                end if
                
                return
            end if

            do while( runlen1 > -1 )
                c1 = ICHAR(firstString(start1:start1))
                c2 = ICHAR(secondString(start2:start2))
                
                if ( c1 > 96) then
                    c1 = c1 - 87
                else if ( c1 > 64) then 
                    c1 = c1 - 55
                else 
                    c1 = IEOR(c1, czero32)
                end if
                
                if ( c2 > 96) then
                    c2 = c2 - 87
                else if ( c2 > 64) then
                    c2 = c2 - 55
                else 
                    c2 = IEOR(c2, czero32)
                end if
                
                if ( c1 > c2 ) then 
                    int32Out = 1
                    return
                end if
                if ( c1 < c2 ) then
                    int32Out = -1
                    return
                end if
                
                start1 = start1 + 1 ; start2 = start2 + 1
                runlen1 = runlen1 - 1
            end do
        end if
    end function assumeCompareAllBase
 
    !| author: Khang Hoang Nguyen
    !  since: 1.0.0.f
    !
    !  <p>Compare two strings as decimal integers. This subroutine 
    !  evaluates both, negative and positive values. There isn't a
    !  maximum length for the strings. Nonetheless, the strings can't be
    !  empty. 
    !
    !  <p>This subroutine ignores leading and trailing whitespaces.
    !
    !  <p>This subroutine disregards leading zeroes.
    !
    !  <p>Error code:<br />
    !  0 - no error<br />
    !  1 - empty string `firstString`(spaces only or 0 length)<br />
    !  2 - empty string `secondString`(spaces only or 0 length)<br />
    !  3 - bad format `firstString`<br />
    !  4 - bad format `secondString`
    !  </p>
    pure subroutine fnStringCompare(firstString, secondString, output, error) 
        implicit none
        character(len=*), intent(in)   ::  firstString                  !! A string to be compared to the string `secondString`.
        character(len=*), intent(in)   ::  secondString                 !! A string to be compared to the string `firstString`.
        integer(k_int32), intent(out)  ::  output                       !! An int32 value of 1 if the `firstString` is larger than the `secondString`, 0 if they are both equal, or -1 if the `firstString` is smaller than the `secondString`. This value may not be a correct value if an error has occurred during the comparison process.
        integer(k_int32), intent(out)  ::  error                        !! An int32 value of 0 if no error was encountered. Otherwise, a true error code.
        integer(k_int32)               ::  str1length, str2length
        integer(k_int32)               ::  c1, c2
        integer(k_int32)               ::  runlen1, runlen2, start1, start2
        logical                        ::  neg1, neg2
        output = 0 ; error = 0

        neg1 = .FALSE. ; neg2 = .FALSE.
        start1 = 1 ; start2 = 1
        str1length = len(firstString) ; str2length = len(secondString) 

        do while ( str1length > 0 )
            if ( firstString(str1length:str1length) /= charspace ) exit
            str1length = str1length - 1
        end do
        do while ( start1 <= str1length )
            if ( firstString(start1:start1) /= charspace ) exit
            start1 = start1 + 1
        end do
        
        do while ( str2length > 0 )
            if ( secondString(str2length:str2length) /= charspace ) exit
            str2length = str2length - 1
        end do
        do while ( start2 <= str2length )
            if ( secondString(start2:start2) /= charspace ) exit
            start2 = start2 + 1
        end do

        runlen1 = str1length - start1
        runlen2 = str2length - start2
        if ( runlen1 == -1 ) goto 1
        if ( runlen2 == -1 ) goto 2
        
        c1 = ICHAR(firstString(start1:start1))
        if( c1 == cneg32 ) then
            start1 = start1 + 1
            if ( start1 > str1length ) goto 3
            neg1 = .TRUE.
        else if( c1 == cpos32 ) then
            start1 = start1 + 1
            if ( start1 > str1length ) goto 3
        end if

        c2 = ICHAR(secondString(start2:start2))
        if( c2 == cneg32 ) then
            start2 = start2 + 1
            if ( start2 > str2length ) goto 4
            neg2 = .TRUE.
        else if( c2 == cpos32 ) then
            start2 = start2 + 1
            if ( start2 > str2length ) goto 4
        end if

        do while( start1 <= str1length )
            if ( firstString(start1:start1) /= charzero ) exit 
            start1 = start1 + 1
        end do 
        do while( start2 <= str2length )
            if ( secondString(start2:start2) /= charzero ) exit
            start2 = start2 + 1
        end do
        
        runlen1 = str1length - start1
        runlen2 = str2length - start2
        if ( runlen1 == -1 ) then
            if ( runlen2 == -1 ) then 
                output = 0
            else if ( neg2 .eqv. .TRUE. ) then 
                output = 1
            else 
                output = -1
            end if
            
            goto 555
        else if ( runlen2 == -1 ) then
            if ( neg1 .eqv. .TRUE. ) then
                output = -1
            else 
                output = 1
            end if
            
            goto 555
        end if
 
        if ( neg1 .neqv. neg2 ) then
            if ( neg1 .eqv. .FALSE. ) then 
                output = 1
            else 
                output = -1
            end if 
            
            goto 555
        end if
        
        if ( neg1 .eqv. .TRUE. ) then
            if ( runlen1 /= runlen2 ) then 
                if ( runlen1 > runlen2 ) then
                    output = -1
                else 
                    output = 1
                end if 
                
                goto 555
            end if
 
            do while ( runlen1 > -1 ) 
                c1 = IEOR(ICHAR(firstString(start1:start1)), czero32)
                c2 = IEOR(ICHAR(secondString(start2:start2)), czero32)
                if ( c1 > 9 ) goto 3
                if ( c2 > 9 ) goto 4
                                                
                if ( c1 < c2 ) then
                    output = 1
                    goto 555
                end if
                if ( c1 > c2 ) then
                    output = -1
                    goto 555
                end if
                
                start1 = start1 + 1 ; start2 = start2 + 1
                runlen1 = runlen1 - 1
            end do
        else 
            if ( runlen1 /= runlen2 ) then 
                if ( runlen1 > runlen2 ) then 
                    output = 1
                else 
                    output = -1
                end if
                
                goto 555
            end if

            do while( runlen1 > -1 )
                c1 = IEOR(ICHAR(firstString(start1:start1)), czero32)
                c2 = IEOR(ICHAR(secondString(start2:start2)), czero32)
                if ( c1 > 9 ) goto 3
                if ( c2 > 9 ) goto 4

                if ( c1 > c2 ) then
                    output = 1
                    goto 555
                end if
                if ( c1 < c2 ) then
                    output = -1
                    goto 555
                end if
                
                start1 = start1 + 1 ; start2 = start2 + 1
                runlen1 = runlen1 - 1
            end do
        end if

        555 continue
        do while ( start1 <= str1length )
            if ( IEOR(ICHAR(firstString(start1:start1)), czero32) > 9 ) goto 3
            start1 = start1 + 1
        end do 
        do while ( start2 <= str2length )
            if ( IEOR(ICHAR(secondString(start2:start2)), czero32) > 9 ) goto 4
            start2 = start2 + 1
        end do
        return
        
        1 continue
            error = 1
            return
        2 continue
            error = 2
            return
        3 continue
            error = 3
            return
        4 continue
            error = 4
    end subroutine fnStringCompare
    
    !| author: Khang Hoang Nguyen
    !  since: 1.0.0.f
    !
    !  <p>Compare two strings as integer strings of a numbering system 
    !  with the radix that is defined by the value of the argument `base`.
    !  There isn't a maximum length for the strings. Nonetheless, the
    !  strings can't be empty. 
    !
    !  <p>This subroutine support numbering system with radix from 2 to
    !  36. This subroutine evaluates both, negative and positive values.
    !
    !  <p>This subroutine ignores leading and trailing whitespaces.
    !
    !  <p>This subroutine disregards leading zeroes.
    !
    !  <p>Error code:<br />
    !  0 - no error<br />
    !  1 - empty `firstString`<br />
    !  2 - empty `secondString`<br />
    !  3 - bad format `firstString`<br />
    !  4 - bad format `secondString`<br />
    !  5 - `base` < 2 or `base` > 36
    !  </p>
    pure subroutine fnStringCompareAsBase(firstString, secondString, base, output, error)
        implicit none
        character(len=*), intent(in)   ::  firstString                  !! A string to be compared to the string `secondString`.
        character(len=*), intent(in)   ::  secondString                 !! A string to be compared to the string `firstString`.
        integer(k_int32), intent(in)   ::  base                         !! An int32 value that define the radix.
        integer(k_int32), intent(out)  ::  output                       !! An int32 value of 1 if the `firstString` is larger than the `secondString`, 0 if they are both equal, or -1 if the `firstString` is smaller than the `secondString`. This value may not be a correct value if an error has occurred during the comparison process.
        integer(k_int32), intent(out)  ::  error                        !! An int32 value of 0 if no error was encountered. Otherwise, a true error code.
        integer(k_int32)               ::  str1length, str2length
        integer(k_int32)               ::  c1, c2
        integer(k_int32)               ::  runlen1, runlen2, start1, start2
        logical                        ::  neg1, neg2
        if ( base < 2 .OR. base > 36 ) goto 5
        output = 0; error = 0

        neg1 = .FALSE. ; neg2 = .FALSE.
        start1 = 1 ; start2 = 1
        str1length = len(firstString) ; str2length = len(secondString) 

        do while ( str1length > 0 )
            if ( firstString(str1length:str1length) /= charspace ) exit
            str1length = str1length - 1
        end do
        do while ( start1 <= str1length )
            if ( firstString(start1:start1) /= charspace ) exit
            start1 = start1 + 1
        end do

        do while ( str2length > 0 )
            if ( secondString(str2length:str2length) /= charspace ) exit
            str2length = str2length - 1
        end do
        do while ( start2 <= str2length )
            if ( secondString(start2:start2) /= charspace ) exit
            start2 = start2 + 1
        end do

        runlen1 = str1length - start1
        runlen2 = str2length - start2
        if ( runlen1 == -1 ) goto 1
        if ( runlen2 == -1 ) goto 2
        
        c1 = ICHAR(firstString(start1:start1))
        if( c1 == cneg32 ) then
            start1 = start1 + 1
            if ( start1 > str1length ) goto 3
            neg1 = .TRUE.
        else if( c1 == cpos32 ) then
            start1 = start1 + 1
            if ( start1 > str1length ) goto 3
        end if

        c2 = ICHAR(secondString(start2:start2))
        if( c2 == cneg32 ) then
            start2 = start2 + 1
            if ( start2 > str2length ) goto 4
            neg2 = .TRUE.
        else if( c2 == cpos32 ) then
            start2 = start2 + 1
            if ( start2 > str2length ) goto 4
        end if

        do while( start1 <= str1length )
            if ( firstString(start1:start1) /= charzero ) exit
            start1 = start1 + 1
        end do 
        do while( start2 <= str2length )
            if ( secondString(start2:start2) /= charzero ) exit
            start2 = start2 + 1
        end do
        
        runlen1 = str1length - start1
        runlen2 = str2length - start2
        if ( runlen1 == -1 ) then
            if ( runlen2 == -1 ) then
                output = 0
            else if ( neg2 .eqv. .TRUE. ) then
                output = 1
            else
                output = -1
            end if
            
            goto 555
        else if ( runlen2 == -1 ) then
            if ( neg1 .eqv. .TRUE. ) then
                output = -1
            else 
                output = 1
            end if
            
            goto 555
        end if
 
        if ( neg1 .neqv. neg2 ) then
            if ( neg1 .eqv. .FALSE. ) then
                output = 1
            else
                output = -1
            end if
            
            goto 555
        end if
        
        if ( neg1 .eqv. .TRUE. ) then
            if ( runlen1 /= runlen2 ) then
                if ( runlen1 > runlen2 ) then
                    output = -1
                else
                    output = 1
                end if 
                
                goto 555
            end if
 
            do while ( runlen1 > -1 ) 
                c1 = ICHAR(firstString(start1:start1))
                c2 = ICHAR(secondString(start2:start2))
                
                if ( c1 > 96 ) then 
                    c1 = c1 - 87
                else if ( c1 > 64 ) then
                    c1 = c1 - 55
                else 
                    c1 = IEOR(c1, czero32)
                    if ( c1 > 9 ) goto 3
                end if                 
                if ( .NOT. (c1 < base) ) goto 3

                if ( c2 > 96 ) then 
                    c2 = c2 - 87
                else if ( c2 > 64 ) then 
                    c2 = c2 - 55
                else 
                    c2 = IEOR(c2, czero32)
                    if ( c2 > 9 ) goto 4
                end if
                if ( .NOT. (c2 < base) ) goto 4
                                                
                if ( c1 < c2 ) then
                    output = 1
                    goto 555
                end if 
                
                if ( c1 > c2 ) then
                    output = -1
                    goto 555
                end if
                
                start1 = start1 + 1 ; start2 = start2 + 1
                runlen1 = runlen1 - 1
            end do
        else 
            if ( runlen1 /= runlen2 ) then 
                if ( runlen1 > runlen2 ) then 
                    output = 1
                else 
                    output = -1
                end if 
                
                goto 555
            end if

            do while( runlen1 > -1 )
                c1 = ICHAR(firstString(start1:start1))
                c2 = ICHAR(secondString(start2:start2))
                
                if ( c1 > 96 ) then 
                    c1 = c1 - 87
                else if ( c1 > 64 ) then
                    c1 = c1 - 55
                else 
                    c1 = IEOR(c1, czero32)
                    if ( c1 > 9 ) goto 3
                end if
                if ( .NOT. (c1 < base) ) goto 3

                if ( c2 > 96 ) then 
                    c2 = c2 - 87
                else if ( c2 > 64 ) then 
                    c2 = c2 - 55
                else
                    c2 = IEOR(c2, czero32)
                    if ( c2 > 9 ) goto 4
                end if
                if ( .NOT. (c2 < base) ) goto 4
                
                if ( c1 > c2 ) then
                    output = 1
                    goto 555
                end if
                        
                if ( c1 < c2 ) then
                    output = -1
                    goto 555
                end if
                
                start1 = start1 + 1 ; start2 = start2 + 1
                runlen1 = runlen1 - 1
            end do
        end if

        555 continue
        do while ( start1 <= str1length )
            c1 = ICHAR(firstString(start1:start1))
            if ( c1 > 96 ) then 
                c1 = c1 - 87
            else if ( c1 > 64 ) then
                c1 = c1 - 55
            else 
                c1 = IEOR(c1, czero32)
                if ( c1 > 9 ) goto 3
            end if 
            
            if ( .NOT. (c1 < base) ) goto 3
            start1 = start1 + 1
        end do 
        do while ( start2 <= str2length )
            c2 = ICHAR(secondString(start2:start2))
            if ( c2 > 96 ) then
                c2 = c2 - 87
            else if ( c2 > 64 ) then
                c2 = c2 - 55
            else 
                c2 = IEOR(c2, czero32)
                if ( c2 > 9 ) goto 4
            end if
            
            if ( .NOT. (c2 < base) ) goto 4
            start2 = start2 + 1
        end do
        return
        
        1 continue
            error = 1
            return
        2 continue
            error = 2
            return
        3 continue
            error = 3
            return
        4 continue
            error = 4
            return 
        5 continue
            output = 0
            error = 5
    end subroutine fnStringCompareAsBase
end module fnNumberStringUtil