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;
Format the signed or unsigned number n
in the specified base.
The value returned by
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;Format the signed or unsigned numbern
in the specified base.The value returned byLongInt
orLongUnsigned
never contains upper-case letters, never starts with an explicit base and underscore, and never ends with a trailingL
. For example, to render an unsigned numberN
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 numberx
.
\paragraph*{Overview.}
Style.Sci
gives scientific notation with fields padded to fixed widths, suitable for making a table. The parameterprec
specifies the number of digits after the decimal point---that is, the relative precision. \index{scientific notation}
Style.Fix
gives fixed point, withprec
once again specifying the number of digits after the decimal point---in this case, the absolute precision. The results ofStyle.Fix
have varying widths, but they will form a table if they are right-aligned (usingFmt.Pad
) in a sufficiently wide field. \index{fixed-point notation}
Style.Auto
is not intended for tables. It gives scientific notation with at mostprec
digits after the decimal point for numbers that are very big or very small. There may be fewer thanprec
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 mostprec+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
toTRUE
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 callToDecimal(x)
convertsx
to a floating-decimal number with automatic precision control~\cite{Steele,Gay}: Just enough digits are retained to distinguishx
from other values of typeT
, which implies that at mostT.MaxSignifDigits
are retained. TheReal
,LongReal
, andExtended
procedures format those digits as an appropriate string of characters. If the precision requested byprec
is higher than the automatic precision provided byToDecimal(x)
, they append trailing zeros. If the precision requested byprec
is lower, they roundToDecimal(x)
as necessary, obeying the current rounding mode. Because they exploit theerrorSign
field of the recordToDecimal(x)
in doing this rounding, they get the same result that roundingx
itself would give.As a consequence, setting
prec
higher thanT.MaxSignifDigits-1
inStyle.Sci
isn't very useful: The trailing digits of all of the resulting numbers will be zero. Settingprec
higher thanT.MaxSignifDigits-1
inStyle.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 eitherNormal
orDenormal
.In those cases,
Style.Sci
returns: a minus sign or blank, the leading nonzero digit ofx
, a decimal point,prec
more digits ofx
, a character'e'
, a minus sign or plus sign, andT.MaxExpDigits
of exponent (with leading zeros as necessary). Whenprec
is zero, the decimal point is omitted.
Style.Fix
returns: a minus sign if necessary, one or more digits, a decimal point, andprec
more digits---never any blanks. Whenprec
is zero, the decimal point is omitted.
Style.Auto
first formatsx
as inStyle.Sci
, using scientific notation withprec
digits after the decimal point. Call this intermediate resultR
.If the exponent of
R
is at least6
in magnitude,Style.Auto
leavesR
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 most5
in magnitude,Style.Auto
reformats the digits ofR
in fixed point, first deleting any trailing zeros and then adding leading or trailing zeros as necessary to bridge the gap from the digits ofR
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
andStyle.Auto
omit the blanks. InStyle.Sci
, if"Infinity"
doesn't fit,"Inf"
is used instead.Setting
literal
toTRUE
alters things as follows: Numbers that are rendered without a decimal point whenliteral
isFALSE
have a decimal point and one trailing zero appended to their digits. For the routinesFmt.LongReal
andFmt.Extended
, an exponent field ofd0
orx0
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 quotient0/0
. (Unfortunately, these quotient strings are so long that they may ruin the formatting ofStyle.Sci
tables whenprec
is small andliteral
isTRUE
.)
TYPE Align = {Left, Right}; PROCEDURE Pad( text: TEXT; length: CARDINAL; padChar: CHAR := ' '; align: Align := Align.Right): TEXT;IfText.Length(text) >= length
, thentext
is returned unchanged. Otherwise,text
is padded withpadChar
until it has the givenlength
. The text goes to the right or left, according toalign
.PROCEDURE F(fmt: TEXT; t1, t2, t3, t4, t5: TEXT := NIL) : TEXT;Usesfmt
as a format string. The result is a copy offmt
in which all format specifiers have been replaced, in order, by the text argumentst1
,t2
, etc.A format specifier contains a field width, alignment and one of two padding characters. The procedureF
evaluates the specifier and replaces it by the corresponding text argument padded as it would be by a call toPad
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 terminatings
.If the minus sign is present the alignment is
Align.Left
, otherwise it isAlign.Right
. The alignment corresponds to thealign
argument toPad
.The number specifies the field width (this corresponds to the
length
argument toPad
). 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 thepadChar
argument toPad
.It is a checked runtime error if
fmt
isNIL
or the number of format specifiers infmt
is not equal to the number of non-nil arguments toF
.Non-nil arguments to
F
must precede anyNIL
arguments; it is a checked runtime error if they do not.If
t1
tot5
are allNIL
andfmt
contains no format specifiers, the result isfmt
.Examples:
F("%s %s\n", "Hello", "World") 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"The following examples are legal but pointless: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;Similar toF
but accepts an array of text arguments. It is a checked runtime error if the number of format specifiers infmt
is not equal toNUMBER(texts)
or if any element oftexts
isNIL
. IfNUMBER(texts) = 0
andfmt
contains no format specifiers the result isfmt
.Example:
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 *>