PreviousUpNext

15.4.1095  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



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 (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,   fn (x, y) =  x or  y);
    bitwise_and =  binary (tagged_unt_guts::bitwise_and,  fn (x, y) =  x and y);
    bitwise_xor =  binary (tagged_unt_guts::bitwise_xor,  fn (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;

};                              #  package integer 





Comments and suggestions to: bugs@mythryl.org

PreviousUpNext