The `Fmt`

interface provides procedures for formatting numbers and
other data as text.
\index{writing formatted data}
\index{formatted data!writing}

INTERFACEFmt ; IMPORT Word, Long, Real AS R, LongReal AS LR, Extended AS ER; PROCEDURE Bool(b: BOOLEAN): TEXT;

Format`b`

as {\tt "TRUE"} or {\tt "FALSE"}.

PROCEDURE Char(c: CHAR): TEXT;

Return a text containing the character`c`

.

TYPE Base = [2..16]; PROCEDURE Int(n: INTEGER; base: Base := 10): TEXT; PROCEDURE Unsigned(n: Word.T; base: Base := 16): TEXT;

The value returned byFormat the signed or unsigned number`n`

in the specified base.

`Int`

or `Unsigned`

never contains upper-case
letters, and it never starts with an explicit base and underscore.
For example, to render an unsigned number `N`

in hexadecimal as a
legal Modula-3 literal, you must write something like:
"16_" & Fmt.Unsigned(N, 16)PROCEDURE LongInt(n: LONGINT; base: Base := 10): TEXT; PROCEDURE LongUnsigned(n: Long.T; base: Base := 16): TEXT;The value returned byFormat the signed or unsigned number`n`

in the specified base.`LongInt`

or`LongUnsigned`

never contains upper-case letters, never starts with an explicit base and underscore, and never ends with a trailing`L`

. For example, to render an unsigned number`N`

in hexadecimal as a legal Modula-3 literal, you must write something like:"16_" & Fmt.Unsigned(N, 16) & "L"TYPE Style = {Sci, Fix, Auto}; PROCEDURE Real( x: REAL; style := Style.Auto; prec: CARDINAL := R.MaxSignifDigits - 1; literal := FALSE) : TEXT; PROCEDURE LongReal( x: LONGREAL; style := Style.Auto; prec: CARDINAL := LR.MaxSignifDigits - 1; literal := FALSE) : TEXT; PROCEDURE Extended( x: EXTENDED; style := Style.Auto; prec: CARDINAL := ER.MaxSignifDigits - 1; literal := FALSE) : TEXT;Format the floating-point number`x`

.

\paragraph*{Overview.}

`Style.Sci`

gives scientific notation with fields padded to fixed widths, suitable for making a table. The parameter`prec`

specifies the number of digits after the decimal point---that is, the relative precision. \index{scientific notation}

`Style.Fix`

gives fixed point, with`prec`

once again specifying the number of digits after the decimal point---in this case, the absolute precision. The results of`Style.Fix`

have varying widths, but they will form a table if they are right-aligned (using`Fmt.Pad`

) in a sufficiently wide field. \index{fixed-point notation}

`Style.Auto`

is not intended for tables. It gives scientific notation with at most`prec`

digits after the decimal point for numbers that are very big or very small. There may be fewer than`prec`

digits after the decimal point because trailing zeros are suppressed. For numbers that are neither too big nor too small, it formats the same significant digits---at most`prec+1`

of them---in fixed point, for greater legibility.All styles omit the decimal point unless it is followed by at least one digit.

Setting

`literal`

to`TRUE`

alters all styles as necessary to make the result a legal Modula-3 literal of the appropriate type.

\paragraph*{Accuracy.}

As discussed in the

`Float`

interface, the call`ToDecimal(x)`

converts`x`

to a floating-decimal number with automatic precision control~\cite{Steele,Gay}: Just enough digits are retained to distinguish`x`

from other values of type`T`

, which implies that at most`T.MaxSignifDigits`

are retained. The`Real`

,`LongReal`

, and`Extended`

procedures format those digits as an appropriate string of characters. If the precision requested by`prec`

is higher than the automatic precision provided by`ToDecimal(x)`

, they append trailing zeros. If the precision requested by`prec`

is lower, they round`ToDecimal(x)`

as necessary, obeying the current rounding mode. Because they exploit the`errorSign`

field of the record`ToDecimal(x)`

in doing this rounding, they get the same result that rounding`x`

itself would give.As a consequence, setting

`prec`

higher than`T.MaxSignifDigits-1`

in`Style.Sci`

isn't very useful: The trailing digits of all of the resulting numbers will be zero. Setting`prec`

higher than`T.MaxSignifDigits-1`

in`Style.Auto`

actually has no effect at all, since trailing zeros are suppressed.

\paragraph*{Details.}

We restrict ourselves at first to those cases where

`Class(x)`

is either`Normal`

or`Denormal`

.In those cases,

`Style.Sci`

returns: a minus sign or blank, the leading nonzero digit of`x`

, a decimal point,`prec`

more digits of`x`

, a character`'e'`

, a minus sign or plus sign, and`T.MaxExpDigits`

of exponent (with leading zeros as necessary). When`prec`

is zero, the decimal point is omitted.

`Style.Fix`

returns: a minus sign if necessary, one or more digits, a decimal point, and`prec`

more digits---never any blanks. When`prec`

is zero, the decimal point is omitted.

`Style.Auto`

first formats`x`

as in`Style.Sci`

, using scientific notation with`prec`

digits after the decimal point. Call this intermediate result`R`

.If the exponent of

`R`

is at least`6`

in magnitude,`Style.Auto`

leaves`R`

in scientific notation, but condenses it by omitting all blanks, plus signs, trailing zero digits, and leading zeros in the exponent. If this leaves no digits after the decimal point, the decimal point itself is omitted.If the exponent of

`R`

is at most`5`

in magnitude,`Style.Auto`

reformats the digits of`R`

in fixed point, first deleting any trailing zeros and then adding leading or trailing zeros as necessary to bridge the gap from the digits of`R`

to the unit's place.For example, assuming the current rounding mode is

`NearestElseEven`

:

Fmt.Real(1.287e6, Style.Auto, prec := 2) = "1.29e6" Fmt.Real(1.297e6, Style.Auto, prec := 2) = "1.3e6" Fmt.Real(1.297e5, Style.Auto, prec := 2) = "130000" Fmt.Real(1.297e-5, Style.Auto, prec := 2) = "0.000013" Fmt.Real(1.297e-6, Style.Auto, prec := 2) = "1.3e-6" Fmt.Real(9.997e5, Style.Auto, prec := 2) = "1e6" Fmt.Real(9.997e-6, Style.Auto, prec := 2) = "0.00001"`Style.Sci`

handles zero by replacing the entire exponent field by blanks, for example: {\tt " 0.00 "}.`Style.Fix`

renders zero with all digits zero; for example,`"0.00"`

.`Style.Auto`

renders zero as`"0"`

. On IEEE implementations, the value minus zero is rendered as a negative number.Also on IEEE implementations,

`Style.Sci`

formats infinities or NaN's with a minus sign or blank, the string`"Infinity"`

or`"NaN"`

, and enough trailing blanks to get the correct overall width.`Style.Fix`

and`Style.Auto`

omit the blanks. In`Style.Sci`

, if`"Infinity"`

doesn't fit,`"Inf"`

is used instead.Setting

`literal`

to`TRUE`

alters things as follows: Numbers that are rendered without a decimal point when`literal`

is`FALSE`

have a decimal point and one trailing zero appended to their digits. For the routines`Fmt.LongReal`

and`Fmt.Extended`

, an exponent field of`d0`

or`x0`

is appended to numbers in fixed point and`'d'`

or`'x'`

is used, rather than`'e'`

, to introduce the exponents of numbers in scientific notation. On IEEE implementations, the string`"Infinity"`

is replaced by`"1.0/0.0"`

,`"1.0d0/0.0d0"`

, or`"1.0x0/0.0x0"`

as appropriate, and`"NaN"`

is similarly replaced by a representation of the quotient`0/0`

. (Unfortunately, these quotient strings are so long that they may ruin the formatting of`Style.Sci`

tables when`prec`

is small and`literal`

is`TRUE`

.)

TYPE Align = {Left, Right}; PROCEDURE Pad( text: TEXT; length: CARDINAL; padChar: CHAR := ' '; align: Align := Align.Right): TEXT;If`Text.Length(text) >= length`

, then`text`

is returned unchanged. Otherwise,`text`

is padded with`padChar`

until it has the given`length`

. The text goes to the right or left, according to`align`

.PROCEDURE F(fmt: TEXT; t1, t2, t3, t4, t5: TEXT := NIL) : TEXT;A format specifier contains a field width, alignment and one of two padding characters. The procedureUses`fmt`

as a format string. The result is a copy of`fmt`

in which all format specifiers have been replaced, in order, by the text arguments`t1`

,`t2`

, etc.`F`

evaluates the specifier and replaces it by the corresponding text argument padded as it would be by a call to`Pad`

with the specified field width, padding character and alignment.The syntax of a format specifier is:

%[-]{0-9}sthat is, a percent character followed by an optional minus sign, an optional number and a compulsory terminating`s`

.If the minus sign is present the alignment is

`Align.Left`

, otherwise it is`Align.Right`

. The alignment corresponds to the`align`

argument to`Pad`

.The number specifies the field width (this corresponds to the

`length`

argument to`Pad`

). If the number is omitted it defaults to zero.If the number is present and starts with the digit

`0`

the padding character is`'0'`

; otherwise it is the space character. The padding character corresponds to the`padChar`

argument to`Pad`

.It is a checked runtime error if

`fmt`

is`NIL`

or the number of format specifiers in`fmt`

is not equal to the number of non-nil arguments to`F`

.Non-nil arguments to

`F`

must precede any`NIL`

arguments; it is a checked runtime error if they do not.If

`t1`

to`t5`

are all`NIL`

and`fmt`

contains no format specifiers, the result is`fmt`

.Examples:

F("%s %s\n", "Hello", "World")The following examples are legal but pointless:returns"Hello World\n". F("%s", Int(3))returns"3" F("%2s", Int(3))returns" 3" F("%-2s", Int(3))returns"3 " F("%02s", Int(3))returns"03" F("%-02s", Int(3))returns"30" F("%s", "%s")returns"%s" F("%s% tax", Int(3))returns"3% tax"F("%-s", Int(3))returns"3" F("%0s", Int(3))returns"3" F("%-0s", Int(3))returns"3"PROCEDURE FN(fmt: TEXT; READONLY texts: ARRAY OF TEXT) : TEXT;Example:Similar to`F`

but accepts an array of text arguments. It is a checked runtime error if the number of format specifiers in`fmt`

is not equal to`NUMBER(texts)`

or if any element of`texts`

is`NIL`

. If`NUMBER(texts) = 0`

and`fmt`

contains no format specifiers the result is`fmt`

.

FN("%s %s %s %s %s %s %s", ARRAY OF TEXT{"Too", "many", "arguments", "for", "F", "to", "handle"})returns {\tt "Too many arguments for F to handle"}.END Fmt. <*PRAGMA SPEC *> <*SPEC Bool(b) ENSURES RES # NIL *> <*SPEC Char(c) ENSURES RES # NIL *> <*SPEC Int(n, base) ENSURES RES # NIL *> <*SPEC Unsigned(n, base) ENSURES RES # NIL *> <*SPEC Real(x, style, prec, literal) ENSURES RES # NIL *> <*SPEC LongReal(x, style, prec, literal) ENSURES RES # NIL *> <*SPEC Extended(x, style, prec, literal) ENSURES RES # NIL *>