package Math::BigInt::Calc;

use 5.006001;
use strict;
use warnings;

use Carp qw< carp croak >;
use Math::BigInt::Lib;

our $VERSION = '2.005002';
$VERSION =~ tr/_//d;

our @ISA = ('Math::BigInt::Lib');

# Package to store unsigned big integers in decimal and do math with them
#
# Internally the numbers are stored in an array with at least 1 element, no
# leading zero parts (except the first) and in base 1eX where X is determined
# automatically at loading time to be the maximum possible value
#
# todo:
# - fully remove funky $# stuff in div() (maybe - that code scares me...)

##############################################################################
# global constants, flags and accessory

# constants for easier life

my $MAX_EXP_F;      # the maximum possible base 10 exponent with "no integer"
my $MAX_EXP_I;      # the maximum possible base 10 exponent with "use integer"

my $MAX_BITS;       # the maximum possible number of bits for $AND_BITS etc.

my $BASE_LEN;       # the current base exponent in use
my $USE_INT;        # whether "use integer" is used in the computations

my $BASE;           # the current base, e.g., 10000 if $BASE_LEN is 5
my $MAX_VAL;        # maximum value for an element, i.e., $BASE - 1

my $AND_BITS;       # maximum value used in binary and, e.g., 0xffff
my $OR_BITS;        # ditto for binary or
my $XOR_BITS;       # ditto for binary xor

my $AND_MASK;       # $AND_BITS + 1, e.g., 0x10000 if $AND_BITS is 0xffff
my $OR_MASK;        # ditto for binary or
my $XOR_MASK;       # ditto for binary xor

sub config {
    my $self = shift;

    croak "Missing input argument" unless @_;

    # Called as a getter.

    if (@_ == 1) {
        my $param = shift;
        croak "Parameter name must be a non-empty string"
          unless defined $param && length $param;
        return $BASE_LEN if $param eq 'base_len';
        return $USE_INT  if $param eq 'use_int';
        croak "Unknown parameter '$param'";
    }

    # Called as a setter.

    my $opts;
    while (@_) {
        my $param = shift;
        croak "Parameter name must be a non-empty string"
          unless defined $param && length $param;
        croak "Missing value for parameter '$param'"
          unless @_;
        my $value = shift;

        if ($param eq 'base_len' || $param eq 'use_int') {
            $opts -> {$param} = $value;
            next;
        }

        croak "Unknown parameter '$param'";
    }

    $BASE_LEN = $opts -> {base_len} if exists $opts -> {base_len};
    $USE_INT  = $opts -> {use_int}  if exists $opts -> {use_int};
    __PACKAGE__ -> _base_len($BASE_LEN, $USE_INT);

    return $self;
}

sub _base_len {
    #my $class = shift;                  # $class is not used
    shift;

    if (@_) {                           # if called as setter ...
        my ($base_len, $use_int) = @_;

        croak "The base length must be a positive integer"
          unless defined($base_len) && $base_len == int($base_len)
                 && $base_len > 0;

        if ( $use_int && ($base_len > $MAX_EXP_I) ||
            !$use_int && ($base_len > $MAX_EXP_F))
        {
            croak "The maximum base length (exponent) is $MAX_EXP_I with",
              " 'use integer' and $MAX_EXP_F without 'use integer'. The",
              " requested settings, a base length of $base_len ",
              $use_int ? "with" : "without", " 'use integer', is invalid.";
        }

        $BASE_LEN = $base_len;
        $BASE = 0 + ("1" . ("0" x $BASE_LEN));
        $MAX_VAL = $BASE - 1;
        $USE_INT = $use_int ? 1 : 0;

        {
            no warnings "redefine";
            if ($use_int) {
                *_mul = \&_mul_use_int;
                *_div = \&_div_use_int;
            } else {
                *_mul = \&_mul_no_int;
                *_div = \&_div_no_int;
            }
        }
    }

    # Find max bits. This is the largest power of two that is both no larger
    # than $BASE and no larger than the maximum integer (i.e., ~0). We need
    # this limitation because _and(), _or(), and _xor() only work on one
    # element at a time.

    my $umax = ~0;                      # largest unsigned integer
    my $tmp  = $umax < $BASE ? $umax : $BASE;

    $MAX_BITS = 0;
    while ($tmp >>= 1) {
        $MAX_BITS++;
    }

    # Limit to 32 bits for portability. Is this really necessary? XXX

    $MAX_BITS = 32 if $MAX_BITS > 32;

    # Find out how many bits _and, _or and _xor can take (old default = 16).
    # Are these tests really necessary? Can't we just use $MAX_BITS? XXX

    for ($AND_BITS = $MAX_BITS ; $AND_BITS > 0 ; $AND_BITS--) {
        my $x = CORE::oct('0b' . '1' x $AND_BITS);
        my $y = $x & $x;
        my $z = 2 * (2 ** ($AND_BITS - 1)) + 1;
        last unless $AND_BITS < $MAX_BITS && $x == $z && $y == $x;
    }

    for ($XOR_BITS = $MAX_BITS ; $XOR_BITS > 0 ; $XOR_BITS--) {
        my $x = CORE::oct('0b' . '1' x $XOR_BITS);
        my $y = $x ^ $x;
        my $z = 2 * (2 ** ($XOR_BITS - 1)) + 1;
        last unless $XOR_BITS < $MAX_BITS && $x == $z && $y == $x;
    }

    for ($OR_BITS = $MAX_BITS ; $OR_BITS > 0 ; $OR_BITS--) {
        my $x = CORE::oct('0b' . '1' x $OR_BITS);
        my $y = $x | $x;
        my $z = 2 * (2 ** ($OR_BITS - 1)) + 1;
        last unless $OR_BITS < $MAX_BITS && $x == $z && $y == $x;
    }

    $AND_MASK = __PACKAGE__->_new(( 2 ** $AND_BITS ));
    $XOR_MASK = __PACKAGE__->_new(( 2 ** $XOR_BITS ));
    $OR_MASK  = __PACKAGE__->_new(( 2 ** $OR_BITS  ));

    return $BASE_LEN unless wantarray;
    return ($BASE_LEN, $BASE, $AND_BITS, $XOR_BITS, $OR_BITS, $BASE_LEN, $MAX_VAL,
            $MAX_BITS, $MAX_EXP_F, $MAX_EXP_I, $USE_INT);
}

sub _new {
    # Given a string representing an integer, returns a reference to an array
    # of integers, where each integer represents a chunk of the original input
    # integer.

    my ($class, $str) = @_;
    #unless ($str =~ /^([1-9]\d*|0)\z/) {
    #    croak("Invalid input string '$str'");
    #}

    my $input_len = length($str) - 1;

    # Shortcut for small numbers.
    return bless [ $str ], $class if $input_len < $BASE_LEN;

    my $format = "a" . (($input_len % $BASE_LEN) + 1);
    $format .= $] < 5.008 ? "a$BASE_LEN" x int($input_len / $BASE_LEN)
                          : "(a$BASE_LEN)*";

    my $self = [ reverse(map { 0 + $_ } unpack($format, $str)) ];
    return bless $self, $class;
}

BEGIN {

    # Compute $MAX_EXP_F, the maximum usable base 10 exponent.

    # The largest element in base 10**$BASE_LEN is 10**$BASE_LEN-1. For instance,
    # with $BASE_LEN = 5, the largest element is 99_999, and the largest carry is
    #
    #     int( 99_999 * 99_999 / 100_000 ) = 99_998
    #
    # so make sure that 99_999 * 99_999 + 99_998 is within the range of integers
    # that can be represented accuratly.
    #
    # Note that on some systems with quadmath support, the following is within
    # the range of numbers that can be represented exactly, but it still gives
    # the incorrect value $r = 2 (even though POSIX::fmod($x, $y) gives the
    # correct value of 1:
    #
    #     $x =  99999999999999999;
    #     $y = 100000000000000000;
    #     $r = $x * $x % $y;            # should be 1
    #
    # so also check for this.

    for ($MAX_EXP_F = 1 ; ; $MAX_EXP_F++) {         # when $MAX_EXP_F = 5
        my $MAX_EXP_FM1 = $MAX_EXP_F - 1;           #   = 4
        my $bs = "1" . ("0" x $MAX_EXP_F);          #   = "100000"
        my $xs = "9" x $MAX_EXP_F;                  #   =  "99999"
        my $cs = ("9" x $MAX_EXP_FM1) . "8";        #   =  "99998"
        my $ys = $cs . ("0" x $MAX_EXP_FM1) . "1";  #   = "9999800001"

        # Compute and check the product.
        my $yn = $xs * $xs;                         #   = 9999800001
        last if $yn != $ys;

        # Compute and check the remainder.
        my $rn = $yn % $bs;                         #   = 1
        last if $rn != 1;

        # Compute and check the carry. The division here is exact.
        my $cn = ($yn - $rn) / $bs;                 #   = 99998
        last if $cn != $cs;

        # Compute and check product plus carry.
        my $zs = $cs . ("9" x $MAX_EXP_F);          #   = "9999899999"
        my $zn = $yn + $cn;                         #   = 99998999999
        last if $zn != $zs;
        last if $zn - ($zn - 1) != 1;
    }
    $MAX_EXP_F--;                       # last test failed, so retract one step

    # Compute $MAX_EXP_I, the maximum usable base 10 exponent within the range
    # of what is available with "use integer". On older versions of Perl,
    # integers are converted to floating point numbers, even though they are
    # within the range of what can be represented as integers. For example, on
    # some 64 bit Perls, 999999999 * 999999999 becomes 999999998000000000, not
    # 999999998000000001, even though the latter is less than the maximum value
    # for a 64 bit integer, 18446744073709551615.

    my $umax = ~0;                      # largest unsigned integer
    for ($MAX_EXP_I = int(0.5 * log($umax) / log(10));
         $MAX_EXP_I > 0;
         $MAX_EXP_I--)
    {                                               # when $MAX_EXP_I = 5
        my $MAX_EXP_IM1 = $MAX_EXP_I - 1;           #   = 4
        my $bs = "1" . ("0" x $MAX_EXP_I);          #   = "100000"
        my $xs = "9" x $MAX_EXP_I;                  #   =  "99999"
        my $cs = ("9" x $MAX_EXP_IM1) . "8";        #   =  "99998"
        my $ys = $cs . ("0" x $MAX_EXP_IM1) . "1";  #   = "9999800001"

        # Compute and check the product.
        my $yn = $xs * $xs;                         #   = 9999800001
        next if $yn != $ys;

        # Compute and check the remainder.
        my $rn = $yn % $bs;                         #   = 1
        next if $rn != 1;

        # Compute and check the carry. The division here is exact.
        my $cn = ($yn - $rn) / $bs;                 #   = 99998
        next if $cn != $cs;

        # Compute and check product plus carry.
        my $zs = $cs . ("9" x $MAX_EXP_I);          #   = "9999899999"
        my $zn = $yn + $cn;                         #   = 99998999999
        next if $zn != $zs;
        next if $zn - ($zn - 1) != 1;
        last;
    }

    ($BASE_LEN, $USE_INT) = $MAX_EXP_F > $MAX_EXP_I
                          ? ($MAX_EXP_F, 0) : ($MAX_EXP_I, 1);

    __PACKAGE__ -> _base_len($BASE_LEN, $USE_INT);
}

###############################################################################

sub _zero {
    # create a zero
    my $class = shift;
    return bless [ 0 ], $class;
}

sub _one {
    # create a one
    my $class = shift;
    return bless [ 1 ], $class;
}

sub _two {
    # create a two
    my $class = shift;
    return bless [ 2 ], $class;
}

sub _ten {
    # create a 10
    my $class = shift;
    my $self = $BASE_LEN == 1 ? [ 0, 1 ] : [ 10 ];
    bless $self, $class;
}

sub _1ex {
    # create a 1Ex
    my $class = shift;

    my $rem = $_[0] % $BASE_LEN;                # remainder
    my $div = ($_[0] - $rem) / $BASE_LEN;       # parts

    # With a $BASE_LEN of 6, 1e14 becomes
    # [ 000000, 000000, 100 ] -> [ 0, 0, 100 ]
    bless [ (0) x $div,  0 + ("1" . ("0" x $rem)) ], $class;
}

sub _copy {
    # make a true copy
    my $class = shift;
    return bless [ @{ $_[0] } ], $class;
}

sub import {
    my $self = shift;

    my $opts;
    my ($base_len, $use_int);
    while (@_) {
        my $param = shift;
        croak "Parameter name must be a non-empty string"
          unless defined $param && length $param;
        croak "Missing value for parameter '$param'"
          unless @_;
        my $value = shift;

        if ($param eq 'base_len' || $param eq 'use_int') {
            $opts -> {$param} = $value;
            next;
        }

        croak "Unknown parameter '$param'";
    }

    $base_len = exists $opts -> {base_len} ? $opts -> {base_len} : $BASE_LEN;
    $use_int  = exists $opts -> {use_int}  ? $opts -> {use_int}  : $USE_INT;
    __PACKAGE__ -> _base_len($base_len, $use_int);

    return $self;
}

##############################################################################
# convert back to string and number

sub _str {
    # Convert number from internal base 1eN format to string format. Internal
    # format is always normalized, i.e., no leading zeros.

    my $ary = $_[1];
    my $idx = $#$ary;           # index of last element

    if ($idx < 0) {             # should not happen
        croak("$_[1] has no elements");
    }

    # Handle first one differently, since it should not have any leading zeros.
    my $ret = int($ary->[$idx]);
    if ($idx > 0) {
        # Interestingly, the pre-padd method uses more time.
        # The old grep variant takes longer (14 vs. 10 sec).
        my $z = '0' x ($BASE_LEN - 1);
        while (--$idx >= 0) {
            $ret .= substr($z . $ary->[$idx], -$BASE_LEN);
        }
    }
    $ret;
}

sub _num {
    # Make a Perl scalar number (int/float) from a BigInt object.
    my $x = $_[1];

    return $x->[0] if @$x == 1;         # below $BASE

    # Start with the most significant element and work towards the least
    # significant element. Avoid multiplying "inf" (which happens if the number
    # overflows) with "0" (if there are zero elements in $x) since this gives
    # "nan" which propagates to the output.

    my $num = 0;
    for (my $i = $#$x ; $i >= 0 ; --$i) {
        $num *= $BASE;
        $num += $x -> [$i];
    }
    return $num;
}

##############################################################################
# actual math code

sub _add {
    # (ref to int_num_array, ref to int_num_array)
    #
    # Routine to add two base 1eX numbers stolen from Knuth Vol 2 Algorithm A
    # pg 231. There are separate routines to add and sub as per Knuth pg 233.
    # This routine modifies array x, but not y.

    my ($c, $x, $y) = @_;

    # $x + 0 => $x

    return $x if @$y == 1 && $y->[0] == 0;

    # 0 + $y => $y->copy

    if (@$x == 1 && $x->[0] == 0) {
        @$x = @$y;
        return $x;
    }

    # For each in Y, add Y to X and carry. If after that, something is left in
    # X, foreach in X add carry to X and then return X, carry. Trades one
    # "$j++" for having to shift arrays.

    my $car = 0;
    my $j = 0;
    for my $i (@$y) {
        $x->[$j] -= $BASE if $car = (($x->[$j] += $i + $car) >= $BASE) ? 1 : 0;
        $j++;
    }
    while ($car != 0) {
        $x->[$j] -= $BASE if $car = (($x->[$j] += $car) >= $BASE) ? 1 : 0;
        $j++;
    }
    $x;
}

sub _inc {
    # (ref to int_num_array, ref to int_num_array)
    # Add 1 to $x, modify $x in place
    my ($c, $x) = @_;

    for my $i (@$x) {
        return $x if ($i += 1) < $BASE; # early out
        $i = 0;                         # overflow, next
    }
    push @$x, 1 if $x->[-1] == 0;       # last overflowed, so extend
    $x;
}

sub _dec {
    # (ref to int_num_array, ref to int_num_array)
    # Sub 1 from $x, modify $x in place
    my ($c, $x) = @_;

    my $MAX = $BASE - 1;                # since MAX_VAL based on BASE
    for my $i (@$x) {
        last if ($i -= 1) >= 0;         # early out
        $i = $MAX;                      # underflow, next
    }
    pop @$x if $x->[-1] == 0 && @$x > 1; # last underflowed (but leave 0)
    $x;
}

sub _sub {
    # (ref to int_num_array, ref to int_num_array, swap)
    #
    # Subtract base 1eX numbers -- stolen from Knuth Vol 2 pg 232, $x > $y
    # subtract Y from X by modifying x in place
    my ($c, $sx, $sy, $s) = @_;

    my $car = 0;
    my $j = 0;
    if (!$s) {
        for my $i (@$sx) {
            last unless defined $sy->[$j] || $car;
            $i += $BASE if $car = (($i -= ($sy->[$j] || 0) + $car) < 0);
            $j++;
        }
        # might leave leading zeros, so fix that
        return __strip_zeros($sx);
    }
    for my $i (@$sx) {
        # We can't do an early out if $x < $y, since we need to copy the high
        # chunks from $y. Found by Bob Mathews.
        #last unless defined $sy->[$j] || $car;
        $sy->[$j] += $BASE
          if $car = ($sy->[$j] = $i - ($sy->[$j] || 0) - $car) < 0;
        $j++;
    }
    # might leave leading zeros, so fix that
    __strip_zeros($sy);
}

sub _mul_use_int {
    # (ref to int_num_array, ref to int_num_array)
    # multiply two numbers in internal representation
    # modifies first arg, second need not be different from first
    # works for 64 bit integer with "use integer"
    my ($c, $xv, $yv) = @_;
    use integer;

    if (@$yv == 1) {
        # shortcut for two very short numbers (improved by Nathan Zook) works
        # also if xv and yv are the same reference, and handles also $x == 0
        if (@$xv == 1) {
            if (($xv->[0] *= $yv->[0]) >= $BASE) {
                $xv->[0] =
                  $xv->[0] - ($xv->[1] = $xv->[0] / $BASE) * $BASE;
            }
            return $xv;
        }
        # $x * 0 => 0
        if ($yv->[0] == 0) {
            @$xv = (0);
            return $xv;
        }

        # multiply a large number a by a single element one, so speed up
        my $y = $yv->[0];
        my $car = 0;
        foreach my $i (@$xv) {
            #$i = $i * $y + $car; $car = $i / $BASE; $i -= $car * $BASE;
            $i = $i * $y + $car;
            $i -= ($car = $i / $BASE) * $BASE;
        }
        push @$xv, $car if $car != 0;
        return $xv;
    }

    # shortcut for result $x == 0 => result = 0
    return $xv if @$xv == 1 && $xv->[0] == 0;

    # since multiplying $x with $x fails, make copy in this case
    $yv = $c->_copy($xv) if $xv == $yv;         # same references?

    my @prod = ();
    my ($prod, $car, $cty);
    for my $xi (@$xv) {
        $car = 0;
        $cty = 0;
        # looping through this if $xi == 0 is silly - so optimize it away!
        $xi = (shift(@prod) || 0), next if $xi == 0;
        for my $yi (@$yv) {
            $prod = $xi * $yi + ($prod[$cty] || 0) + $car;
            $prod[$cty++] = $prod - ($car = $prod / $BASE) * $BASE;
        }
        $prod[$cty] += $car if $car;    # need really to check for 0?
        $xi = shift(@prod) || 0;        # || 0 makes v5.005_3 happy
    }
    push @$xv, @prod;
    $xv;
}

sub _mul_no_int {
    # (ref to int_num_array, ref to int_num_array)
    # multiply two numbers in internal representation
    # modifies first arg, second need not be different from first
    my ($c, $xv, $yv) = @_;

    if (@$yv == 1) {
        # shortcut for two very short numbers (improved by Nathan Zook) works
        # also if xv and yv are the same reference, and handles also $x == 0
        if (@$xv == 1) {
            if (($xv->[0] *= $yv->[0]) >= $BASE) {
                my $rem = $xv->[0] % $BASE;
                $xv->[1] = ($xv->[0] - $rem) / $BASE;
                $xv->[0] = $rem;
            }
            return $xv;
        }
        # $x * 0 => 0
        if ($yv->[0] == 0) {
            @$xv = (0);
            return $xv;
        }

        # multiply a large number a by a single element one, so speed up
        my $y = $yv->[0];
        my $car = 0;
        my $rem;
        foreach my $i (@$xv) {
            $i = $i * $y + $car;
            $rem = $i % $BASE;
            $car = ($i - $rem) / $BASE;
            $i = $rem;
        }
        push @$xv, $car if $car != 0;
        return $xv;
    }

    # shortcut for result $x == 0 => result = 0
    return $xv if @$xv == 1 && $xv->[0] == 0;

    # since multiplying $x with $x fails, make copy in this case
    $yv = $c->_copy($xv) if $xv == $yv;         # same references?

    my @prod = ();
    my ($prod, $rem, $car, $cty);
    for my $xi (@$xv) {
        $car = 0;
        $cty = 0;
        # looping through this if $xi == 0 is silly - so optimize it away!
        $xi = (shift(@prod) || 0), next if $xi == 0;
        for my $yi (@$yv) {
            $prod = $xi * $yi + ($prod[$cty] || 0) + $car;
            $rem = $prod % $BASE;
            $car = ($prod - $rem) / $BASE;
            $prod[$cty++] = $rem;
        }
        $prod[$cty] += $car if $car;    # need really to check for 0?
        $xi = shift(@prod) || 0;        # || 0 makes v5.005_3 happy
    }
    push @$xv, @prod;
    $xv;
}

sub _div_use_int {
    # ref to array, ref to array, modify first array and return remainder if
    # in list context

    # This version works on integers
    use integer;

    my ($c, $x, $yorg) = @_;

    # the general div algorithm here is about O(N*N) and thus quite slow, so
    # we first check for some special cases and use shortcuts to handle them.

    # if both numbers have only one element:
    if (@$x == 1 && @$yorg == 1) {
        # shortcut, $yorg and $x are two small numbers
        if (wantarray) {
            my $rem = [ $x->[0] % $yorg->[0] ];
            bless $rem, $c;
            $x->[0] = $x->[0] / $yorg->[0];
            return ($x, $rem);
        } else {
            $x->[0] = $x->[0] / $yorg->[0];
            return $x;
        }
    }

    # if x has more than one, but y has only one element:
    if (@$yorg == 1) {
        my $rem;
        $rem = $c->_mod($c->_copy($x), $yorg) if wantarray;

        # shortcut, $y is < $BASE
        my $j = @$x;
        my $r = 0;
        my $y = $yorg->[0];
        my $b;
        while ($j-- > 0) {
            $b = $r * $BASE + $x->[$j];
            $r = $b % $y;
            $x->[$j] = $b / $y;
        }
        pop(@$x) if @$x > 1 && $x->[-1] == 0;   # remove any trailing zero
        return ($x, $rem) if wantarray;
        return $x;
    }

    # now x and y have more than one element

    # check whether y has more elements than x, if so, the result is 0
    if (@$yorg > @$x) {
        my $rem;
        $rem = $c->_copy($x) if wantarray;      # make copy
        @$x = 0;                                # set to 0
        return ($x, $rem) if wantarray;         # including remainder?
        return $x;                              # only x, which is [0] now
    }

    # check whether the numbers have the same number of elements, in that case
    # the result will fit into one element and can be computed efficiently
    if (@$yorg == @$x) {
        my $cmp = 0;
        for (my $j = $#$x ; $j >= 0 ; --$j) {
            last if $cmp = $x->[$j] - $yorg->[$j];
        }

        if ($cmp == 0) {        # x = y
            @$x = 1;
            return $x, $c->_zero() if wantarray;
            return $x;
        }

        if ($cmp < 0) {         # x < y
            if (wantarray) {
                my $rem = $c->_copy($x);
                @$x = 0;
                return $x, $rem;
            }
            @$x = 0;
            return $x;
        }
    }

    # all other cases:

    my $y = $c->_copy($yorg);           # always make copy to preserve

    my $tmp;
    my $dd = $BASE / ($y->[-1] + 1);
    if ($dd != 1) {
        my $car = 0;
        for my $xi (@$x) {
            $xi = $xi * $dd + $car;
            $xi -= ($car = $xi / $BASE) * $BASE;
        }
        push(@$x, $car);
        $car = 0;
        for my $yi (@$y) {
            $yi = $yi * $dd + $car;
            $yi -= ($car = $yi / $BASE) * $BASE;
        }
    } else {
        push(@$x, 0);
    }

    # @q will accumulate the final result, $q contains the current computed
    # part of the final result

    my @q = ();
    my ($v2, $v1) = @$y[-2, -1];
    $v2 = 0 unless $v2;
    while ($#$x > $#$y) {
        my ($u2, $u1, $u0) = @$x[-3 .. -1];
        $u2 = 0 unless $u2;
        #warn "oups v1 is 0, u0: $u0 $y->[-2] $y->[-1] l ",scalar @$y,"\n"
        # if $v1 == 0;
        my $tmp = $u0 * $BASE + $u1;
        my $rem = $tmp % $v1;
        my $q = $u0 == $v1 ? $MAX_VAL : (($tmp - $rem) / $v1);
        --$q while $v2 * $q > ($u0 * $BASE + $u1 - $q * $v1) * $BASE + $u2;
        if ($q) {
            my $prd;
            my ($car, $bar) = (0, 0);
            for (my $yi = 0, my $xi = $#$x - $#$y - 1; $yi <= $#$y; ++$yi, ++$xi) {
                $prd = $q * $y->[$yi] + $car;
                $prd -= ($car = int($prd / $BASE)) * $BASE;
                $x->[$xi] += $BASE if $bar = (($x->[$xi] -= $prd + $bar) < 0);
            }
            if ($x->[-1] < $car + $bar) {
                $car = 0;
                --$q;
                for (my $yi = 0, my $xi = $#$x - $#$y - 1; $yi <= $#$y; ++$yi, ++$xi) {
                    $x->[$xi] -= $BASE
                      if $car = (($x->[$xi] += $y->[$yi] + $car) >= $BASE);
                }
            }
        }
        pop(@$x);
        unshift(@q, $q);
    }

    if (wantarray) {
        my $d = bless [], $c;
        if ($dd != 1) {
            my $car = 0;
            my $prd;
            for my $xi (reverse @$x) {
                $prd = $car * $BASE + $xi;
                $car = $prd - ($tmp = $prd / $dd) * $dd;
                unshift @$d, $tmp;
            }
        } else {
            @$d = @$x;
        }
        @$x = @q;
        __strip_zeros($x);
        __strip_zeros($d);
        return ($x, $d);
    }
    @$x = @q;
    __strip_zeros($x);
    $x;
}

sub _div_no_int {
    # ref to array, ref to array, modify first array and return remainder if
    # in list context

    my ($c, $x, $yorg) = @_;

    # the general div algorithm here is about O(N*N) and thus quite slow, so
    # we first check for some special cases and use shortcuts to handle them.

    # if both numbers have only one element:
    if (@$x == 1 && @$yorg == 1) {
        # shortcut, $yorg and $x are two small numbers
        my $rem = [ $x->[0] % $yorg->[0] ];
        bless $rem, $c;
        $x->[0] = ($x->[0] - $rem->[0]) / $yorg->[0];
        return ($x, $rem) if wantarray;
        return $x;
    }

    # if x has more than one, but y has only one element:
    if (@$yorg == 1) {
        my $rem;
        $rem = $c->_mod($c->_copy($x), $yorg) if wantarray;

        # shortcut, $y is < $BASE
        my $j = @$x;
        my $r = 0;
        my $y = $yorg->[0];
        my $b;
        while ($j-- > 0) {
            $b = $r * $BASE + $x->[$j];
            $r = $b % $y;
            $x->[$j] = ($b - $r) / $y;
        }
        pop(@$x) if @$x > 1 && $x->[-1] == 0;   # remove any trailing zero
        return ($x, $rem) if wantarray;
        return $x;
    }

    # now x and y have more than one element

    # check whether y has more elements than x, if so, the result is 0
    if (@$yorg > @$x) {
        my $rem;
        $rem = $c->_copy($x) if wantarray;      # make copy
        @$x = 0;                                # set to 0
        return ($x, $rem) if wantarray;         # including remainder?
        return $x;                              # only x, which is [0] now
    }

    # check whether the numbers have the same number of elements, in that case
    # the result will fit into one element and can be computed efficiently
    if (@$yorg == @$x) {
        my $cmp = 0;
        for (my $j = $#$x ; $j >= 0 ; --$j) {
            last if $cmp = $x->[$j] - $yorg->[$j];
        }

        if ($cmp == 0) {        # x = y
            @$x = 1;
            return $x, $c->_zero() if wantarray;
            return $x;
        }

        if ($cmp < 0) {         # x < y
            if (wantarray) {
                my $rem = $c->_copy($x);
                @$x = 0;
                return $x, $rem;
            }
            @$x = 0;
            return $x;
        }
    }

    # all other cases:

    my $y = $c->_copy($yorg);           # always make copy to preserve

    my $tmp = $y->[-1] + 1;
    my $rem = $BASE % $tmp;
    my $dd  = ($BASE - $rem) / $tmp;
    if ($dd != 1) {
        my $car = 0;
        for my $xi (@$x) {
            $xi = $xi * $dd + $car;
            $rem = $xi % $BASE;
            $car = ($xi - $rem) / $BASE;
            $xi = $rem;
        }
        push(@$x, $car);
        $car = 0;
        for my $yi (@$y) {
            $yi = $yi * $dd + $car;
            $rem = $yi % $BASE;
            $car = ($yi - $rem) / $BASE;
            $yi = $rem;
        }
    } else {
        push(@$x, 0);
    }

    # @q will accumulate the final result, $q contains the current computed
    # part of the final result

    my @q = ();
    my ($v2, $v1) = @$y[-2, -1];
    $v2 = 0 unless $v2;
    while ($#$x > $#$y) {
        my ($u2, $u1, $u0) = @$x[-3 .. -1];
        $u2 = 0 unless $u2;
        #warn "oups v1 is 0, u0: $u0 $y->[-2] $y->[-1] l ",scalar @$y,"\n"
        # if $v1 == 0;
        my $tmp = $u0 * $BASE + $u1;
        my $rem = $tmp % $v1;
        my $q = $u0 == $v1 ? $MAX_VAL : (($tmp - $rem) / $v1);
        --$q while $v2 * $q > ($u0 * $BASE + $u1 - $q * $v1) * $BASE + $u2;
        if ($q) {
            my $prd;
            my ($car, $bar) = (0, 0);
            for (my $yi = 0, my $xi = $#$x - $#$y - 1; $yi <= $#$y; ++$yi, ++$xi) {
                $prd = $q * $y->[$yi] + $car;
                $rem = $prd % $BASE;
                $car = ($prd - $rem) / $BASE;
                $prd -= $car * $BASE;
                $x->[$xi] += $BASE if $bar = (($x->[$xi] -= $prd + $bar) < 0);
            }
            if ($x->[-1] < $car + $bar) {
                $car = 0;
                --$q;
                for (my $yi = 0, my $xi = $#$x - $#$y - 1; $yi <= $#$y; ++$yi, ++$xi) {
                    $x->[$xi] -= $BASE
                      if $car = (($x->[$xi] += $y->[$yi] + $car) >= $BASE);
                }
            }
        }
        pop(@$x);
        unshift(@q, $q);
    }

    if (wantarray) {
        my $d = bless [], $c;
        if ($dd != 1) {
            my $car = 0;
            my ($prd, $rem);
            for my $xi (reverse @$x) {
                $prd = $car * $BASE + $xi;
                $rem = $prd % $dd;
                $tmp = ($prd - $rem) / $dd;
                $car = $rem;
                unshift @$d, $tmp;
            }
        } else {
            @$d = @$x;
        }
        @$x = @q;
        __strip_zeros($x);
        __strip_zeros($d);
        return ($x, $d);
    }
    @$x = @q;
    __strip_zeros($x);
    $x;
}

##############################################################################
# testing

sub _acmp {
    # Internal absolute post-normalized compare (ignore signs)
    # ref to array, ref to array, return <0, 0, >0
    # Arrays must have at least one entry; this is not checked for.
    my ($c, $cx, $cy) = @_;

    # shortcut for short numbers
    return (($cx->[0] <=> $cy->[0]) <=> 0)
      if @$cx == 1 && @$cy == 1;

    # fast comp based on number of array elements (aka pseudo-length)
    my $lxy = (@$cx - @$cy)
      # or length of first element if same number of elements (aka difference 0)
      ||
        # need int() here because sometimes the last element is '00018' vs '18'
        (length(int($cx->[-1])) - length(int($cy->[-1])));

    return -1 if $lxy < 0;      # already differs, ret
    return  1 if $lxy > 0;      # ditto

    # manual way (abort if unequal, good for early ne)
    my $a;
    my $j = @$cx;
    while (--$j >= 0) {
        last if $a = $cx->[$j] - $cy->[$j];
    }
    $a <=> 0;
}

sub _len {
    # compute number of digits in base 10

    # int() because add/sub sometimes leaves strings (like '00005') instead of
    # '5' in this place, thus causing length() to report wrong length
    my $cx = $_[1];

    (@$cx - 1) * $BASE_LEN + length(int($cx->[-1]));
}

sub _digit {
    # Return the nth digit. Zero is rightmost, so _digit(123, 0) gives 3.
    # Negative values count from the left, so _digit(123, -1) gives 1.
    my ($c, $x, $n) = @_;

    my $len = _len('', $x);

    $n += $len if $n < 0;               # -1 last, -2 second-to-last

    # Math::BigInt::Calc returns 0 if N is out of range, but this is not done
    # by the other backend libraries.

    return "0" if $n < 0 || $n >= $len; # return 0 for digits out of range

    my $elem = int($n / $BASE_LEN);     # index of array element
    my $digit = $n % $BASE_LEN;         # index of digit within the element
    substr("0" x $BASE_LEN . "$x->[$elem]", -1 - $digit, 1);
}

sub _zeros {
    # Return number of trailing zeros in decimal.
    # Check each array element for having 0 at end as long as elem == 0
    # Upon finding a elem != 0, stop.

    my $x = $_[1];

    return 0 if @$x == 1 && $x->[0] == 0;

    my $zeros = 0;
    foreach my $elem (@$x) {
        if ($elem != 0) {
            $elem =~ /[^0](0*)\z/;
            $zeros += length($1);       # count trailing zeros
            last;                       # early out
        }
        $zeros += $BASE_LEN;
    }
    $zeros;
}

##############################################################################
# _is_* routines

sub _is_zero {
    # return true if arg is zero
    @{$_[1]} == 1 && $_[1]->[0] == 0 ? 1 : 0;
}

sub _is_even {
    # return true if arg is even
    $_[1]->[0] % 2 ? 0 : 1;
}

sub _is_odd {
    # return true if arg is odd
    $_[1]->[0] % 2 ? 1 : 0;
}

sub _is_one {
    # return true if arg is one
    @{$_[1]} == 1 && $_[1]->[0] == 1 ? 1 : 0;
}

sub _is_two {
    # return true if arg is two
    @{$_[1]} == 1 && $_[1]->[0] == 2 ? 1 : 0;
}

sub _is_ten {
    # return true if arg is ten
    if ($BASE_LEN == 1) {
        @{$_[1]} == 2 && $_[1]->[0] == 0 && $_[1]->[1] == 1 ? 1 : 0;
    } else {
        @{$_[1]} == 1 && $_[1]->[0] == 10 ? 1 : 0;
    }
}

sub __strip_zeros {
    # Internal normalization function that strips leading zeros from the array.
    # Args: ref to array
    my $x = shift;

    push @$x, 0 if @$x == 0;    # div might return empty results, so fix it
    return $x if @$x == 1;      # early out

    #print "strip: cnt $cnt i $i\n";
    # '0', '3', '4', '0', '0',
    #  0    1    2    3    4
    # cnt = 5, i = 4
    # i = 4
    # i = 3
    # => fcnt = cnt - i (5-2 => 3, cnt => 5-1 = 4, throw away from 4th pos)
    # >= 1: skip first part (this can be zero)

    my $i = $#$x;
    while ($i > 0) {
        last if $x->[$i] != 0;
        $i--;
    }
    $i++;
    splice(@$x, $i) if $i < @$x;
    $x;
}

###############################################################################
# check routine to test internal state for corruptions

sub _check {
    # used by the test suite
    my ($class, $x) = @_;

    my $msg = $class -> SUPER::_check($x);
    return $msg if $msg;

    my $n;
    eval { $n = @$x };
    return "Not an array reference" unless $@ eq '';

    return "Reference to an empty array" unless $n > 0;

    # The following fails with Math::BigInt::FastCalc because a
    # Math::BigInt::FastCalc "object" is an unblessed array ref.
    #
    #return 0 unless ref($x) eq $class;

    for (my $i = 0 ; $i <= $#$x ; ++ $i) {
        my $e = $x -> [$i];

        return "Element at index $i is undefined"
          unless defined $e;

        return "Element at index $i is a '" . ref($e) .
          "', which is not a scalar"
          unless ref($e) eq "";

        # It would be better to use the regex /^([1-9]\d*|0)\z/, but that fails
        # in Math::BigInt::FastCalc, because it sometimes creates array
        # elements like "000000".
        return "Element at index $i is '$e', which does not look like an" .
          " normal integer" unless $e =~ /^\d+\z/;

        return "Element at index $i is '$e', which is not smaller than" .
          " the base '$BASE'" if $e >= $BASE;

        return "Element at index $i (last element) is zero"
          if $#$x > 0 && $i == $#$x && $e == 0;
    }

    return 0;
}

###############################################################################

sub _mod {
    # if possible, use mod shortcut
    my ($c, $x, $yo) = @_;

    # slow way since $y too big
    if (@$yo > 1) {
        my ($xo, $rem) = $c->_div($x, $yo);
        @$x = @$rem;
        return $x;
    }

    my $y = $yo->[0];

    # if both are single element arrays
    if (@$x == 1) {
        $x->[0] %= $y;
        return $x;
    }

    # if @$x has more than one element, but @$y is a single element
    my $b = $BASE % $y;
    if ($b == 0) {
        # when BASE % Y == 0 then (B * BASE) % Y == 0
        # (B * BASE) % $y + A % Y => A % Y
        # so need to consider only last element: O(1)
        $x->[0] %= $y;
    } elsif ($b == 1) {
        # else need to go through all elements in @$x: O(N), but loop is a bit
        # simplified
        my $r = 0;
        foreach (@$x) {
            $r = ($r + $_) % $y; # not much faster, but heh...
            #$r += $_ % $y; $r %= $y;
        }
        $r = 0 if $r == $y;
        $x->[0] = $r;
    } else {
        # else need to go through all elements in @$x: O(N)
        my $r = 0;
        my $bm = 1;
        foreach (@$x) {
            $r = ($_ * $bm + $r) % $y;
            $bm = ($bm * $b) % $y;

            #$r += ($_ % $y) * $bm;
            #$bm *= $b;
            #$bm %= $y;
            #$r %= $y;
        }
        $r = 0 if $r == $y;
        $x->[0] = $r;
    }
    @$x = $x->[0];              # keep one element of @$x
    return $x;
}

##############################################################################
# shifts

sub _rsft {
    my ($c, $x, $n, $b) = @_;
    return $x if $c->_is_zero($x) || $c->_is_zero($n);

    # For backwards compatibility, allow the base $b to be a scalar.

    $b = $c->_new($b) unless ref $b;

    if ($c -> _acmp($b, $c -> _ten())) {
        return scalar $c->_div($x, $c->_pow($c->_copy($b), $n));
    }

    # shortcut (faster) for shifting by 10)
    # multiples of $BASE_LEN
    my $dst = 0;                # destination
    my $src = $c->_num($n);     # as normal int
    my $xlen = (@$x - 1) * $BASE_LEN + length(int($x->[-1]));
    if ($src >= $xlen or ($src == $xlen and !defined $x->[1])) {
        # 12345 67890 shifted right by more than 10 digits => 0
        splice(@$x, 1);         # leave only one element
        $x->[0] = 0;            # set to zero
        return $x;
    }
    my $rem = $src % $BASE_LEN;   # remainder to shift
    $src = int($src / $BASE_LEN); # source
    if ($rem == 0) {
        splice(@$x, 0, $src);   # even faster, 38.4 => 39.3
    } else {
        my $len = @$x - $src;   # elems to go
        my $vd;
        my $z = '0' x $BASE_LEN;
        $x->[ @$x ] = 0;          # avoid || 0 test inside loop
        while ($dst < $len) {
            $vd = $z . $x->[$src];
            $vd = substr($vd, -$BASE_LEN, $BASE_LEN - $rem);
            $src++;
            $vd = substr($z . $x->[$src], -$rem, $rem) . $vd;
            $vd = substr($vd, -$BASE_LEN, $BASE_LEN) if length($vd) > $BASE_LEN;
            $x->[$dst] = int($vd);
            $dst++;
        }
        splice(@$x, $dst) if $dst > 0;       # kill left-over array elems
        pop(@$x) if $x->[-1] == 0 && @$x > 1; # kill last element if 0
    }                                        # else rem == 0
    $x;
}

sub _lsft {
    my ($c, $x, $n, $b) = @_;

    return $x if $c->_is_zero($x) || $c->_is_zero($n);

    # For backwards compatibility, allow the base $b to be a scalar.

    $b = $c->_new($b) unless ref $b;

    # If the base is a power of 10, use shifting, since the internal
    # representation is in base 10eX.

    my $bstr = $c->_str($b);
    if ($bstr =~ /^1(0+)\z/) {

        # Adjust $n so that we're shifting in base 10. Do this by multiplying
        # $n by the base 10 logarithm of $b: $b ** $n = 10 ** (log10($b) * $n).

        my $log10b = length($1);
        $n = $c->_mul($c->_new($log10b), $n);
        $n = $c->_num($n);              # shift-len as normal int

        # $q is the number of places to shift the elements within the array,
        # and $r is the number of places to shift the values within the
        # elements.

        my $r = $n % $BASE_LEN;
        my $q = ($n - $r) / $BASE_LEN;

        # If we must shift the values within the elements ...

        if ($r) {
            my $i = @$x;                # index
            $x->[$i] = 0;               # initialize most significant element
            my $z = '0' x $BASE_LEN;
            my $vd;
            while ($i >= 0) {
                $vd = $x->[$i];
                $vd = $z . $vd;
                $vd = substr($vd, $r - $BASE_LEN, $BASE_LEN - $r);
                $vd .= $i > 0 ? substr($z . $x->[$i - 1], -$BASE_LEN, $r)
                              : '0' x $r;
                $vd = substr($vd, -$BASE_LEN, $BASE_LEN) if length($vd) > $BASE_LEN;
                $x->[$i] = int($vd);    # e.g., "0...048" -> 48 etc.
                $i--;
            }

            pop(@$x) if $x->[-1] == 0;  # if most significant element is zero
        }

        # If we must shift the elements within the array ...

        if ($q) {
            unshift @$x, (0) x $q;
        }

    } else {
        $x = $c->_mul($x, $c->_pow($b, $n));
    }

    return $x;
}

sub _pow {
    # power of $x to $y
    # ref to array, ref to array, return ref to array
    my ($c, $cx, $cy) = @_;

    if (@$cy == 1 && $cy->[0] == 0) {
        splice(@$cx, 1);
        $cx->[0] = 1;           # y == 0 => x => 1
        return $cx;
    }

    if ((@$cx == 1 && $cx->[0] == 1) || #    x == 1
        (@$cy == 1 && $cy->[0] == 1))   # or y == 1
    {
        return $cx;
    }

    if (@$cx == 1 && $cx->[0] == 0) {
        splice (@$cx, 1);
        $cx->[0] = 0;           # 0 ** y => 0 (if not y <= 0)
        return $cx;
    }

    my $pow2 = $c->_one();

    my $y_bin = $c->_as_bin($cy);
    $y_bin =~ s/^0b//;
    my $len = length($y_bin);
    while (--$len > 0) {
        $c->_mul($pow2, $cx) if substr($y_bin, $len, 1) eq '1'; # is odd?
        $c->_mul($cx, $cx);
    }

    $c->_mul($cx, $pow2);
    $cx;
}

sub _nok {
    # Return binomial coefficient (n over k).
    # Given refs to arrays, return ref to array.
    # First input argument is modified.

    my ($c, $n, $k) = @_;

    # If k > n/2, or, equivalently, 2*k > n, compute nok(n, k) as
    # nok(n, n-k), to minimize the number if iterations in the loop.

    {
        my $twok = $c->_mul($c->_two(), $c->_copy($k)); # 2 * k
        if ($c->_acmp($twok, $n) > 0) {               # if 2*k > n
            $k = $c->_sub($c->_copy($n), $k);         # k = n - k
        }
    }

    # Example:
    #
    # / 7 \       7!       1*2*3*4 * 5*6*7   5 * 6 * 7       6   7
    # |   | = --------- =  --------------- = --------- = 5 * - * -
    # \ 3 /   (7-3)! 3!    1*2*3*4 * 1*2*3   1 * 2 * 3       2   3

    if ($c->_is_zero($k)) {
        @$n = 1;
    } else {

        # Make a copy of the original n, since we'll be modifying n in-place.

        my $n_orig = $c->_copy($n);

        # n = 5, f = 6, d = 2 (cf. example above)

        $c->_sub($n, $k);
        $c->_inc($n);

        my $f = $c->_copy($n);
        $c->_inc($f);

        my $d = $c->_two();

        # while f <= n (the original n, that is) ...

        while ($c->_acmp($f, $n_orig) <= 0) {

            # n = (n * f / d) == 5 * 6 / 2 (cf. example above)

            $c->_mul($n, $f);
            $c->_div($n, $d);

            # f = 7, d = 3 (cf. example above)

            $c->_inc($f);
            $c->_inc($d);
        }

    }

    return $n;
}

sub _fac {
    # factorial of $x
    # ref to array, return ref to array
    my ($c, $cx) = @_;

    # We cache the smallest values. Don't assume that a single element has a
    # value larger than 9 or else it won't work with a $BASE_LEN of 1.

    if (@$cx == 1) {
        my @factorials =
          (
           '1',
           '1',
           '2',
           '6',
           '24',
           '120',
           '720',
           '5040',
           '40320',
           '362880',
          );
        if ($cx->[0] <= $#factorials) {
            my $tmp = $c -> _new($factorials[ $cx->[0] ]);
            @$cx = @$tmp;
            return $cx;
        }
    }

    # The old code further below doesn't work for small values of $BASE_LEN.
    # Alas, I have not been able to (or taken the time to) decipher it, so for
    # the case when $BASE_LEN is small, we call the parent class. This code
    # works in for every value of $x and $BASE_LEN. We could use this code for
    # all cases, but it is a little slower than the code further below, so at
    # least for now we keep the code below.

    if ($BASE_LEN <= 2) {
        my $tmp = $c -> SUPER::_fac($cx);
        @$cx = @$tmp;
        return $cx;
    }

    # This code does not work for small values of $BASE_LEN.

    if ((@$cx == 1) &&          # we do this only if $x >= 12 and $x <= 7000
        ($cx->[0] >= 12 && $cx->[0] < 7000)) {

        # Calculate (k-j) * (k-j+1) ... k .. (k+j-1) * (k + j)
        # See http://blogten.blogspot.com/2007/01/calculating-n.html
        # The above series can be expressed as factors:
        #   k * k - (j - i) * 2
        # We cache k*k, and calculate (j * j) as the sum of the first j odd integers

        # This will not work when N exceeds the storage of a Perl scalar, however,
        # in this case the algorithm would be way too slow to terminate, anyway.

        # As soon as the last element of $cx is 0, we split it up and remember
        # how many zeors we got so far. The reason is that n! will accumulate
        # zeros at the end rather fast.
        my $zero_elements = 0;

        # If n is even, set n = n -1
        my $k = $c->_num($cx);
        my $even = 1;
        if (($k & 1) == 0) {
            $even = $k;
            $k --;
        }
        # set k to the center point
        $k = ($k + 1) / 2;
        #  print "k $k even: $even\n";
        # now calculate k * k
        my $k2 = $k * $k;
        my $odd = 1;
        my $sum = 1;
        my $i = $k - 1;
        # keep reference to x
        my $new_x = $c->_new($k * $even);
        @$cx = @$new_x;
        if ($cx->[0] == 0) {
            $zero_elements ++;
            shift @$cx;
        }
        #  print STDERR "x = ", $c->_str($cx), "\n";
        my $BASE2 = int(sqrt($BASE))-1;
        my $j = 1;
        while ($j <= $i) {
            my $m = ($k2 - $sum);
            $odd += 2;
            $sum += $odd;
            $j++;
            while ($j <= $i && ($m < $BASE2) && (($k2 - $sum) < $BASE2)) {
                $m *= ($k2 - $sum);
                $odd += 2;
                $sum += $odd;
                $j++;
                #      print STDERR "\n k2 $k2 m $m sum $sum odd $odd\n"; sleep(1);
            }
            if ($m < $BASE) {
                $c->_mul($cx, [$m]);
            } else {
                $c->_mul($cx, $c->_new($m));
            }
            if ($cx->[0] == 0) {
                $zero_elements ++;
                shift @$cx;
            }
            #    print STDERR "Calculate $k2 - $sum = $m (x = ", $c->_str($cx), ")\n";
        }
        # multiply in the zeros again
        unshift @$cx, (0) x $zero_elements;
        return $cx;
    }

    # go forward until $base is exceeded limit is either $x steps (steps == 100
    # means a result always too high) or $base.
    my $steps = 100;
    $steps = $cx->[0] if @$cx == 1;
    my $r = 2;
    my $cf = 3;
    my $step = 2;
    my $last = $r;
    while ($r * $cf < $BASE && $step < $steps) {
        $last = $r;
        $r *= $cf++;
        $step++;
    }
    if ((@$cx == 1) && $step == $cx->[0]) {
        # completely done, so keep reference to $x and return
        $cx->[0] = $r;
        return $cx;
    }

    # now we must do the left over steps
    my $n;                      # steps still to do
    if (@$cx == 1) {
        $n = $cx->[0];
    } else {
        $n = $c->_copy($cx);
    }

    # Set $cx to the last result below $BASE (but keep ref to $x)
    $cx->[0] = $last;
    splice (@$cx, 1);
    # As soon as the last element of $cx is 0, we split it up and remember
    # how many zeors we got so far. The reason is that n! will accumulate
    # zeros at the end rather fast.
    my $zero_elements = 0;

    # do left-over steps fit into a scalar?
    if (ref $n eq 'ARRAY') {
        # No, so use slower inc() & cmp()
        # ($n is at least $BASE here)
        my $base_2 = int(sqrt($BASE)) - 1;
        #print STDERR "base_2: $base_2\n";
        while ($step < $base_2) {
            if ($cx->[0] == 0) {
                $zero_elements ++;
                shift @$cx;
            }
            my $b = $step * ($step + 1);
            $step += 2;
            $c->_mul($cx, [$b]);
        }
        $step = [$step];
        while ($c->_acmp($step, $n) <= 0) {
            if ($cx->[0] == 0) {
                $zero_elements ++;
                shift @$cx;
            }
            $c->_mul($cx, $step);
            $c->_inc($step);
        }
    } else {
        # Yes, so we can speed it up slightly

        #    print "# left over steps $n\n";

        my $base_4 = int(sqrt(sqrt($BASE))) - 2;
        #print STDERR "base_4: $base_4\n";
        my $n4 = $n - 4;
        while ($step < $n4 && $step < $base_4) {
            if ($cx->[0] == 0) {
                $zero_elements ++;
                shift @$cx;
            }
            my $b = $step * ($step + 1);
            $step += 2;
            $b *= $step * ($step + 1);
            $step += 2;
            $c->_mul($cx, [$b]);
        }
        my $base_2 = int(sqrt($BASE)) - 1;
        my $n2 = $n - 2;
        #print STDERR "base_2: $base_2\n";
        while ($step < $n2 && $step < $base_2) {
            if ($cx->[0] == 0) {
                $zero_elements ++;
                shift @$cx;
            }
            my $b = $step * ($step + 1);
            $step += 2;
            $c->_mul($cx, [$b]);
        }
        # do what's left over
        while ($step <= $n) {
            $c->_mul($cx, [$step]);
            $step++;
            if ($cx->[0] == 0) {
                $zero_elements ++;
                shift @$cx;
            }
        }
    }
    # multiply in the zeros again
    unshift @$cx, (0) x $zero_elements;
    $cx;                        # return result
}

sub _log_int {
    # calculate integer log of $x to base $base
    # ref to array, ref to array - return ref to array
    my ($c, $x, $base) = @_;

    # X == 0 => NaN
    return if @$x == 1 && $x->[0] == 0;

    # BASE 0 or 1 => NaN
    return if @$base == 1 && $base->[0] < 2;

    # X == 1 => 0 (is exact)
    if (@$x == 1 && $x->[0] == 1) {
        @$x = 0;
        return $x, 1;
    }

    my $cmp = $c->_acmp($x, $base);

    # X == BASE => 1 (is exact)
    if ($cmp == 0) {
        @$x = 1;
        return $x, 1;
    }

    # 1 < X < BASE => 0 (is truncated)
    if ($cmp < 0) {
        @$x = 0;
        return $x, 0;
    }

    my $x_org = $c->_copy($x);  # preserve x

    # Compute a guess for the result based on:
    # $guess = int ( length_in_base_10(X) / ( log(base) / log(10) ) )
    my $len = $c->_len($x_org);
    my $log = log($base->[-1]) / log(10);

    # for each additional element in $base, we add $BASE_LEN to the result,
    # based on the observation that log($BASE, 10) is BASE_LEN and
    # log(x*y) == log(x) + log(y):
    $log += (@$base - 1) * $BASE_LEN;

    # calculate now a guess based on the values obtained above:
    my $res = $c->_new(int($len / $log));

    @$x = @$res;
    my $trial = $c->_pow($c->_copy($base), $x);
    my $acmp = $c->_acmp($trial, $x_org);

    # Did we get the exact result?

    return $x, 1 if $acmp == 0;

    # Too small?

    while ($acmp < 0) {
        $c->_mul($trial, $base);
        $c->_inc($x);
        $acmp = $c->_acmp($trial, $x_org);
    }

    # Too big?

    while ($acmp > 0) {
        $c->_div($trial, $base);
        $c->_dec($x);
        $acmp = $c->_acmp($trial, $x_org);
    }

    return $x, 1 if $acmp == 0;         # result is exact
    return $x, 0;                       # result is too small
}

sub _ilog2 {
    # calculate int(log2($x))

    # There is virtually nothing to gain from computing this any differently
    # than _log_int(), but it is important that we don't use the method
    # inherited from the parent, because that method is very slow for backend
    # libraries whose internal representation uses base 10.

    my ($c, $x) = @_;
    ($x, my $is_exact) = $c -> _log_int($x, $c -> _two());
    return wantarray ? ($x, $is_exact) : $x;
}

sub _ilog10 {
    # calculate int(log10($x))

    my ($c, $x) = @_;

    # X == 0 => NaN
    return if @$x == 1 && $x->[0] == 0;

    # X == 1 => 0 (is exact)
    if (@$x == 1 && $x->[0] == 1) {
        @$x = 0;
        return wantarray ? ($x, 1) : $x;
    }

    my $x_orig = $c -> _copy($x);
    my $nm1 = $c -> _len($x) - 1;

    my $xtmp = $c -> _new($nm1);
    @$x = @$xtmp;

    return $x unless wantarray;

    # See if the original $x is an exact power of 10, in which case all but the
    # most significan chunks are 0, and the most significant chunk is a power
    # of 10.

    my $is_pow10 = 1;
    for my $i (0 .. $#$x_orig - 1) {
        last unless $is_pow10 = $x_orig->[$i] == 0;
    }
    $is_pow10 &&= $x_orig->[-1] == 10**int(0.5 + log($x_orig->[-1]) / log(10));

    return wantarray ? ($x, 1) : $x if $is_pow10;
    return wantarray ? ($x, 0) : $x;
}

sub _clog2 {
    # calculate ceil(log2($x))

    my ($c, $x) = @_;

    # X == 0 => NaN

    return if @$x == 1 && $x->[0] == 0;

    # X == 1 => 0 (is exact)

    if (@$x == 1 && $x->[0] == 1) {
        @$x = 0;
        return wantarray ? ($x, 1) : $x;
    }

    my $base = $c -> _two();
    my $acmp = $c -> _acmp($x, $base);

    # X == BASE => 1 (is exact)

    if ($acmp == 0) {
        @$x = 1;
        return wantarray ? ($x, 1) : $x;
    }

    # 1 < X < BASE => 0 (is truncated)

    if ($acmp < 0) {
        @$x = 0;
        return wantarray ? ($x, 0) : $x;
    }

    # Compute a guess for the result based on:
    # $guess = int( length_in_base_10(X) / (log(base) / log(10)) )

    my $len    = $c -> _len($x);
    my $log    = log(2) / log(10);
    my $guess  = $c -> _new(int($len / $log));
    my $x_orig = $c -> _copy($x);
    @$x = @$guess;

    my $trial = $c -> _pow($c -> _copy($base), $x);
    $acmp = $c -> _acmp($trial, $x_orig);

    # Too big?

    while ($acmp > 0) {
        $c -> _div($trial, $base);
        $c -> _dec($x);
        $acmp = $c -> _acmp($trial, $x_orig);
    }

    # Too small?

    while ($acmp < 0) {
        $c -> _mul($trial, $base);
        $c -> _inc($x);
        $acmp = $c -> _acmp($trial, $x_orig);
    }

    return wantarray ? ($x, 1) : $x if $acmp == 0;      # result is exact
    return wantarray ? ($x, 0) : $x;                    # result is too small
}

sub _clog10 {
    # calculate ceil(log2($x))
    my ($c, $x) = @_;

    # X == 0 => NaN
    return if @$x == 1 && $x->[0] == 0;

    # X == 1 => 0 (is exact)
    if (@$x == 1 && $x->[0] == 1) {
        @$x = 0;
        return wantarray ? ($x, 1) : $x;
    }

    # Get the number of base 10 digits. $n is the desired output, except when
    # $x is an exact power of 10, in which case $n is 1 too big.

    my $n = $c -> _len($x);

    # See if $x is an exact power of 10, in which case all but the most
    # significan chunks are 0, and the most significant chunk is a power of 10.

    my $is_pow10 = 1;
    for my $i (0 .. $#$x - 1) {
        last unless $is_pow10 = $x->[$i] == 0;
    }
    $is_pow10 &&= $x->[-1] == 10**int(0.5 + log($x->[-1]) / log(10));

    $n-- if $is_pow10;

    my $xtmp = $c ->_new($n);
    @$x = @$xtmp;

    return wantarray ? ($x, 1) : $x if $is_pow10;       # result is exact
    return wantarray ? ($x, 0) : $x;                    # result is too small
}

# for debugging:
use constant DEBUG => 0;
my $steps = 0;
sub steps { $steps };

sub _sqrt {
    # square-root of $x in-place

    my ($c, $x) = @_;

    if (@$x == 1) {
        # fits into one Perl scalar, so result can be computed directly
        $x->[0] = int(sqrt($x->[0]));
        return $x;
    }

    # Create an initial guess for the square root.

    my $s;
    if (@$x % 2) {
        $s = [ (0) x ((@$x - 1) / 2), int(sqrt($x->[-1])) ];
    } else {
        $s = [ (0) x ((@$x - 2) / 2), int(sqrt($x->[-2] + $x->[-1] * $BASE)) ];
    }

    # Newton's method for the square root of y:
    #
    #                      x(n) * x(n) - y
    #     x(n+1) = x(n) - -----------------
    #                          2 * x(n)

    my $cmp;
    while (1) {
        my $sq = $c -> _mul($c -> _copy($s), $s);
        $cmp = $c -> _acmp($sq, $x);

        # If x(n)*x(n) > y, compute
        #
        #                      x(n) * x(n) - y
        #     x(n+1) = x(n) - -----------------
        #                          2 * x(n)

        if ($cmp > 0) {
            my $num = $c -> _sub($c -> _copy($sq), $x);
            my $den = $c -> _mul($c -> _two(), $s);
            my $delta = $c -> _div($num, $den);
            last if $c -> _is_zero($delta);
            $s = $c -> _sub($s, $delta);
        }

        # If x(n)*x(n) < y, compute
        #
        #                      y - x(n) * x(n)
        #     x(n+1) = x(n) + -----------------
        #                          2 * x(n)

        elsif ($cmp < 0) {
            my $num = $c -> _sub($c -> _copy($x), $sq);
            my $den = $c -> _mul($c -> _two(), $s);
            my $delta = $c -> _div($num, $den);
            last if $c -> _is_zero($delta);
            $s = $c -> _add($s, $delta);
        }

        # If x(n)*x(n) = y, we have the exact result.

        else {
            last;
        }
    }

    $s = $c -> _dec($s) if $cmp > 0;    # never overshoot
    @$x = @$s;
    return $x;
}

sub _root {
    # Take n'th root of $x in place.

    my ($c, $x, $n) = @_;

    # Small numbers.

    if (@$x == 1) {
        return $x if $x -> [0] == 0 || $x -> [0] == 1;

        if (@$n == 1) {
            # Result can be computed directly. Adjust initial result for
            # numerical errors, e.g., int(1000**(1/3)) is 2, not 3.
            my $y = int($x->[0] ** (1 / $n->[0]));
            my $yp1 = $y + 1;
            $y = $yp1 if $yp1 ** $n->[0] == $x->[0];
            $x->[0] = $y;
            return $x;
        }
    }

    # If x <= n, the result is always (truncated to) 1.

    if ((@$x > 1 || $x -> [0] > 0) &&           # if x is non-zero ...
        $c -> _acmp($x, $n) <= 0)               # ... and x <= n
    {
        my $one = $c -> _one();
        @$x = @$one;
        return $x;
    }

    # If $n is a power of two, take sqrt($x) repeatedly, e.g., root($x, 4) =
    # sqrt(sqrt($x)), root($x, 8) = sqrt(sqrt(sqrt($x))).

    my $b = $c -> _as_bin($n);
    if ($b =~ /0b1(0+)$/) {
        my $count = length($1);       # 0b100 => len('00') => 2
        my $cnt = $count;             # counter for loop
        unshift @$x, 0;               # add one element, together with one
                                      #   more below in the loop this makes 2
        while ($cnt-- > 0) {
            # 'Inflate' $x by adding one element, basically computing
            # $x * $BASE * $BASE. This gives us more $BASE_LEN digits for
            # result since len(sqrt($X)) approx == len($x) / 2.
            unshift @$x, 0;
            # Calculate sqrt($x), $x is now one element to big, again. In the
            # next round we make that two, again.
            $c -> _sqrt($x);
        }

        # $x is now one element too big, so truncate result by removing it.
        shift @$x;

        return $x;
    }

    my $DEBUG = 0;

    # Now the general case. This works by finding an initial guess. If this
    # guess is incorrect, a relatively small delta is chosen. This delta is
    # used to find a lower and upper limit for the correct value. The delta is
    # doubled in each iteration. When a lower and upper limit is found,
    # bisection is applied to narrow down the region until we have the correct
    # value.

    # Split x into mantissa and exponent in base 10, so that
    #
    #   x = xm * 10^xe, where 0 < xm < 1 and xe is an integer

    my $x_str = $c -> _str($x);
    my $xm    = "." . $x_str;
    my $xe    = length($x_str);

    # From this we compute the base 10 logarithm of x
    #
    #   log_10(x) = log_10(xm) + log_10(xe^10)
    #             = log(xm)/log(10) + xe
    #
    # and then the base 10 logarithm of y, where y = x^(1/n)
    #
    #   log_10(y) = log_10(x)/n

    my $log10x = log($xm) / log(10) + $xe;
    my $log10y = $log10x / $c -> _num($n);

    # And from this we compute ym and ye, the mantissa and exponent (in
    # base 10) of y, where 1 < ym <= 10 and ye is an integer.

    my $ye = int $log10y;
    my $ym = 10 ** ($log10y - $ye);

    # Finally, we scale the mantissa and exponent to incraese the integer
    # part of ym, before building the string representing our guess of y.

    if ($DEBUG) {
        print "\n";
        print "xm     = $xm\n";
        print "xe     = $xe\n";
        print "log10x = $log10x\n";
        print "log10y = $log10y\n";
        print "ym     = $ym\n";
        print "ye     = $ye\n";
        print "\n";
    }

    my $d = $ye < 15 ? $ye : 15;
    $ym *= 10 ** $d;
    $ye -= $d;

    my $y_str = sprintf('%.0f', $ym) . "0" x $ye;
    my $y = $c -> _new($y_str);

    if ($DEBUG) {
        print "ym     = $ym\n";
        print "ye     = $ye\n";
        print "\n";
        print "y_str  = $y_str (initial guess)\n";
        print "\n";
    }

    # See if our guess y is correct.

    my $trial = $c -> _pow($c -> _copy($y), $n);
    my $acmp  = $c -> _acmp($trial, $x);

    if ($acmp == 0) {
        @$x = @$y;
        return $x;
    }

    # Find a lower and upper limit for the correct value of y. Start off with a
    # delta value that is approximately the size of the accuracy of the guess.

    my $lower;
    my $upper;

    my $delta = $c -> _new("1" . ("0" x $ye));
    my $two   = $c -> _two();

    if ($acmp < 0) {
        $lower = $y;
        while ($acmp < 0) {
            $upper = $c -> _add($c -> _copy($lower), $delta);

            if ($DEBUG) {
                print "lower  = $lower\n";
                print "upper  = $upper\n";
                print "delta  = $delta\n";
                print "\n";
            }
            $acmp  = $c -> _acmp($c -> _pow($c -> _copy($upper), $n), $x);
            if ($acmp == 0) {
                @$x = @$upper;
                return $x;
            }
            $delta = $c -> _mul($delta, $two);
        }
    }

    elsif ($acmp > 0) {
        $upper = $y;
        while ($acmp > 0) {
            if ($c -> _acmp($upper, $delta) <= 0) {
                $lower = $c -> _zero();
                last;
            }
            $lower = $c -> _sub($c -> _copy($upper), $delta);

            if ($DEBUG) {
                print "lower  = $lower\n";
                print "upper  = $upper\n";
                print "delta  = $delta\n";
                print "\n";
            }
            $acmp  = $c -> _acmp($c -> _pow($c -> _copy($lower), $n), $x);
            if ($acmp == 0) {
                @$x = @$lower;
                return $x;
            }
            $delta = $c -> _mul($delta, $two);
        }
    }

    # Use bisection to narrow down the interval.

    my $one = $c -> _one();
    {

        $delta = $c -> _sub($c -> _copy($upper), $lower);
        if ($c -> _acmp($delta, $one) <= 0) {
            @$x = @$lower;
            return $x;
        }

        if ($DEBUG) {
            print "lower  = $lower\n";
            print "upper  = $upper\n";
            print "delta   = $delta\n";
            print "\n";
        }

        $delta = $c -> _div($delta, $two);
        my $middle = $c -> _add($c -> _copy($lower), $delta);

        $acmp  = $c -> _acmp($c -> _pow($c -> _copy($middle), $n), $x);
        if ($acmp < 0) {
            $lower = $middle;
        } elsif ($acmp > 0) {
            $upper = $middle;
        } else {
            @$x = @$middle;
            return $x;
        }

        redo;
    }

    $x;
}

##############################################################################
# binary stuff

sub _and {
    my ($c, $x, $y) = @_;

    # the shortcut makes equal, large numbers _really_ fast, and makes only a
    # very small performance drop for small numbers (e.g. something with less
    # than 32 bit) Since we optimize for large numbers, this is enabled.
    return $x if $c->_acmp($x, $y) == 0; # shortcut

    my $m = $c->_one();
    my ($xr, $yr);
    my $mask = $AND_MASK;

    my $x1 = $c->_copy($x);
    my $y1 = $c->_copy($y);
    my $z  = $c->_zero();

    use integer;
    until ($c->_is_zero($x1) || $c->_is_zero($y1)) {
        ($x1, $xr) = $c->_div($x1, $mask);
        ($y1, $yr) = $c->_div($y1, $mask);

        $c->_add($z, $c->_mul([ 0 + $xr->[0] & 0 + $yr->[0] ], $m));
        $c->_mul($m, $mask);
    }

    @$x = @$z;
    return $x;
}

sub _xor {
    my ($c, $x, $y) = @_;

    return $c->_zero() if $c->_acmp($x, $y) == 0; # shortcut (see -and)

    my $m = $c->_one();
    my ($xr, $yr);
    my $mask = $XOR_MASK;

    my $x1 = $c->_copy($x);
    my $y1 = $c->_copy($y);      # make copy
    my $z  = $c->_zero();

    use integer;
    until ($c->_is_zero($x1) || $c->_is_zero($y1)) {
        ($x1, $xr) = $c->_div($x1, $mask);
        ($y1, $yr) = $c->_div($y1, $mask);
        # make ints() from $xr, $yr (see _and())
        #$b = 1; $xrr = 0; foreach (@$xr) { $xrr += $_ * $b; $b *= $BASE; }
        #$b = 1; $yrr = 0; foreach (@$yr) { $yrr += $_ * $b; $b *= $BASE; }
        #$c->_add($x, $c->_mul($c->_new($xrr ^ $yrr)), $m) );

        $c->_add($z, $c->_mul([ 0 + $xr->[0] ^ 0 + $yr->[0] ], $m));
        $c->_mul($m, $mask);
    }
    # the loop stops when the shorter of the two numbers is exhausted
    # the remainder of the longer one will survive bit-by-bit, so we simple
    # multiply-add it in
    $c->_add($z, $c->_mul($x1, $m) ) if !$c->_is_zero($x1);
    $c->_add($z, $c->_mul($y1, $m) ) if !$c->_is_zero($y1);

    @$x = @$z;
    return $x;
}

sub _or {
    my ($c, $x, $y) = @_;

    return $x if $c->_acmp($x, $y) == 0; # shortcut (see _and)

    my $m = $c->_one();
    my ($xr, $yr);
    my $mask = $OR_MASK;

    my $x1 = $c->_copy($x);
    my $y1 = $c->_copy($y);      # make copy
    my $z  = $c->_zero();

    use integer;
    until ($c->_is_zero($x1) || $c->_is_zero($y1)) {
        ($x1, $xr) = $c->_div($x1, $mask);
        ($y1, $yr) = $c->_div($y1, $mask);
        # make ints() from $xr, $yr (see _and())
        #    $b = 1; $xrr = 0; foreach (@$xr) { $xrr += $_ * $b; $b *= $BASE; }
        #    $b = 1; $yrr = 0; foreach (@$yr) { $yrr += $_ * $b; $b *= $BASE; }
        #    $c->_add($x, $c->_mul(_new( $c, ($xrr | $yrr) ), $m) );
        $c->_add($z, $c->_mul([ 0 + $xr->[0] | 0 + $yr->[0] ], $m));
        $c->_mul($m, $mask);
    }
    # the loop stops when the shorter of the two numbers is exhausted
    # the remainder of the longer one will survive bit-by-bit, so we simple
    # multiply-add it in
    $c->_add($z, $c->_mul($x1, $m) ) if !$c->_is_zero($x1);
    $c->_add($z, $c->_mul($y1, $m) ) if !$c->_is_zero($y1);

    @$x = @$z;
    return $x;
}

sub _as_hex {
    # convert a decimal number to hex (ref to array, return ref to string)
    my ($c, $x) = @_;

    return "0x0" if @$x == 1 && $x->[0] == 0;

    my $x1 = $c->_copy($x);

    my $x10000 = [ 0x10000 ];

    my $es = '';
    my $xr;
    until (@$x1 == 1 && $x1->[0] == 0) {        # _is_zero()
        ($x1, $xr) = $c->_div($x1, $x10000);
        $es = sprintf('%04x', $xr->[0]) . $es;
    }
    #$es = reverse $es;
    $es =~ s/^0*/0x/;
    return $es;
}

sub _as_bin {
    # convert a decimal number to bin (ref to array, return ref to string)
    my ($c, $x) = @_;

    return "0b0" if @$x == 1 && $x->[0] == 0;

    my $x1 = $c->_copy($x);

    my $x10000 = [ 0x10000 ];

    my $es = '';
    my $xr;

    until (@$x1 == 1 && $x1->[0] == 0) {        # _is_zero()
        ($x1, $xr) = $c->_div($x1, $x10000);
        $es = sprintf('%016b', $xr->[0]) . $es;
    }
    $es =~ s/^0*/0b/;
    return $es;
}

sub _as_oct {
    # convert a decimal number to octal (ref to array, return ref to string)
    my ($c, $x) = @_;

    return "00" if @$x == 1 && $x->[0] == 0;

    my $x1 = $c->_copy($x);

    my $x1000 = [ 1 << 15 ];    # 15 bits = 32768 = 0100000

    my $es = '';
    my $xr;
    until (@$x1 == 1 && $x1->[0] == 0) {        # _is_zero()
        ($x1, $xr) = $c->_div($x1, $x1000);
        $es = sprintf("%05o", $xr->[0]) . $es;
    }
    $es =~ s/^0*/0/;            # excactly one leading zero
    return $es;
}

sub _from_oct {
    # convert a octal number to decimal (string, return ref to array)
    my ($c, $os) = @_;

    my $m = $c->_new(1 << 30);          # 30 bits at a time (<32 bits!)
    my $d = 10;                         # 10 octal digits at a time

    my $mul = $c->_one();
    my $x = $c->_zero();

    my $len = int((length($os) - 1) / $d);      # $d digit parts, w/o the '0'
    my $val;
    my $i = -$d;
    while ($len >= 0) {
        $val = substr($os, $i, $d);             # get oct digits
        $val = CORE::oct($val);
        $i -= $d;
        $len --;
        my $adder = $c -> _new($val);
        $c->_add($x, $c->_mul($adder, $mul)) if $val != 0;
        $c->_mul($mul, $m) if $len >= 0;        # skip last mul
    }
    $x;
}

sub _from_hex {
    # convert a hex number to decimal (string, return ref to array)
    my ($c, $hs) = @_;

    my $m = $c->_new(0x10000000);       # 28 bit at a time (<32 bit!)
    my $d = 7;                          # 7 hexadecimal digits at a time
    my $mul = $c->_one();
    my $x = $c->_zero();

    my $len = int((length($hs) - 2) / $d); # $d digit parts, w/o the '0x'
    my $val;
    my $i = -$d;
    while ($len >= 0) {
        $val = substr($hs, $i, $d);     # get hex digits
        $val =~ s/^0x// if $len == 0; # for last part only because
        $val = CORE::hex($val);       # hex does not like wrong chars
        $i -= $d;
        $len --;
        my $adder = $c->_new($val);
        # if the resulting number was to big to fit into one element, create a
        # two-element version (bug found by Mark Lakata - Thanx!)
        if (CORE::length($val) > $BASE_LEN) {
            $adder = $c->_new($val);
        }
        $c->_add($x, $c->_mul($adder, $mul)) if $val != 0;
        $c->_mul($mul, $m) if $len >= 0; # skip last mul
    }
    $x;
}

sub _from_bin {
    # convert a hex number to decimal (string, return ref to array)
    my ($c, $bs) = @_;

    # instead of converting X (8) bit at a time, it is faster to "convert" the
    # number to hex, and then call _from_hex.

    my $hs = $bs;
    $hs =~ s/^[+-]?0b//;                                # remove sign and 0b
    my $l = length($hs);                                # bits
    $hs = '0' x (8 - ($l % 8)) . $hs if ($l % 8) != 0;  # padd left side w/ 0
    my $h = '0x' . unpack('H*', pack ('B*', $hs));      # repack as hex

    $c->_from_hex($h);
}

##############################################################################
# special modulus functions

sub _modinv {

    # modular multiplicative inverse
    my ($c, $x, $y) = @_;

    # modulo zero
    if ($c->_is_zero($y)) {
        return;
    }

    # modulo one
    if ($c->_is_one($y)) {
        return $c->_zero(), '+';
    }

    my $u = $c->_zero();
    my $v = $c->_one();
    my $a = $c->_copy($y);
    my $b = $c->_copy($x);

    # Euclid's Algorithm for bgcd(), only that we calc bgcd() ($a) and the result
    # ($u) at the same time. See comments in BigInt for why this works.
    my $q;
    my $sign = 1;
    {
        ($a, $q, $b) = ($b, $c->_div($a, $b));          # step 1
        last if $c->_is_zero($b);

        my $t = $c->_add(                               # step 2:
                         $c->_mul($c->_copy($v), $q),   #  t =   v * q
                         $u);                           #      + u
        $u = $v;                                        #  u = v
        $v = $t;                                        #  v = t
        $sign = -$sign;
        redo;
    }

    # if the gcd is not 1, then return NaN
    return unless $c->_is_one($a);

    ($v, $sign == 1 ? '+' : '-');
}

sub _modpow {
    # modulus of power ($x ** $y) % $z
    my ($c, $num, $exp, $mod) = @_;

    # a^b (mod 1) = 0 for all a and b
    if ($c->_is_one($mod)) {
        @$num = 0;
        return $num;
    }

    # 0^a (mod m) = 0 if m != 0, a != 0
    # 0^0 (mod m) = 1 if m != 0
    if ($c->_is_zero($num)) {
        if ($c->_is_zero($exp)) {
            @$num = 1;
        } else {
            @$num = 0;
        }
        return $num;
    }

    # We could do the following, but it doesn't actually save any time. The
    # _copy() is needed in case $num and $mod are the same object.
    #$num = $c->_mod($c->_copy($num), $mod);

    my $acc = $c->_copy($num);
    my $t = $c->_one();

    my $expbin = $c->_to_bin($exp);
    my $len = length($expbin);
    while ($len--) {
        if (substr($expbin, $len, 1) eq '1') {  # if odd
            $t = $c->_mul($t, $acc);
            $t = $c->_mod($t, $mod);
        }
        $acc = $c->_mul($acc, $acc);
        $acc = $c->_mod($acc, $mod);
    }
    @$num = @$t;
    $num;
}

sub _gcd {
    # Greatest common divisor.

    my ($c, $x, $y) = @_;

    # gcd(0, 0) = 0
    # gcd(0, a) = a, if a != 0

    if (@$x == 1 && $x->[0] == 0) {
        if (@$y == 1 && $y->[0] == 0) {
            @$x = 0;
        } else {
            @$x = @$y;
        }
        return $x;
    }

    # Until $y is zero ...

    until (@$y == 1 && $y->[0] == 0) {

        # Compute remainder.

        $c->_mod($x, $y);

        # Swap $x and $y.

        my $tmp = $c->_copy($x);
        @$x = @$y;
        $y = $tmp;              # no deref here; that would modify input $y
    }

    return $x;
}

1;

=pod

=head1 NAME

Math::BigInt::Calc - pure Perl module to support Math::BigInt

=head1 SYNOPSIS

    # to use it with Math::BigInt
    use Math::BigInt lib => 'Calc';

    # to use it with Math::BigFloat
    use Math::BigFloat lib => 'Calc';

    # to use it with Math::BigRat
    use Math::BigRat lib => 'Calc';

    # explicitly set base length and whether to "use integer"
    use Math::BigInt::Calc base_len => 4, use_int => 1;
    use Math::BigInt lib => 'Calc';

=head1 DESCRIPTION

Math::BigInt::Calc inherits from Math::BigInt::Lib.

In this library, the numbers are represented interenally in base B = 10**N,
where N is the largest possible integer that does not cause overflow in the
intermediate computations. The base B elements are stored in an array, with the
least significant element stored in array element zero. There are no leading
zero elements, except a single zero element when the number is zero. For
instance, if B = 10000, the number 1234567890 is represented internally as
[7890, 3456, 12].

=head1 OPTIONS

When the module is loaded, it computes the maximum exponent, i.e., power of 10,
that can be used with and without "use integer" in the computations. The default
is to use this maximum exponent. If the combination of the 'base_len' value and
the 'use_int' value exceeds the maximum value, an error is thrown.

=over 4

=item base_len

The base length can be specified explicitly with the 'base_len' option. The
value must be a positive integer.

    use Math::BigInt::Calc base_len => 4;  # use 10000 as internal base

=item use_int

This option is used to specify whether "use integer" should be used in the
internal computations. The value is interpreted as a boolean value, so use 0 or
"" for false and anything else for true. If the 'base_len' is not specified
together with 'use_int', the current value for the base length is used.

    use Math::BigInt::Calc use_int => 1;   # use "use integer" internally

=back

=head1 METHODS

This overview constains only the methods that are specific to
C<Math::BigInt::Calc>. For the other methods, see L<Math::BigInt::Lib>.

=over 4

=item _base_len()

Specify the desired base length and whether to enable "use integer" in the
computations.

    Math::BigInt::Calc -> _base_len($base_len, $use_int);

Note that it is better to specify the base length and whether to use integers as
options when the module is loaded, for example like this

    use Math::BigInt::Calc base_len => 6, use_int => 1;

=back

=head1 SEE ALSO

L<Math::BigInt::Lib> for a description of the API.

Alternative libraries L<Math::BigInt::FastCalc>, L<Math::BigInt::GMP>,
L<Math::BigInt::Pari>, L<Math::BigInt::GMPz>, and L<Math::BigInt::BitVect>.

Some of the modules that use these libraries L<Math::BigInt>,
L<Math::BigFloat>, and L<Math::BigRat>.

=cut
