ZetCode

Algol 68 number formatting

last modified November 14, 2026

Algol 68 has no separate formatting library: the prelude returns text for a number with one of three procedures, and the program decides how wide the field should be. whole formats an integer, fixed formats a real with a fixed number of decimals, and float formats a real in a field whose mantissa and exponent share the available space.

This article uses those procedures to align a table, then converts integers to other bases, writes Roman numerals, and groups digits with commas. Every procedure returns a STRING, so a formatted number can be stored, concatenated, or written like any other text.

Integers with whole

whole(n, w) returns the integer n in a field of w characters. A positive width reserves one column for the sign and prints a plus for a positive number, a negative width left-aligns the digits and drops the plus, and a width of zero gives the digits with no padding at all.

widths.a68
BEGIN
  print(("[", whole(42, 6), "]", newline));
  print(("[", whole(-42, 6), "]", newline));
  print(("[", whole(42, -6), "]", newline));
  print(("[", whole(42, 1), "]", newline));
  print(("[", whole(0, 3), "]", newline));
  print(("[", whole(1234567, 0), "]", newline))
END

The brackets show the exact field: whole(42, 6) produces three spaces, a plus, and the two digits. A negative width moves the digits to the left edge of the field, which is what a left-aligned column needs. When the number and its sign need more columns than the width allows, the result is a row of asterisks, one for each requested column, and not an error.

$ a68g widths.a68
[   +42]
[   -42]
[    42]
[*]
[ +0]
[1234567]

Reals with fixed and float

fixed(r, w, d) writes a real with d digits after the decimal point in a field of w characters. The value is rounded, and a width of zero returns the number without padding and without a sign, which is convenient when the text is embedded in a sentence.

float(r, w, d, e) writes a real in a field of w characters with d decimals and an exponent field of e characters. The point floats to fill the field: a wider field extends the mantissa and lowers the exponent, so the same value can be written with different powers of ten.

reals.a68
BEGIN
  print(("[", fixed(3.14159, 8, 3), "]", newline));
  print(("[", fixed(3.14159, 0, 2), "]", newline));
  print(("[", fixed(2.5, 6, 0), "]", newline));
  print(("[", float(1234.5678, 12, 2, 3), "]", newline));
  print(("[", float(0.000123, 12, 4, 3), "]", newline))
END

The first call rounds 3.14159 to three decimals inside an eight-character field, and the second leaves the width to the number itself. The third call shows that rounding at zero decimals gives 3, so fixed is not a truncation. In the last two calls the last parameter is the width of the exponent, and the exponent is chosen so that the mantissa occupies the rest of the field: 0.000123 is the same number as 12.3000e-5.

$ a68g reals.a68
[  +3.142]
[3.14]
[    +3]
[+1234.57e +0]
[+12.3000e -5]

Another base

A base conversion reads the number from the right: the last digit is the remainder of a division by the base, the next digit is the remainder of the quotient, and the loop stops when the quotient reaches zero. Prepending each digit builds the result in the correct order.

base.a68
PROC to base = (INT n, INT b) STRING:
(
  STRING digits = "0123456789abcdef";
  IF n = 0 THEN
    "0"
  ELSE
    STRING s := "";
    INT m := n;
    WHILE m > 0 DO
      s := digits[m MOD b + 1] + s;
      m := m OVER b
    OD;
    s
  FI
);

BEGIN
  print(("255 in base  2: ", to base(255, 2), newline));
  print(("255 in base 16: ", to base(255, 16), newline));
  print(("1000 in base 8: ", to base(1000, 8), newline));
  print(("0 in base 2:    ", to base(0, 2), newline))
END

The digit string works for any base up to 16, and its lower-case letters keep the hexadecimal output readable next to the upper-case keywords of the language. The subscript m MOD b + 1 maps a remainder of 0 to the first character. Zero is a special case, because the loop would otherwise produce an empty string. The procedure assumes a non-negative number; a sign would have to be handled before the conversion.

$ a68g base.a68
255 in base  2: 11111111
255 in base 16: ff
1000 in base 8: 1750
0 in base 2:    0

Roman numerals

Roman numerals are written by subtracting the largest symbol that still fits, again and again, until nothing remains. The subtractive pairs such as CM for 900 are entries of their own, so the loop never has to special-case a digit.

roman.a68
PROC roman = (INT n) STRING:
(
  []INT vals = (1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1);
  []STRING syms = ("M", "CM", "D", "CD", "C", "XC", "L", "XL",
                   "X", "IX", "V", "IV", "I");
  STRING r := "";
  INT m := n;
  FOR i TO UPB vals DO
    WHILE m >= vals[i] DO
      r +:= syms[i];
      m -:= vals[i]
    OD
  OD;
  r
);

BEGIN
  [1:6] INT ns := (1, 4, 9, 14, 40, 1994);
  FOR i TO UPB ns DO
    print((whole(ns[i], 5), " = ", roman(ns[i]), newline))
  OD
END

The two rows are parallel: vals[i] is the value of syms[i]. The outer loop visits the values from largest to smallest, and the inner WHILE repeats a symbol as many times as it fits. Because the pairs 900, 400, 90, 40, 9, and 4 come before the smaller symbols, 1994 is built as 1000, 900, 90, and 4.

$ a68g roman.a68
   +1 = I
   +4 = IV
   +9 = IX
  +14 = XIV
  +40 = XL
+1994 = MCMXCIV

Aligned columns

A table is a row of structures whose fields are printed with fixed widths. The names need padding on the right, the numbers use whole with a positive width, and the prices use fixed with the same number of decimals. The header uses the same widths, so every column lines up.

table.a68
MODE ITEM = STRUCT(STRING name, INT qty, REAL price);

PROC pad right = (STRING s, INT w) STRING:
(
  STRING r := s;
  FOR i TO w - UPB s DO r +:= " " OD;
  r
);

PROC pad left = (STRING s, INT w) STRING:
(
  STRING r := "";
  FOR i TO w - UPB s DO r +:= " " OD;
  r + s
);

BEGIN
  []ITEM items = (
    ("bolts", 25, 0.35),
    ("nuts", 100, 0.12),
    ("washers", 8, 1.25));
  print((pad right("item", 10), pad left("qty", 6),
         pad left("price", 9), pad left("total", 10), newline));
  FOR i TO UPB items DO
    ITEM it = items[i];
    REAL total = REAL(qty OF it) * price OF it;
    print((pad right(name OF it, 10),
           whole(qty OF it, 6),
           fixed(price OF it, 9, 2),
           fixed(total, 10, 2), newline))
  OD
END

pad right appends spaces until the string has the requested width, and pad left prepends them. The header needs the second one because a positive width in whole includes the sign column, so whole(25, 6) and the word qty are aligned only when the word is pushed to the right as well. The total is computed from the quantity and the unit price with a widening of the integer quantity to REAL.

$ a68g table.a68
item         qty    price     total
bolts        +25    +0.35     +8.75
nuts        +100    +0.12    +12.00
washers       +8    +1.25    +10.00

Grouping digits

A number written for people often has a separator every three digits. The grouping loop walks the digits from the right, copies each one to the front of the result, and inserts a comma after every third digit. A descending loop is written with BY -1 and the lower bound as its TO part.

group.a68
PROC group = (INT n) STRING:
(
  STRING s = whole(n, 0);
  STRING r := "";
  INT count := 0;
  FOR i FROM UPB s BY -1 TO 1 DO
    r := s[i] + r;
    count +:= 1;
    IF count MOD 3 = 0 AND i > 1 THEN r := "," + r FI
  OD;
  r
);

BEGIN
  [1:4] INT ns := (1234567, 1000, 999, 0);
  FOR i TO UPB ns DO print((group(ns[i]), newline)) OD
END

The test i > 1 stops a comma from being placed before the first digit, so 1000 becomes 1,000 and not ,1,000. The counter starts at zero on each call, and the descending subscript never leaves the row because the loop ends at 1.

$ a68g group.a68
1,234,567
1,000
999
0

Next steps

printf and putf describe the same layout with format denotations, which is the subject of the formatted transput article. Values too large for an INT need the long modes of the big integers article, and strings explains the concatenation that the procedures here use to build their results.

Source

Revised Report on the Algorithmic Language Algol 68

The Algol 68 Genie project

In this article we have formatted integers and reals in fixed fields, converted integers to other bases, written Roman numerals, aligned a table, and grouped digits with commas.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Algol 68 tutorials.