[PATCH V3] New version of proc whole and dependencies

Jose E. Marchesi jemarch@gnu.org
Tue Jul 14 21:32:21 GMT 2026


Hi Chris.

This is looking good! :)

We will need some tests added to gcc/gcc/testsuite/algol68/execute, in
files like whole-1.a68, whole-2.a68, ...

For example:


whole-1.a68


 if bits_width = 32
 then assert(whole(max_int,0) = "4294967296");
      ...
 elif bits_width = 64
 then assert(whole(max_int,0) = "18446744073709551616");
      ...
 fi


> Good morning,
>
> I am proposing a "squashed" version of the two patches so far
> submitted based on Jose's kind advice.  In this patch I have not
> addressed the formatting guidelines last discussed (happy to do so in
> another) - I'm hoping to fold them in with any more review comments
> regarding the idea, the function of the code, whether this patch in
> general meets expectations (for example, is this correctly labelled in
> the subject line?).
>
>>From f0528f3b445b9ad578cee7f93a048366f6d6999c 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
>
> New whole and dependencies
>
> Proposed replacement for whole() as presented in the RR on 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 do only convert the
> integer when it is non-zero.
>
> Back in the 1980s and 1990s, I maintained a relatively large suite of inter-
> related 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.
>
> This left-to-right approach can be easily extended to providing for thousands
> separators (or the thousands / 10 thousands / millions etc approach used in
> India).  As well, it could be worth making whole, fixed and float respond to
> the locale.
>
> 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
> ---
>  libga68/standard.a68.in | 168 +++++++++++++++++++++++++++++++---------
>  1 file changed, 133 insertions(+), 35 deletions(-)
>
> diff --git a/libga68/standard.a68.in b/libga68/standard.a68.in
> index 5246679fa9f..1cf5726c8bb 100644
> --- a/libga68/standard.a68.in
> +++ b/libga68/standard.a68.in
> @@ -58,28 +58,103 @@ def
>                       {reti {,}}
>                    );
>
> +    { The definition of mode Integer used by both the RR subwhole and
> this one.  }
> +
> +    mode Integer = union (
> +                      {iter L {long long } {long } {} {short } {short short }}
> +                      {L}int
> +                      {reti {,}}
> +                   );
> +
> +    { 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, for each integer multiplication to eliminate the leading digit,
> +      and in the lookup operator WHOLEDIGITS to determine the number
> of digits in
> +      the integer to be converted.  }
> +
> +    int whole_max_entry := 1;
> +    long long int whole_p10 := long long 1;
> +    long long int whole_stop_after = long_long_max_int % long long 10;
> +    while whole_p10 < whole_stop_after
> +    do
> +       whole_p10 *:= long long 10;
> +       whole_max_entry +:= 1
> +    od;
> +    heap [1:whole_max_entry]long long int whole_powers_of_10;
> +    while whole_max_entry > 0
> +    do
> +       whole_powers_of_10[whole_max_entry] := whole_p10;
> +       whole_p10 %:= long long 10;
> +       whole_max_entry -:= 1
> +    od;
> +
> +    { 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.  }
> +
> +    op WHOLEDIGITS = (Integer 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;
> +          int num_digits := 1;
> +          for i from (LWB whole_powers_of_10) + 1 to UPB whole_powers_of_10
> +              while work <= -whole_powers_of_10[i]
> +          do
> +             num_digits +:= 1
> +          od;
> +          num_digits
> +    end { WHOLEDIGITS };
> +
> +    { 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)
> +       case v
> +       in
> +          {iter L {long long } {long } {} {short } {short short }}
> +          ({L}int x):
> +             if 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);
> +                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
>            {reti {,}}
> -       esac;
> +       out
> +          fixed(v, width, 0)
> +       esac { whole };
> +
>
>      pub proc fixed = (Number v, int width, after) string:
>         case v in
> @@ -137,22 +212,45 @@ def
>             {reti {,}}
>          esac;
>
> -    { Returns a string of maximum length `width' containing a decimal
> -      representation of the positive integer `v'. }
> -
> -    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
> +    { 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.
> +
> +      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 = (Integer v, ref []char buffer, ref int buf_ch) void:
> +       case int char_zero = ABS "0"; 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;
> +       esac { subwhole };
>
>      { Returns a string of maximum length `width' containing a rounded
>        decimal representation of the positive real number `v'; if
> -- 
> 2.53.0


More information about the Algol68 mailing list