PreviousUpNext

15.4.1124  src/lib/std/src/multiword-int-guts.pkg

## multiword-int-guts.pkg  -- indefinite-precision integer arithmetic.
## COPYRIGHT (c) 2003 by The SML/NJ Fellowship.
## Author of the current code: Matthias Blume (blume@tti-c.org)
#
# The implementation in this file, together with its counterpart
# in lib/core/init/core-multiword-int.pkg inf
#
#     src/lib/core/init/core-multiword-int.pkg
#
# is derived from an earlier implementation of integer
# in standard.lib.
#
# That implementation, in turn, was derived from
# Andrzej Filinski's bignum package.
#
# The idea is that this package conforms to the specification of
# integer as described in the SML Basis library reference.
#
# The type multiword_int::Int itself is abstract.  A concrete version (together
# with conversions between abstract and concrete) is provided
# by package core_multiword_int.  (The type is a built-in type because
# the compiler must have some intrinsic knowledge of it in order to
# be able to implement
#   - multiword_int::Int literals
#   - conversion shortcuts (one_word_int::fromLarge o int::toLarge, etc.)
#   - overloading on literals
#   - pattern matching on literals
#
# Package core_multiword_int implements all the "essential" pieces which
# are required for the pervasive dictionary and for supporting the
# compiler (literals, conversions).
#
# The present package implements the rest and provides the complete
# interface as mandated by the Basis spec.
#
# The current implementation is not as efficient
# as it could and should be.             XXX BUGGO FIXME

# Compiled by:
#     src/lib/std/src/standard-core.sublib



###            "Anyone who has never made a mistake
###             has never tried anything new."
###
###                          -- Albert Einstein



stipulate
    package lms =  list_mergesort;                                              # list_mergesort                        is from   src/lib/src/list-mergesort.pkg
herein

    package multiword_int_guts
          : Multiword_Int                                                                       # Multiword_Int         is from   src/lib/std/src/multiword-int.api
    {
        Int =  multiword_int::Int;

        precision = NULL;
        min_int   = NULL;
        max_int   = NULL;

        # The following assumes large_int = one_word_int.
        # If integer is provided, it will be
        # large_int and to_large and from_large
        # will be the identity function.

        to_int     = inline_t::in::to_int;
        from_int   = inline_t::in::from_int;

        to_multiword_int   = inline_t::in::to_large;
        from_multiword_int = inline_t::in::from_large;

        Rep == core_multiword_int::Rep;

        concrete =  core_multiword_int::concrete;                                               # core_multiword_int    is from   src/lib/core/init/core-multiword-int.pkg
        abstract =  core_multiword_int::abstract;

        base_bits =  tagged_unt_guts::to_int_x core_multiword_int::base_bits;

        fun binary (f, gen_sign) (x, y)
            =
            {   my BI { negative=>sx, digits=>xs } =  concrete x;
                my BI { negative=>sy, digits=>ys } =  concrete y;

                sign = gen_sign (sx, sy);


                # Convert to two's complement;
                # Compute (- x - borrow)
                #
                fun twos (FALSE,  x, borrow) =>  (x, 0u0);
                    twos (TRUE, 0u0,    0u0) =>  (0u0, 0u0); #  no borrow 
                    twos (TRUE,   x, borrow) =>  (core_multiword_int::base - x - borrow, 0u1); #  Borrow
                end;

                # Convert to ones's complement 
                #
                ones = twos; 

                fun loop ([], [], _, _, _)
                        =>
                        [];

                    loop ([], y ! ys, bx, by, bz)
                        => 
                        loop1 (0u0, [], y, ys, bx, by, bz);

                    loop (x ! xs, [], bx, by, bz)
                        => 
                        loop1 (x, xs, 0u0, [], bx, by, bz);

                    loop (x ! xs, y ! ys, bx, by, bz)
                        => 
                        loop1 (x, xs, y, ys, bx, by, bz);
                end 

                also
                fun loop1 (x, xs, y, ys, bx, by, bz)
                    = 
                    {   # Convert from ones complement:
                        #
                        my (x, bx) =  twos (sx, x, bx);
                        my (y, by) =  twos (sy, y, by);
                        z  = f (x, y);

                        # Convert back to ones complement:
                        #
                        my (z, bz) = ones (sign, z, bz);
                        zs = loop (xs, ys, bx, by, bz);

                        case (z, zs)    #  strip leading zero 
                            (0u0, []) =>  [];
                            (z, zs)   =>  z ! zs;
                        esac;
                    };

                case (loop (xs, ys, 0u0, 0u0, 0u0))
                    []     =>  abstract (BI { digits => [], negative => FALSE } );
                    digits =>  abstract (BI { negative => sign, digits } );
                esac;
            };

        fun shift_amount w
            =
            { bytes =>  tagged_unt_guts::(/) (w, core_multiword_int::base_bits),
              bits  =>  tagged_unt_guts::(%) (w, core_multiword_int::base_bits)
            };

        infix my | & << >>;

        my (<<) = tagged_unt_guts::(<<);
        my (>>) = tagged_unt_guts::(>>);
        my (&)  = tagged_unt_guts::bitwise_and;
        my (|)  = tagged_unt_guts::bitwise_or;


        # Formatting for bases 2, 8, 16 by
        # slicing off the right number of bits:
        #
        fun bitformat (bits, maxdig, digvec) i
            =
            {   fun dig d
                    =
                    string_guts::get_byte_as_char (digvec, tagged_unt_guts::to_int_x d);

                my BI { digits, negative }
                    =
                    concrete i;

                fun addsign l
                    =
                    negative  ??  '-' ! l
                              ::        l;

                fun loop (chars, [], 0u0, _)
                        =>
                        string_guts::implode (addsign chars);

                    loop (chars, xs, c, cb)
                        =>
                        if (cb >= bits)

                            loop (dig (c & maxdig) ! chars,
                                 xs, c >> bits, cb - bits);
                        else
                            my (x, xs')
                                =
                                case xs
                                    []      =>   (0u0, []);
                                    x ! xs' =>   (x, xs');
                                esac;

                            a = ((x << cb) | c) & maxdig;

                            loop (dig a ! chars, xs',
                                  x >> (bits - cb),
                                  core_multiword_int::base_bits - bits + cb);
                        fi;
                end;

                case digits
                    [] =>  "0";
                    _  =>  loop ([], digits, 0u0, 0u0);
                esac;
            };                      

        my (dec_base, dec_digs)
            =
            try (0u1000000000, 9)
            where
                fun try (b, d)
                    =
                    if (b <= core_multiword_int::base)

                         (b, d);
                    else
                         try (tagged_unt_guts::(/) (b, 0u10), d - 1);
                    fi;
            end;

        # Decimal formatting by repeatedly dividing
        # by the largest possible power of 10:
        #
        fun decformat i
            =
            {   to_string
                    =
                    tagged_unt_guts::format number_string::DECIMAL;


                fun dec_dig d
                    =
                    number_string::pad_left  '0'  dec_digs  (to_string d);


                fun loop (l, [])
                        =>
                        l;

                    loop (l, [x])
                        =>
                        to_string x ! l;

                    loop (l, xs)
                        =>
                        {   my (q, r)
                                =
                                core_multiword_int::natdivmodd  (xs, dec_base);

                            loop (dec_dig r ! l, q);
                        };
                end;

                case (concrete i)

                    BI { digits => [], ... }
                        =>
                       "0";

                    BI { digits, negative }
                        =>
                        cat  if negative  "-" ! loop ([], digits);
                             else               loop ([], digits);
                             fi;
                esac;
            };


        fun format number_string::OCTAL   =>  bitformat (0u3, 0ux7, "01234567");
            format number_string::HEX     =>  bitformat (0u4, 0uxf, "0123456789abcdef");
            format number_string::BINARY  =>  bitformat (0u1, 0ux1, "01");
            format number_string::DECIMAL =>  decformat;
        end;

        fun sign i
            =
            case (concrete i)
                BI { digits => [], ... } => 0;
                BI { negative,     ... } => if negative  -1;
                                            else          1;
                                            fi;
            esac;


        fun same_sign (i, j)
            =
            sign i == sign j;


        fun bitwise_not x
            =
            -(x + abstract (BI { negative => FALSE, digits => [0u1] } ));


        fun log2 i
            =
            case (concrete i)
                #          
                BI { negative => TRUE, ... }
                    =>
                    raise exception DOMAIN;

                BI { digits, ... }
                    =>
                    loop (digits, 0)
                    where
                        fun wloop (0u0,  _) =>  raise exception DOMAIN; # Should never happen.
                            wloop (0u1, lg) =>  lg;
                            wloop (w,   lg) =>  wloop (tagged_unt_guts::(>>) (w, 0u1), lg + 1);
                        end;

                        fun loop ([],     lg) =>  raise exception DOMAIN;
                            loop ([x],    lg) =>  wloop (x, lg);
                            loop (x ! xs, lg) =>  loop (xs, lg + base_bits);
                        end;
                    end;
            esac;

        bitwise_or  =  binary (tagged_unt_guts::bitwise_or,   \\ (x, y) =  x or  y);
        bitwise_and =  binary (tagged_unt_guts::bitwise_and,  \\ (x, y) =  x and y);
        bitwise_xor =  binary (tagged_unt_guts::bitwise_xor,  \\ (x, y) =  x !=  y);


        # left shift; just shift the digits,
        # no special treatment for
        # signed versus unsigned:
        #
        fun lshift (i, w)
            =
            case (concrete i)

                BI { digits => [], negative }
                    =>
                    i;          # i == 0 

                BI { digits, negative }
                    =>
                    {   my { bytes, bits }
                            =
                            shift_amount  w;

                        bits' =  core_multiword_int::base_bits - bits;

                        fun pad (0u0, xs) =>  xs;
                            pad (n,   xs) =>  pad (n - 0u1,  0u0 ! xs);
                        end;

                        fun shift ([],   0u0) =>  [];
                            shift ([], carry) =>  [carry];

                            shift (x ! xs, carry)
                                =>
                                digit ! shift (xs, carry')
                                where
                                    max_val =  core_multiword_int::max_digit;

                                    digit  =  ((x << bits) | carry) & max_val;

                                    carry' =  x >> bits';
                                end;
                        end;

                        abstract
                            (BI { negative,
                                  digits => if (bits == 0u0)
                                                pad (bytes, digits);
                                            else
                                                pad (bytes, shift (digits, 0u0));
                                            fi
                                }
                            );
                    };
            esac;


        # Right shift. 
        #
        fun rshift (i, w)
            =
            case (concrete i)

                BI { digits => [], negative }
                    =>
                    i;          #  i == 0 

                BI { digits, negative }
                    =>
                    {   my { bytes, bits }
                            =
                            shift_amount w;

                        bits' =  core_multiword_int::base_bits - bits;

                        fun drop (0u0, i      ) =>  i; 
                            drop (n,   []     ) =>  [];
                            drop (n,   x ! xs) =>  drop (n - 0u1, xs);
                        end;

                        fun shift []
                                =>
                                ([], 0u0);

                            shift (x ! xs)
                                =>
                                {   my (zs, borrow)
                                        =
                                        shift xs;

                                    z = borrow | (x >> bits);

                                    borrow' = (x << bits') & core_multiword_int::max_digit;

                                    #  strip leading 0 
                                    case (z, zs)

                                        (0u0, []) =>  ([], borrow');
                                        _         =>  (z ! zs,   borrow');
                                    esac;
                                };
                        end;

                        digits
                            =
                            if (bits == 0u0)

                                drop (bytes, digits);
                            else
                                #1 (shift (drop (bytes, digits)));
                            fi;

                        abstract  case digits   
                                      [] => BI { negative => FALSE, digits => [] };
                                      _  => BI { negative,          digits       };
                                  esac;
                    };
            esac;


        fun startscan (do_it, hex) getchar s
            =
            sign (number_string::skip_ws getchar s)
            where
                fun hexprefix (neg, s)
                    =
                    case (getchar s)
                        THE (('x' | 'X'), s') => do_it (neg, s');
                        _ => do_it (neg, s);
                    esac;

                fun prefix (neg, s)
                    =
                    if hex   hexprefix (neg, s);
                    else     do_it (neg, s);
                    fi;

                fun sign s
                    =
                    case (getchar s)
                        NULL => NULL;
                        THE ('-', s') =>  prefix (TRUE,  s');
                        THE ('+', s') =>  prefix (FALSE, s');
                        _             =>  prefix (FALSE, s);
                    esac;
            end;


        fun bitscan (bits, dig_val, hex) getchar s
            =
            startscan
                (check_first_digit, hex)
                getchar
                s
            where
                fun dcons (0u0, []) =>  [];
                    dcons (x,   xs) =>  x ! xs;
                end;

                fun check_first_digit (neg, s)
                    =
                    {   pos0    =  core_multiword_int::base_bits - bits;
                        max_val =  core_multiword_int::max_digit;

                        fun digloop (d, pos, nat, s)
                            =
                            {   fun done ()
                                    =
                                    {   i = case (dcons (d, nat))

                                                 []  =>  BI { negative => FALSE, digits => [] };
                                                 nat =>  BI { negative => neg, digits => nat };
                                            esac;

                                        i = abstract i;

                                        THE ( if (pos == 0u0)  i;
                                              else             (rshift (i, pos));
                                              fi,

                                              s
                                            );
                                    };

                                case (getchar s )

                                    NULL => done ();

                                    THE (c, s')
                                        =>
                                        case (dig_val c)

                                            NULL => done ();

                                            THE v =>
                                                if (pos < bits)

                                                    if (pos == 0u0)

                                                        digloop (v << pos0, pos0, dcons (d, nat), s');
                                                    else
                                                        digloop ((v << (pos0 + pos)) & max_val,
                                                                pos0 + pos,
                                                                dcons (d | (v >> (bits - pos)), nat),
                                                                s');
                                                    fi;
                                                else
                                                    digloop (d | (v << (pos - bits)), pos - bits,
                                                            nat, s');
                                                fi;
                                        esac;
                                esac;
                            };

                        case (getchar s)

                            THE (c, s')
                                =>
                                case (dig_val c)
                                    THE v =>  digloop (v << pos0, pos0, [], s');
                                    NULL  =>  NULL;
                                esac;

                            NULL => NULL;
                        esac;
                    };
            end;

        fun decscan getchar s
            =
            startscan
                (check_first_digit, FALSE)
                getchar
                s

            where

                fun dig_val '0' => THE 0u0;
                    dig_val '1' => THE 0u1;
                    dig_val '2' => THE 0u2;
                    dig_val '3' => THE 0u3;
                    dig_val '4' => THE 0u4;
                    dig_val '5' => THE 0u5;
                    dig_val '6' => THE 0u6;
                    dig_val '7' => THE 0u7;
                    dig_val '8' => THE 0u8;
                    dig_val '9' => THE 0u9;
                    dig_val _ => NULL;
                end;

                fun digloop (negative, nat, fact, v, s)
                    =
                    {   fun done ()
                            =
                            {   i = case (core_multiword_int::natmadd (fact, nat, v))

                                        []     => abstract (BI { negative => FALSE,
                                                                 digits => [] } );

                                        digits => abstract (BI { negative,
                                                                 digits } );
                                    esac;

                                THE (i, s);
                            };

                        case (getchar s)

                            THE (c, s')
                                =>
                                case (dig_val c)

                                    THE v'
                                        =>
                                        if (fact == dec_base)

                                            digloop (negative,
                                                     core_multiword_int::natmadd (fact, nat, v),
                                                     0u10, v', s');
                                        else
                                            digloop (negative,
                                                     nat, fact * 0u10, v * 0u10 + v', s');
                                        fi;

                                    NULL => done ();
                                esac;

                            NULL => done ();
                        esac;
                    };

                fun check_first_digit (negative, s)
                    =
                    case (getchar s)

                        THE (c, s')
                            =>
                            case (dig_val c)
                                 THE v =>  digloop (negative, [], 0u10, v, s');
                                 NULL  =>  NULL;
                            esac;

                        NULL => NULL;
                    esac;
            end;

        fun bin_dig_val '0' =>  THE 0u0;
            bin_dig_val '1' =>  THE 0u1;
            bin_dig_val _   =>  NULL;
        end;

        fun oct_dig_val '0' =>  THE 0u0;
            oct_dig_val '1' =>  THE 0u1;
            oct_dig_val '2' =>  THE 0u2;
            oct_dig_val '3' =>  THE 0u3;
            oct_dig_val '4' =>  THE 0u4;
            oct_dig_val '5' =>  THE 0u5;
            oct_dig_val '6' =>  THE 0u6;
            oct_dig_val '7' =>  THE 0u7;
            oct_dig_val _   =>  NULL;
        end;

        fun hex_dig_val  '0'        =>  THE 0ux0;
            hex_dig_val  '1'        =>  THE 0ux1;
            hex_dig_val  '2'        =>  THE 0ux2;
            hex_dig_val  '3'        =>  THE 0ux3;
            hex_dig_val  '4'        =>  THE 0ux4;
            hex_dig_val  '5'        =>  THE 0ux5;
            hex_dig_val  '6'        =>  THE 0ux6;
            hex_dig_val  '7'        =>  THE 0ux7;
            hex_dig_val  '8'        =>  THE 0ux8;
            hex_dig_val  '9'        =>  THE 0ux9;
            hex_dig_val ('a' | 'A') =>  THE 0uxa;
            hex_dig_val ('b' | 'B') =>  THE 0uxb;
            hex_dig_val ('c' | 'C') =>  THE 0uxc;
            hex_dig_val ('d' | 'D') =>  THE 0uxd;
            hex_dig_val ('e' | 'E') =>  THE 0uxe;
            hex_dig_val ('f' | 'F') =>  THE 0uxf;
            hex_dig_val _ => NULL;
        end;

        fun scan number_string::DECIMAL =>  decscan;
            scan number_string::HEX     =>  bitscan (0u4, hex_dig_val, TRUE);
            scan number_string::OCTAL   =>  bitscan (0u3, oct_dig_val, FALSE);
            scan number_string::BINARY  =>  bitscan (0u1, bin_dig_val, FALSE);
        end;

        my (-_)   =  core_multiword_int::neg;
        my neg    =  core_multiword_int::neg;
        my (+)    =  core_multiword_int::(+);
        my (*)    =  core_multiword_int::(*);
        my (/)    =  core_multiword_int::div ;
        my (%)    =  core_multiword_int::mod ;
        my (-)    =  core_multiword_int::(-);
        my (<)    =  core_multiword_int::(<);
        my (<=)   =  core_multiword_int::(<=);
        my (>)    =  core_multiword_int::(>);
        my (>=)   =  core_multiword_int::(>=);

        div_mod  =  core_multiword_int::div_mod ;
        quot_rem =  core_multiword_int::quot_rem ;
        quot     =  core_multiword_int::quot ;
        rem      =  core_multiword_int::rem ;
        compare  =  core_multiword_int::compare ;
        abs      =  core_multiword_int::abs ;
        pow      =  core_multiword_int::pow ;

        fun max arg =  case (compare arg)   GREATER => #1 arg;    _ => #2 arg;   esac;
        fun min arg =  case (compare arg)   LESS    => #1 arg;    _ => #2 arg;   esac;

        to_string
            =
            format  number_string::DECIMAL;

        fun from_string s
            =
            number_string::scan_string (scan number_string::DECIMAL) s;

        my (<<)  =  lshift;
        my (>>>) =  rshift;

        fun 0! =>  1;
            n! =>  n * (n - 1)! ;
        end;

        fun is_prime p                  # A very simple and naive primality tester.  2009-09-02 CrT.
            =
            {   p = abs(p);                     # Try to do something reasonable with negative numbers.

                if   (p < 4)       TRUE;        # Call zero prime.
                elif (p % 2 == 0)  TRUE;        # Special-case even numbers to halve our loop time.
                else
                    # Test all odd numbers less than p/2:

                    lim = p / 2;

                    loop 3
                    where
                        fun loop i
                            =
                            if   (i == lim)     FALSE;
                            elif (p % i == 0)   TRUE;
                            else                loop (i + 2);
                            fi;
                    end;
                fi;
            };

        fun factors n
            =
            factors' (n, 2, [])
            where
                fun factors' (n, p, results)
                    =
                    if (p*p > n)

                        reverse (n ! results);

                    elif (n % p == 0)

                        factors' (n/p, p,   p ! results);
                    else
                        factors' (n,   p+1,     results);
                    fi;
            end;

        fun sum ints
            =
            sum' (ints, 0)
            where
                fun sum' (      [], result) =>  result;
                    sum' (i ! rest, result) =>  sum' (rest, i + result);
                end;
            end;

        fun product ints
            =
            product' (ints, 1)
            where
                fun product' (      [], result) =>  result;
                    product' (i ! rest, result) =>  product' (rest, i * result);
                end;
            end;

        fun list_min [] =>   raise exception DIE "Cannot do list_min on empty list";
            #
            list_min (i ! ints)
                =>
                min' (ints, i: Int)
                where
                    fun min' (      [], result) =>  result;
                        min' (i ! rest, result) =>  min'  (rest,  i < result ?? i :: result);
                    end;
                end;
        end;

        fun list_max [] =>   raise exception DIE "Cannot do list_max on empty list";
            #
            list_max (i ! ints)
                =>
                min' (ints, i: Int)
                where
                    fun min' (      [], result) =>  result;
                        min' (i ! rest, result) =>  min'  (rest,  i > result ?? i :: result);
                    end;
                end;
        end;

        fun sort ints
            =
            lms::sort_list (>) ints;

        fun sort_and_drop_duplicates ints
            =
            lms::sort_list_and_drop_duplicates  compare  ints;


        fun mean []     =>      0;                                                      # Would throwing an exception be better?  In graphics, at least, often it is better to just gloss over the occasional special case...
            mean ints   =>      sum ints   /   from_int (length ints);
        end;

        fun median []
                =>
                0;                                                                      # As above, arbitrary, possibly should throw exception.

            median ints
                =>
                {   len  = from_int (length ints);
                    ints = lms::sort_list (>) ints;
                    #
                    i1 = len / 2;
                    i2 = i1 - 1;

                    if (is_odd(len))
                        #       
                        # Return middle element:
                        #       
                        list::nth (ints, to_int i1);
                    else
                        # Return average of the two middle elements:
                        #
                        n1 = list::nth (ints, to_int i1); 
                        n2 = list::nth (ints, to_int i2); 

                        (n1 + n2) / 2;
                    fi;
                }
                where
                    fun is_odd(i) =  (bitwise_and (i, from_int 1) == from_int 1);
                end;
        end;

    };                          #  package integer 
end;




Comments and suggestions to: bugs@mythryl.org

PreviousUpNext