Patch for new whole and dependencies

chris hermansen clhermansen@gmail.com
Mon Jul 13 02:08:44 GMT 2026


>From e013db446558771f3607223c2529291ef8fddd10 Mon Sep 17 00:00:00 2001
From: Chris Hermansen <chris.hermansen@nuevaconsulting.com>
Date: Sun, 12 Jul 2026 18:02:04 -0700
Subject: [PATCH] New version of proc whole and dependencies

---
 libga68/standard.a68.in | 237 ++++++++++++++++++++++++++++++++++------
 1 file changed, 201 insertions(+), 36 deletions(-)

diff --git a/libga68/standard.a68.in b/libga68/standard.a68.in
index 5246679fa9f..05324d74a4d 100644
--- a/libga68/standard.a68.in
+++ b/libga68/standard.a68.in
@@ -57,29 +57,162 @@ def
                      {L} real
                      {reti {,}}
                   );
+    { Proposed replacement for whole() as provided in RR p.159.
+
+      Several factors motivated me to propose this replacement for the code
+      provided by the RR:
+
+      This code works on the number from left to right, rather than right to
+      left as seen in the RR.  Working from left to right in this way requires
+      either:
+      - processing all 10 digits, meaning worthless effort for every leading
+        zero digit, or
+      - determining how many significant digits there are, which means some
+        lookup code (I believe this is faster but I haven't benchmarked it at
+        this point)
+
+      By determining beforehand how many significant digits there are, we
+      can allocate a working buffer of exactly the right length, which
+      eliminates:
+      - the (expensive) string concatenation approach used in the RR proc
+        subwhole
+      - the need to keep in mind a fixed-length buffer should GNU
Algol 68 begin
+        to support longer integers (128, 256, whatever)
+      - the need to trim the fixed-length buffer once the converted integer is
+        in place
+
+      Finally, working left to right
+      - eliminates the need to apply ABS to the number to be converted, thereby
+        eliminating the need to depend on  what ABS (- max_int - 1) produces
+          (note the RR DOES NOT deal with this issue)
+        - replaces one (expensive) integer division with one (less expensive)
+          integer multiplication using a looked-up power of 10
+
+      I have also reviewed van Vliet's proposed whole and subwhole and do not
+      find them to share the same advantages, though they only convert the
+      integer when it is non-zero.
+
+      Back in the 1980s and 1990s, I maintained a relatively large suite of
+      interrelated Pascal programs.  At one point I needed to develop some
+      specific output routines not served by the Pascal compiler I was using at
+      the time.  In those dark days, computer time was expensive and code
+      efficiency was mandatory; I ended up stumbling on the idea of converting
+      left-to-right to eliminate one expensive integer division.
+
+      Seeing this nice article on the same kind of conversion, I was
reminded of
+      "the good old days":
+
+      https://towardsdatascience.com/34-faster-integer-to-string-conversion-algorithm-c72453d25352
+    }
+
+    {
+      The WHOLEDIGITS operator is used to determine the number of
decimal digits in a
+      number.  We convert the operand to long long and make it negative if
+      necessary, to allow for twos-complement minimum (negative) integer.
+
+      Note that if long long ever becomes (say) 128 this lookup needs to be
+      lengthened.  At some point, it may be worthwhile to have a
separate lookup
+      for each length (maybe even now).
+    }
+
+    op {ℵ₀} WHOLEDIGITS = (union(
+        {iter L {long long } {long } {} {short } {short short }}
+        {L}int
+        {reti {,}}
+      ) number) int:
+    begin
+      long long int work =
+      case number in
+      {iter L {long long } {long } {} {short } {short short }}
+      {iter K {} {LENG } {LENG LENG } {LENG LENG LENG } {LENG LENG LENG LENG }}
+      ({L}int x):
+        {K}(x > {L}0 | -x | x)
+      {reti {,}}
+      esac;
+      if   work > - long long 10 then 1
+      elif work > - long long 100 then 2
+      elif work > - long long 1 000 then 3
+      elif work > - long long 10 000 then 4
+      elif work > - long long 100 000 then 5
+      elif work > - long long 1 000 000 then 6
+      elif work > - long long 10 000 000 then 7
+      elif work > - long long 100 000 000 then 8
+      elif work > - long long 1 000 000 000 then 9
+      elif work > - long long 10 000 000 000 then 10
+      elif work > - long long 100 000 000 000 then 11
+      elif work > - long long 1 000 000 000 000 then 12
+      elif work > - long long 10 000 000 000 000 then 13
+      elif work > - long long 100 000 000 000 000 then 14
+      elif work > - long long 1 000 000 000 000 000 then 15
+      elif work > - long long 10 000 000 000 000 000 then 16
+      elif work > - long long 100 000 000 000 000 000 then 17
+      elif work > - long long 1 000 000 000 000 000 000 then 18
+      else 19 fi
+    end { WHOLEDIGITS };
+
+    { The whole_powers_of_10 row is used to look up the appropriate power of 10
+      for each integer division required to select the leading in the
conversion
+      process and for each integer multiplication to eliminate the leading
+      digit.
+
+      Note that if long long ever becomes (say) 128 this table needs to be
+      lengthened.  At some point, it may be worthwhile to have a
separate lookup
+      for each length (maybe even now).
+    }
+
+    []long long int {ℵ₀} whole_powers_of_10 = []long long int (long long 1,
+        long long 10, long long 100, long long 1 000, long long 10 000,
+        long long 100 000, long long 1 000 000, long long 10 000 000,
+        long long 100 000 000, long long 1 000 000 000,
+        long long 10 000 000 000, long long 100 000 000 000,
+        long long 1 000 000 000 000, long long 10 000 000 000 000,
+        long long 100 000 000 000 000, long long 1 000 000 000 000 000,
+        long long 10 000 000 000 000 000, long long 100 000 000 000 000 000,
+        long long 1 000 000 000 000 000 000);
+
+    { proc whole checks for a too-small width, returning a string of the error
+      character if so; it left-pads the result with blanks as necessary and the
+      sign as necessary; and it relies on subwhole to do the actual digit
+      conversion.  The [] result returned is either exactly the number of chars
+      needed (width = 0) to hold the converted integer and negative sign if < 0
+      or width characters (width ≠ 0).
+    }

     pub proc whole = (Number v, int width) string:
-       case v in
-          {iter L  {short short}  {short}  {} {long}  {long long}}
-          {iter L_ {short_short_} {short_} {} {long_} {long_long_}}
-          ({L} int x):
-             (int length := ABS width - (x < {L} 0 OR width > 0 | 1 | 0),
-                            {L} int n := ABS x;
-              if width = 0
-              then {L} int m := n; length := 0;
-                   while m %:= {L} 10; length +:= 1; m /= {L} 0
-                   do ~ od
-              fi;
-              string s := subwhole (n, length);
-              if length = 0 OR char_in_string (errorchar, loc int, s)
-              then ABS width * errorchar
-              else (x < {L} 0 | "-" |: width > 0 | "+" | "") +=: s;
-                   (width /= 0 | (ABS width - UPB s) * " " +=: s);
-                   s
-              fi),
-          ({L} real x): fixed (x, width, 0)
-          {reti {,}}
-       esac;
+      case v in
+      {iter L {long long } {long } {} {short } {short short }}
+      ({L}int x):
+      begin
+        int digits_required = WHOLEDIGITS x;
+        bool negative = x < {L}0;
+        int signs_required = (negative OR width > 0 | 1 | 0);
+        int chars_required = signs_required + digits_required;
+        int chars_available = (width = 0 | chars_required | ABS width);
+        if chars_available < chars_required
+        then
+            chars_available * "*"
+        else
+            [1:chars_available]char buffer;
+            int spaces_required = chars_available - chars_required;
+            int buf_ch := 1;
+            while buf_ch <= spaces_required
+            do
+              buffer[buf_ch] := " ";
+              buf_ch +:= 1
+            od;
+            if signs_required > 0
+            then
+                buffer[buf_ch] := (negative | "-" | "+");
+                buf_ch +:= 1
+            fi;
+            subwhole(x, buffer, buf_ch);
+            buffer
+        fi
+      end { ({L}int x): }
+      {reti {,}}
+      out
+         fixed(v, width, 0)
+      esac { whole };

     pub proc fixed = (Number v, int width, after) string:
        case v in
@@ -137,22 +270,54 @@ def
            {reti {,}}
         esac;

-    { Returns a string of maximum length `width' containing a decimal
-      representation of the positive integer `v'. }
+    { The RR proc subwhole looks like
+
+      proc ℵ₀ subwhole = (number v, int width) string: { implementation };
+
+      We deviate from that design below.  This means that, should someone copy
+      proc putf from the RR, they must recognize that the subwhole mentioned
+      there is no longer defined here, and make adjustments.  Similarly for
+      any other references to subwhole (as in van Vliet for example).
+
+      I considered calling this subwhole something else (whole_do_conv for
+      example) but that would mean anyone else calling subwhole hoping to get
+      the new version would silently get the old subwhole instead.
+
+      This subwhole converts digit by digit from left to right.  It relies on
+      the caller having padded out the buffer with spaces and sign as required
+      and begins filling digits starting at the value passed in buf_ch.  The
+      final []char array and final value of buf_ch are returned.
+    }
+
+    proc {ℵ₀} subwhole = (union (
+      {iter L {long long } {long } {} {short } {short short }}
+      {L}int
+      {reti {,}}
+    ) v, ref []char buffer, ref int buf_ch) void:
+    begin
+      int char_zero = ABS "0";
+      case v in
+      {iter L {long long } {long } {} {short } {short short }}
+      {iter K {LENG LENG } {LENG } {} {SHORTEN } {SHORTEN SHORTEN }}
+      {iter S {SHORTEN SHORTEN } {SHORTEN } {} {LENG } {LENG LENG }}k
+      {iter T {} {SHORTEN } {SHORTEN SHORTEN } {SHORTEN SHORTEN
SHORTEN } {SHORTEN SHORTEN SHORTEN SHORTEN }}
+      ({L}int number_to_convert):
+      begin
+        {L}int work := number_to_convert;
+        while buf_ch <= UPB buffer
+        do
+          int digit_number = UPB buffer - buf_ch + 1;
+          {L}int p10 = {T}whole_powers_of_10[digit_number];
+          int digit = {S} (work % p10);
+          buffer[buf_ch] := REPR (ABS digit + char_zero);
+          buf_ch +:= 1;
+          work -:= {K}digit * p10
+        od
+      end
+      {reti {,}}
+      esac
+    end { subwhole };

-    proc subwhole = (Number v, int width) string:
-       case v in
-          {iter L {short short} {short} {} {long}    {long long}}
-          {iter S {LENG LENG}   {LENG}  {} {SHORTEN} {SHORTEN SHORTEN}}
-          ({L} int x):
-             begin string s, {L} int n := x;
-                   while dig_char ({S} (n MOD {L} 10)) +=: s;
-                         n %:= {L} 10; n /= {L} 0
-                   do ~ od;
-                   (UPB s > width | width * errorchar | s)
-             end
-          {reti {,}}
-       esac;

     { Returns a string of maximum length `width' containing a rounded
       decimal representation of the positive real number `v'; if
-- 
2.53.0

-- 
Chris Hermansen · clhermansen "at" gmail "dot" com

C'est ma façon de parler.


More information about the Algol68 mailing list