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