Skip to content

Numbers ​

a terminal session on numbers: is_integer, is_real, is_number, to_number, read_number with its error, assigning numbers, hex, summing the numbers of a text

The kinds ​

use stringifor exports the portable kinds of PENF:

ParameterTypeBytes
I1Pinteger1
I2Pinteger2
I4Pinteger4
I8Pinteger8
R4Preal4
R8Preal8
R16Preal16
  • R16P is supported (in the assignment and in to_number) when the library is compiled with the -DPENF_R16P preprocessor flag.
  • I2P is not supported when the _NVF macro is defined (NVIDIA Fortran compatibility).

Is it a number? ​

f90
program number_check
!< Is it a number?
use stringifor
implicit none
type(string) :: s(6)
integer      :: i

s(1) = '42' ; s(2) = '-3.14' ; s(3) = '6.02e23' ; s(4) = '1.' ; s(5) = '12 apples' ; s(6) = '007'
print '(A)', 'string      number integer real digit'
do i = 1, size(s)
  print '(A,4(L1,6X))', s(i)%fill(width=12, right=.true., filling_char=' ')//'', &
        s(i)%is_number(), s(i)%is_integer(), s(i)%is_real(), s(i)%is_digit()
enddo
endprogram number_check
$ number_check
string      number integer real digit
42          T      T      F      T
-3.14       T      F      T      F
6.02e23     T      F      T      F
1.          T      F      T      F
12 apples   F      F      F      F
007         T      T      F      T
MethodTrue if the string is
is_integer([allow_spaces])an integer, with an optional sign
is_real([allow_spaces])a real: digits with a decimal point or an exponent (or both); an integer is not a real
is_number([allow_spaces])an integer or a real
is_digit()made of digits only

Leading and trailing blanks are accepted, unless allow_spaces=.false..

From a string to a number ​

f90
program number_cast
!< From a string to a number.
use stringifor
implicit none
type(string) :: s
real(R8P)    :: x
integer(I4P) :: n

s = '3.4e2'
if (s%is_number()) x = s%to_number(kind=1._R8P)   ! the kind of the argument selects the result
print '(F0.1)', x
s = '42'
if (s%is_integer()) n = s%to_number(kind=1_I4P)
print '(I0)', n + 1
x = s%to_number(kind=1._R8P)                      ! an integer string is a valid real
print '(F0.1)', x
endprogram number_cast
$ number_cast
340.0
43
42.0

to_number(kind) returns the number in the string. The argument selects the kind of the result and nothing else: its value is ignored, pass any constant of the wanted kind (1_I4P, 1._R8P). A cast to a real kind accepts a real or an integer string, a cast to an integer kind an integer string.

A plain decimal number (a sign, at most 18 significant digits, a decimal point, an exponent) is converted without the read statement, and correctly rounded: any integer in the range of the kind, and any R4P or R8P real, by one exact operation when the digits and the power of ten are exact in the kind and by the Eisel-Lemire algorithm otherwise. About 50 ns per number with gfortran, against about 500 ns for read. R16P and strings of more digits go through read, so those results are the compiler's (ifx and nvfortran are not always correctly rounded there).

The forms of a real ​

f90
program number_forms
!< The forms a real can take in a string: what Fortran writes, and the special values.
use stringifor
implicit none
type(string) :: fields(6)
real(R8P)    :: value
integer      :: iostat
integer      :: f

fields(1) = '1.5-101' ! as E and ES edit descriptors write a 3 digit exponent
fields(2) = '1.5d3'
fields(3) = 'inf'
fields(4) = '-Infinity'
fields(5) = 'nan'
fields(6) = '2026-11' ! not a number
do f = 1, size(fields)
  call fields(f)%read_number(value, iostat=iostat)
  print '(A10,1X,L1,1X,ES12.4)', fields(f)%chars(), iostat == 0, value
enddo
endprogram number_forms
$ number_forms
   1.5-101 T   1.5000-101
     1.5d3 T   1.5000E+03
       inf T     Infinity
 -Infinity T    -Infinity
       nan T          NaN
   2026-11 F          NaN
  • The exponent letter can be e, E, d or D. After a decimal point it can also be missing, with a sign and exactly 3 digits: 1.5-101 is what the E and ES edit descriptors write for a three digit exponent. read also takes 1-2 as 1e-2; here it is not a number, or 2026-11 would be one.
  • nan, nan(chars), inf and infinity, in any case and with a sign, are reals, for is_real and is_number too.
  • Past the range of the kind, the result is an infinity or a zero with iostat 0.

WARNING

On a string that does not hold a suitable number the result is 0 for an integer kind and a quiet NaN for a real kind: check with is_number or is_integer first to tell a parsed 0 from a failure, or use read_number.

With an error status ​

f90
program checked_cast
!< Numbers read from strings, with an error status.
use stringifor
implicit none
type(string)      :: fields(3)
real(R8P)         :: value
integer           :: iostat
character(len=99) :: iomsg
integer           :: f

fields(1) = '3.25'
fields(2) = 'n/a'
fields(3) = '1e3'
do f = 1, size(fields)
  call fields(f)%read_number(value, iostat=iostat, iomsg=iomsg)
  if (iostat == 0) then
    print '(A,F8.2)', fields(f)//' -> ', value
  else
    print '(A)', fields(f)//' -> error: '//trim(iomsg)
  endif
enddo
endprogram checked_cast
$ checked_cast
3.25 ->     3.25
n/a -> error: the string is not a number
1e3 ->  1000.00

read_number(number[, iostat][, iomsg]) is a subroutine: the kind is the one of number, and the failure is reported as by a read statement, iostat being 0 on success and positive otherwise, iomsg explaining it. On failure number gets the same value as to_number, 0 or NaN. It is elemental: on an array of strings, number and iostat are arrays.

From a number to a string ​

f90
program number_to_string
!< From a number to a string.
use stringifor
implicit none
type(string) :: s

s = 42
print '(A)', s//''
s = -127_I1P
print '(A)', s//''
s = 2.5_R4P
print '(A)', s//''
s = 1._R8P/3._R8P
print '(A)', s//''
s = 255
print '(A)', s%hex()//' '//s%hex(uppercase=.true.)
s = -1
print '(A)', s%hex(bits=16)//''                   ! two's complement on 16 bits
endprogram number_to_string
$ number_string
+42
-127
+0.250000000E+01
+0.33333333333333331E+000
ff FF
ffff
f90
program shortest_text
!< A real as the shortest text that reads back as exactly that number, against the assignment.
use stringifor
implicit none
real(R8P)    :: values(5)
type(string) :: full
type(string) :: short
integer      :: v

values = [0.1_R8P, 100._R8P, 1.e23_R8P, 1.5e-7_R8P, -2.5_R8P]
do v = 1, size(values)
  full = values(v)
  short = shortest(values(v))
  print '(A,1X,A)', full%chars(), short%chars()
enddo
if (short%to_number(1._R8P) == values(5)) print '(A)', 'it reads back as the same number'
endprogram shortest_text
$ shortest_text
+0.10000000000000001E+000 0.1
+0.10000000000000000E+003 100.0
+0.99999999999999992E+023 1e+23
+0.14999999999999999E-006 1.5e-07
-0.25000000000000000E+001 -2.5
it reads back as the same number
  • An integer is written with its sign; a real with its sign and all the significant digits of its kind, so the string holds the nearest representable value. For another layout, write the number with a format into a character.
  • shortest(number), a module procedure for R4P and R8P, returns the shortest text that reads back as exactly that number, the closest of them if more than one, in the layout of Python's repr. The assignment keeps its format.
  • hex([bits][, uppercase]) returns the hexadecimal representation of the integer in the string, or a string not allocated if it is not an integer. A negative number is in two's complement on bits bits (default 64); the number is truncated to its lowest bits bits.