pic_format Module

Deterministic, compiler independent formatting of reals and integers.

Why this module contains no write statement

Not even an internal write with a fully explicit edit descriptor is used for reals, because two things remain processor dependent even then:

  • the rounding mode applied to the last emitted digit (ROUND= defaults to PROCESSOR_DEFINED, and the ROUND= specifier itself is not uniformly supported), and
  • whether an optional leading zero is written for Fw.d output of a magnitude below one (0.25 versus .25).

Instead the exact decimal expansion of the IEEE value is generated here with integer arithmetic only, and then rounded explicitly. Integers are delegated to to_string from pic_strings, whose no-format integer path is already a hand rolled digit by digit conversion that accumulates in negative space so that -huge()-1 converts without overflow.

Rounding

Reals are rounded round half to even against the exact binary value of the argument, not against the decimal literal that was written in the source. This is the only rounding rule that can be reproduced exactly, and it is occasionally surprising: 0.15_dp is really 0.1499999999999999944..., so to_string_fixed(0.15_dp, 1) is "0.1", not "0.2". A tie is only a tie when the binary value is exactly halfway, which happens only for dyadic rationals such as 0.25 or 2.5.

Special values

Canonical spellings, identical on every compiler and in every routine: NaN of any payload or sign renders as NaN, positive infinity as Inf and negative infinity as -Inf.

The sign of a non-zero argument is always preserved, including for values that round to zero: to_string_fixed(-1.0e-9_dp, 2) is "-0.00".

Zero is always rendered without a sign, so to_string_fixed(-0.0_dp, 2) is "0.00", not "-0.00". Distinguishing the signed zeros is processor dependent in Fortran (F2018 16.9.165 makes SIGN return |A| where the processor cannot tell them apart) and Intel and AOCC do not tell them apart under their default floating point model. Rendering a sign here would make the output depend on the compiler and its flags, which is exactly what this module exists to prevent, so negative zero is canonicalised to positive zero instead. This matches pic_array_hash, which collapses the signed zeros to one digest for the same reason.


Uses

  • module~~pic_format~~UsesGraph module~pic_format pic_format module~pic_strings pic_strings module~pic_format->module~pic_strings module~pic_types pic_types module~pic_format->module~pic_types module~pic_strings->module~pic_types iso_c_binding iso_c_binding module~pic_strings->iso_c_binding module~pic_ascii pic_ascii module~pic_strings->module~pic_ascii module~pic_optional_value pic_optional_value module~pic_strings->module~pic_optional_value module~pic_string_type pic_string_type module~pic_strings->module~pic_string_type module~pic_types->iso_c_binding iso_fortran_env iso_fortran_env module~pic_types->iso_fortran_env module~pic_ascii->module~pic_types module~pic_optional_value->module~pic_types module~pic_string_type->module~pic_types module~pic_string_type->module~pic_ascii module~pic_string_type->module~pic_optional_value

Variables

Type Visibility Attributes Name Initial
character(len=*), public, parameter :: PIC_FORMAT_INF = "Inf"

Canonical rendering of positive infinity.

integer(kind=default_int), public, parameter :: PIC_FORMAT_MAX_DECIMALS = 200_default_int

Largest number of decimals honoured by to_string_fixed; larger requests are clamped to this value so that the output stays bounded.

integer(kind=default_int), public, parameter :: PIC_FORMAT_MAX_SIG_DIGITS = 40_default_int

Largest number of significant digits honoured by to_string_sci; larger requests are clamped to this value.

character(len=*), public, parameter :: PIC_FORMAT_NAN = "NaN"

Canonical rendering of a not a number value.

character(len=*), public, parameter :: PIC_FORMAT_NEG_INF = "-Inf"

Canonical rendering of negative infinity.

character(len=*), private, parameter :: ALL_DIGITS = "0123456789"

Digit lookup table, mirroring the table driven integer conversion in pic_strings_to_strings. Position d + 1 holds the character for the value d, so the position of a digit is also the character of its successor, which is what the carry propagation needs.

integer(kind=int64), private, parameter :: LIMB_BASE = 1000000000_int64

Radix of the big integer scratch representation. Fixed width int64 on purpose: the limb products must not change size with the build default integer kind.

integer(kind=default_int), private, parameter :: LIMB_DIGITS = 9_default_int

Decimal digits carried in one big integer limb.

integer(kind=default_int), private, parameter :: MAX_DIGITS = MAX_LIMBS*LIMB_DIGITS

Decimal digit capacity of the scratch big integer.

integer(kind=default_int), private, parameter :: MAX_LIMBS = 100_default_int

Limb capacity. The widest intermediate is mantissa * 5**1126 for the smallest subnormal double, which needs 803 decimal digits, so 100 limbs (900 digits) is comfortably sufficient.

character(len=*), private, parameter :: ODD_DIGITS = "13579"

Used to test the parity of the last retained digit for half to even.

integer(kind=default_int), private, parameter :: WORK_LEN = MAX_DIGITS+PIC_FORMAT_MAX_DECIMALS+2_default_int

Length of the digit string scratch buffers.


Interfaces

public interface to_string_fixed

Render a real in fixed point notation with an exact number of decimals. Fixed point rendering, never an exponent.

The result carries exactly decimals digits after the decimal point, correctly rounded round half to even from the exact binary value. A leading 0 is always written for magnitudes below one, and decimals equal to zero produces no trailing decimal point. decimals is clamped to the range [0, PIC_FORMAT_MAX_DECIMALS].

  • private pure function to_string_fixed_sp(value, decimals) result(string)

    Fixed point rendering of a real(sp) value.

    Widening to real(dp) is exact for every finite real(sp) value and preserves NaN and both infinities, so the double precision expansion produces the same digits.

    Arguments

    Type IntentOptional Attributes Name
    real(kind=sp), intent(in) :: value
    integer(kind=default_int), intent(in) :: decimals

    Return Value character(len=:), allocatable

  • private pure function to_string_fixed_dp(value, decimals) result(string)

    Fixed point rendering of a real(dp) value.

    Arguments

    Type IntentOptional Attributes Name
    real(kind=dp), intent(in) :: value
    integer(kind=default_int), intent(in) :: decimals

    Return Value character(len=:), allocatable

public interface to_string_sci

Render a real in scientific notation with a canonical exponent field. Scientific rendering, canonical form [-]D[.DDD]e[+-]NN.

Exactly sig_digits significant digits are produced, one before the decimal point and the rest after it; sig_digits equal to one yields no decimal point. The exponent letter is always a lowercase e, the exponent sign is always present, and the exponent magnitude is written with a minimum of two digits, zero padded on the left (three digits are used when the magnitude requires them, as for 1.0e-300). Zero is rendered with an exponent of zero. sig_digits is clamped to the range [1, PIC_FORMAT_MAX_SIG_DIGITS].

  • private pure function to_string_sci_sp(value, sig_digits) result(string)

    Scientific rendering of a real(sp) value. See to_string_fixed_sp for why the widening to real(dp) is lossless.

    Arguments

    Type IntentOptional Attributes Name
    real(kind=sp), intent(in) :: value
    integer(kind=default_int), intent(in) :: sig_digits

    Return Value character(len=:), allocatable

  • private pure function to_string_sci_dp(value, sig_digits) result(string)

    Scientific rendering of a real(dp) value.

    Arguments

    Type IntentOptional Attributes Name
    real(kind=dp), intent(in) :: value
    integer(kind=default_int), intent(in) :: sig_digits

    Return Value character(len=:), allocatable

public interface to_string_width

Render an integer right aligned in a field of the requested width. Right aligned integer rendering, blank padded on the left.

The value is never truncated and never replaced by asterisks: if it does not fit in width characters the field is widened and the full number is returned, so the result length is max(width, needed). A width of zero or less simply returns the unpadded number.

  • private pure function to_string_width_i32(value, width) result(string)

    Right aligned rendering of an integer(int32) value.

    Arguments

    Type IntentOptional Attributes Name
    integer(kind=int32), intent(in) :: value
    integer(kind=default_int), intent(in) :: width

    Return Value character(len=:), allocatable

  • private pure function to_string_width_i64(value, width) result(string)

    Right aligned rendering of an integer(int64) value.

    Arguments

    Type IntentOptional Attributes Name
    integer(kind=int64), intent(in) :: value
    integer(kind=default_int), intent(in) :: width

    Return Value character(len=:), allocatable


Functions

private pure function assemble_fixed(negative, rounded, n_rounded, point_pos, dec) result(string)

Place the decimal point in an already rounded digit string.

Read more…

Arguments

Type IntentOptional Attributes Name
logical, intent(in) :: negative
character(len=*), intent(in) :: rounded
integer(kind=default_int), intent(in) :: n_rounded
integer(kind=default_int), intent(in) :: point_pos
integer(kind=default_int), intent(in) :: dec

Return Value character(len=:), allocatable

private pure function exponent_field(exp10) result(string)

Canonical exponent field: sign, then at least two zero padded digits.

Arguments

Type IntentOptional Attributes Name
integer(kind=default_int), intent(in) :: exp10

Return Value character(len=:), allocatable

private pure function is_negative(value) result(negative)

True for negative values. The result for negative zero is processor dependent, so callers must not rely on it: every caller here tests value == 0 first and drops the sign for zero. Not valid for NaN, which the callers have already filtered out.

Arguments

Type IntentOptional Attributes Name
real(kind=dp), intent(in) :: value

Return Value logical

private pure function pad_left(text, width) result(string)

Blank pad on the left, widening rather than truncating.

Arguments

Type IntentOptional Attributes Name
character(len=*), intent(in) :: text
integer(kind=default_int), intent(in) :: width

Return Value character(len=:), allocatable

private pure function to_string_fixed_dp(value, decimals) result(string)

Fixed point rendering of a real(dp) value.

Arguments

Type IntentOptional Attributes Name
real(kind=dp), intent(in) :: value
integer(kind=default_int), intent(in) :: decimals

Return Value character(len=:), allocatable

private pure function to_string_fixed_sp(value, decimals) result(string)

Fixed point rendering of a real(sp) value.

Read more…

Arguments

Type IntentOptional Attributes Name
real(kind=sp), intent(in) :: value
integer(kind=default_int), intent(in) :: decimals

Return Value character(len=:), allocatable

private pure function to_string_sci_dp(value, sig_digits) result(string)

Scientific rendering of a real(dp) value.

Arguments

Type IntentOptional Attributes Name
real(kind=dp), intent(in) :: value
integer(kind=default_int), intent(in) :: sig_digits

Return Value character(len=:), allocatable

private pure function to_string_sci_sp(value, sig_digits) result(string)

Scientific rendering of a real(sp) value. See to_string_fixed_sp for why the widening to real(dp) is lossless.

Arguments

Type IntentOptional Attributes Name
real(kind=sp), intent(in) :: value
integer(kind=default_int), intent(in) :: sig_digits

Return Value character(len=:), allocatable

private pure function to_string_width_i32(value, width) result(string)

Right aligned rendering of an integer(int32) value.

Arguments

Type IntentOptional Attributes Name
integer(kind=int32), intent(in) :: value
integer(kind=default_int), intent(in) :: width

Return Value character(len=:), allocatable

private pure function to_string_width_i64(value, width) result(string)

Right aligned rendering of an integer(int64) value.

Arguments

Type IntentOptional Attributes Name
integer(kind=int64), intent(in) :: value
integer(kind=default_int), intent(in) :: width

Return Value character(len=:), allocatable


Subroutines

private pure subroutine bd_mul_pow(limb, n_limb, base, power)

Multiply the big integer in place by base**power.

Read more…

Arguments

Type IntentOptional Attributes Name
integer(kind=int64), intent(inout) :: limb(:)
integer(kind=default_int), intent(inout) :: n_limb
integer(kind=int64), intent(in) :: base
integer(kind=default_int), intent(in) :: power

private pure subroutine bd_mul_small(limb, n_limb, factor)

Multiply the big integer in place by a factor smaller than LIMB_BASE.

Read more…

Arguments

Type IntentOptional Attributes Name
integer(kind=int64), intent(inout) :: limb(:)
integer(kind=default_int), intent(inout) :: n_limb
integer(kind=int64), intent(in) :: factor

private pure subroutine bd_set(limb, n_limb, value)

Initialise the big integer from a strictly positive int64.

Arguments

Type IntentOptional Attributes Name
integer(kind=int64), intent(out) :: limb(:)
integer(kind=default_int), intent(out) :: n_limb
integer(kind=int64), intent(in) :: value

private pure subroutine bd_to_digits(limb, n_limb, exact, n_exact)

Write the big integer out as a decimal digit string, no leading zeros.

Arguments

Type IntentOptional Attributes Name
integer(kind=int64), intent(in) :: limb(:)
integer(kind=default_int), intent(in) :: n_limb
character(len=*), intent(out) :: exact
integer(kind=default_int), intent(out) :: n_exact

private pure subroutine expand_exact(value, exact, n_exact, point_pos)

Exact decimal expansion of a finite, strictly positive value.

Read more…

Arguments

Type IntentOptional Attributes Name
real(kind=dp), intent(in) :: value
character(len=*), intent(out) :: exact
integer(kind=default_int), intent(out) :: n_exact
integer(kind=default_int), intent(out) :: point_pos

private pure subroutine increment_digits(rounded, n_rounded, bump)

Add one to a decimal digit string, growing it on carry out.

Arguments

Type IntentOptional Attributes Name
character(len=*), intent(inout) :: rounded
integer(kind=default_int), intent(inout) :: n_rounded
integer(kind=default_int), intent(out) :: bump

private pure subroutine limb_to_digits(value, chunk)

Expand one limb into exactly LIMB_DIGITS zero padded characters, table driven the same way pic_strings converts integers.

Arguments

Type IntentOptional Attributes Name
integer(kind=int64), intent(in) :: value
character(len=*), intent(out) :: chunk

private pure subroutine round_decimal(exact, n_exact, n_keep, rounded, n_rounded, bump)

Round an exact digit string to n_keep leading digits, half to even.

Read more…

Arguments

Type IntentOptional Attributes Name
character(len=*), intent(in) :: exact
integer(kind=default_int), intent(in) :: n_exact
integer(kind=default_int), intent(in) :: n_keep
character(len=*), intent(out) :: rounded
integer(kind=default_int), intent(out) :: n_rounded
integer(kind=default_int), intent(out) :: bump

private pure subroutine special_text(value, string, special)

Detect NaN and the infinities and return their canonical spelling.

Read more…

Arguments

Type IntentOptional Attributes Name
real(kind=dp), intent(in) :: value
character(len=:), intent(out), allocatable :: string
logical, intent(out) :: special