use vars qw/@ISA $VERSION/;
@ISA = qw(Exporter);
-$VERSION = '0.22';
+$VERSION = '0.36';
# Package to store unsigned big integers in decimal and do math with them
# The BEGIN block is used to determine which of the two variants gives the
# correct result.
+# Beware of things like:
+# $i = $i * $y + $car; $car = int($i / $MBASE); $i = $i % $MBASE;
+# This works on x86, but fails on ARM (SA1100, iPAQ) due to whoknows what
+# reasons. So, use this instead (slower, but correct):
+# $i = $i * $y + $car; $car = int($i / $MBASE); $i -= $MBASE * $car;
+
##############################################################################
# global constants, flags and accessory
my ($MBASE,$BASE,$RBASE,$BASE_LEN,$MAX_VAL,$BASE_LEN2,$BASE_LEN_SMALL);
my ($AND_BITS,$XOR_BITS,$OR_BITS);
my ($AND_MASK,$XOR_MASK,$OR_MASK);
-my ($LEN_CONVERT);
sub _base_len
{
$MBASE = int("1e".$BASE_LEN_SMALL);
$RBASE = abs('1e-'.$BASE_LEN_SMALL); # see USE_MUL
$MAX_VAL = $MBASE-1;
- $LEN_CONVERT = 0;
- $LEN_CONVERT = 1 if $BASE_LEN_SMALL != $BASE_LEN;
-
+
#print "BASE_LEN: $BASE_LEN MAX_VAL: $MAX_VAL BASE: $BASE RBASE: $RBASE ";
#print "BASE_LEN_SMALL: $BASE_LEN_SMALL MBASE: $MBASE\n";
- if ($caught & 1 != 0)
+ undef &_mul;
+ undef &_div;
+
+ # $caught & 1 != 0 => cannot use MUL
+ # $caught & 2 != 0 => cannot use DIV
+ # The parens around ($caught & 1) were important, indeed, if we would use
+ # & here.
+ if ($caught == 2) # 2
{
- # must USE_MUL
+ # print "# use mul\n";
+ # must USE_MUL since we cannot use DIV
*{_mul} = \&_mul_use_mul;
*{_div} = \&_div_use_mul;
}
- else # $caught must be 2, since it can't be 1 nor 3
+ else # 0 or 1
{
+ # print "# use div\n";
# can USE_DIV instead
*{_mul} = \&_mul_use_div;
*{_div} = \&_div_use_div;
$e = 5 if $^O =~ /^uts/; # UTS get's some special treatment
$e = 5 if $^O =~ /^unicos/; # unicos is also problematic (6 seems to work
# there, but we play safe)
- $e = 8 if $e > 8; # cap, for VMS, OS/390 and other 64 bit systems
+ $e = 5 if $] < 5.006; # cap, for older Perls
+ $e = 7 if $e > 7; # cap, for VMS, OS/390 and other 64 bit systems
+ # 8 fails inside random testsuite, so take 7
# determine how many digits fit into an integer and can be safely added
# together plus carry w/o causing an overflow
- # this below detects 15 on a 64 bit system, because after that it becomes
- # 1e16 and not 1000000 :/ I can make it detect 18, but then I get a lot of
- # test failures. Ugh! (Tomake detect 18: uncomment lines marked with *)
use integer;
- my $bi = 5; # approx. 16 bit
- $num = int('9' x $bi);
- # $num = 99999; # *
- # while ( ($num+$num+1) eq '1' . '9' x $bi) # *
- while ( int($num+$num+1) eq '1' . '9' x $bi)
- {
- $bi++; $num = int('9' x $bi);
- # $bi++; $num *= 10; $num += 9; # *
- }
- $bi--; # back off one step
+
+ ############################################################################
+ # the next block is no longer important
+
+ ## this below detects 15 on a 64 bit system, because after that it becomes
+ ## 1e16 and not 1000000 :/ I can make it detect 18, but then I get a lot of
+ ## test failures. Ugh! (Tomake detect 18: uncomment lines marked with *)
+
+ #my $bi = 5; # approx. 16 bit
+ #$num = int('9' x $bi);
+ ## $num = 99999; # *
+ ## while ( ($num+$num+1) eq '1' . '9' x $bi) # *
+ #while ( int($num+$num+1) eq '1' . '9' x $bi)
+ # {
+ # $bi++; $num = int('9' x $bi);
+ # # $bi++; $num *= 10; $num += 9; # *
+ # }
+ #$bi--; # back off one step
# by setting them equal, we ignore the findings and use the default
# one-size-fits-all approach from former versions
- $bi = $e; # XXX, this should work always
+ my $bi = $e; # XXX, this should work always
__PACKAGE__->_base_len($e,$bi); # set and store
# find out how many bits _and, _or and _xor can take (old default = 16)
# I don't think anybody has yet 128 bit scalars, so let's play safe.
local $^W = 0; # don't warn about 'nonportable number'
- $AND_BITS = 15; $XOR_BITS = 15; $OR_BITS = 15;
+ $AND_BITS = 15; $XOR_BITS = 15; $OR_BITS = 15;
# find max bits, we will not go higher than numberofbits that fit into $BASE
# to make _and etc simpler (and faster for smaller, slower for large numbers)
my $max = 16;
while (2 ** $max < $BASE) { $max++; }
+ {
+ no integer;
+ $max = 16 if $] < 5.006; # older Perls might not take >16 too well
+ }
my ($x,$y,$z);
do {
$AND_BITS++;
}
-##############################################################################
-# convert between the "small" and the "large" representation
-
-sub _to_large
- {
- # take an array in base $BASE_LEN_SMALL and convert it in-place to $BASE_LEN
- my ($c,$x) = @_;
-
-# print "_to_large $BASE_LEN_SMALL => $BASE_LEN\n";
-
- return $x if $LEN_CONVERT == 0 || # nothing to converconvertor
- @$x == 1; # only one element => early out
-
- # 12345 67890 12345 67890 contents
- # to 3 2 1 0 index
- # 123456 7890123 4567890 contents
-
-# # faster variant
-# my @d; my $str = '';
-# my $z = '0' x $BASE_LEN_SMALL;
-# foreach (@$x)
-# {
-# # ... . 04321 . 000321
-# $str = substr($z.$_,-$BASE_LEN_SMALL,$BASE_LEN_SMALL) . $str;
-# if (length($str) > $BASE_LEN)
-# {
-# push @d, substr($str,-$BASE_LEN,$BASE_LEN); # extract one piece
-# substr($str,-$BASE_LEN,$BASE_LEN) = ''; # remove it
-# }
-# }
-# push @d, $str if $str !~ /^0*$/; # extract last piece
-# @$x = @d;
-# $x->[-1] = int($x->[-1]); # strip leading zero
-# $x;
-
- my $ret = "";
- my $l = scalar @$x; # number of parts
- $l --; $ret .= int($x->[$l]); $l--;
- my $z = '0' x ($BASE_LEN_SMALL-1);
- while ($l >= 0)
- {
- $ret .= substr($z.$x->[$l],-$BASE_LEN_SMALL);
- $l--;
- }
- my $str = _new($c,\$ret); # make array
- @$x = @$str; # clobber contents of $x
- $x->[-1] = int($x->[-1]); # strip leading zero
- }
-
-sub _to_small
- {
- # take an array in base $BASE_LEN and convert it in-place to $BASE_LEN_SMALL
- my ($c,$x) = @_;
-
- return $x if $LEN_CONVERT == 0; # nothing to do
- return $x if @$x == 1 && length(int($x->[0])) <= $BASE_LEN_SMALL;
-
- my $d = _str($c,$x);
- my $il = length($$d)-1;
- ## this leaves '00000' instead of int 0 and will be corrected after any op
- # clobber contents of $x
- @$x = reverse(unpack("a" . ($il % $BASE_LEN_SMALL+1)
- . ("a$BASE_LEN_SMALL" x ($il / $BASE_LEN_SMALL)), $$d));
-
- $x->[-1] = int($x->[-1]); # strip leading zero
- }
-
###############################################################################
sub _new
{
# (ref to string) return ref to num_array
- # Convert a number from string format to internal base 100000 format.
- # Assumes normalized value as input.
+ # Convert a number from string format (without sign) to internal base
+ # 1ex format. Assumes normalized value as input.
my $d = $_[1];
my $il = length($$d)-1;
# this leaves '00000' instead of int 0 and will be corrected after any op
sub _two
{
- # create a two (for _pow)
+ # create a two (used internally for shifting)
[ 2 ];
}
{
# (ref to int_num_array, ref to int_num_array)
# routine to add 1 to a base 1eX numbers
- # This routine clobbers up array x, but not y.
+ # This routine modifies array x
my ($c,$x) = @_;
for my $i (@$x)
{
# (ref to int_num_array, ref to int_num_array)
# routine to add 1 to a base 1eX numbers
- # This routine clobbers up array x, but not y.
+ # This routine modifies array x
my ($c,$x) = @_;
my $MAX = $BASE-1; # since MAX_VAL based on MBASE
sub _sub
{
- # (ref to int_num_array, ref to int_num_array)
+ # (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 (X is always greater/equal!) by modifying x in place
+ # subtract Y from X by modifying x in place
my ($c,$sx,$sy,$s) = @_;
my $car = 0; my $i; my $j = 0;
#print "case 1 (swap)\n";
for $i (@$sx)
{
- last unless defined $sy->[$j] || $car;
+ # we can't do an early out if $x is < than $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++;
sub _mul_use_mul
{
- # (BINT, BINT) return nothing
+ # (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) = @_;
- # shortcut for two very short numbers (improved by Nathan Zook)
- # works also if xv and yv are the same reference
- if ((@$xv == 1) && (@$yv == 1))
- {
- if (($xv->[0] *= $yv->[0]) >= $MBASE)
- {
- $xv->[0] = $xv->[0] - ($xv->[1] = int($xv->[0] * $RBASE)) * $MBASE;
- };
- return $xv;
- }
- # shortcut for result == 0
- if ( ((@$xv == 1) && ($xv->[0] == 0)) ||
- ((@$yv == 1) && ($yv->[0] == 0)) )
+ if (@$yv == 1)
{
- @$xv = (0);
+ # 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]) >= $MBASE)
+ {
+ $xv->[0] = $xv->[0] - ($xv->[1] = int($xv->[0] * $RBASE)) * $MBASE;
+ };
+ 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 = int($i * $RBASE); $i -= $car * $MBASE;
+ }
+ 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 = [@$xv] if "$xv" eq "$yv"; # same references?
- if ($LEN_CONVERT != 0)
- {
- $c->_to_small($xv); $c->_to_small($yv);
- }
+ $yv = [@$xv] if $xv == $yv; # same references?
my @prod = (); my ($prod,$car,$cty,$xi,$yi);
$xi = shift @prod || 0; # || 0 makes v5.005_3 happy
}
push @$xv, @prod;
- if ($LEN_CONVERT != 0)
- {
- $c->_to_large($yv);
- $c->_to_large($xv);
- }
- else
- {
- __strip_zeros($xv);
- }
+ __strip_zeros($xv);
$xv;
}
sub _mul_use_div
{
- # (BINT, BINT) return nothing
+ # (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) = @_;
- # shortcut for two very short numbers (improved by Nathan Zook)
- # works also if xv and yv are the same reference
- if ((@$xv == 1) && (@$yv == 1))
- {
- if (($xv->[0] *= $yv->[0]) >= $MBASE)
- {
- $xv->[0] =
- $xv->[0] - ($xv->[1] = int($xv->[0] / $MBASE)) * $MBASE;
- };
- return $xv;
- }
- # shortcut for result == 0
- if ( ((@$xv == 1) && ($xv->[0] == 0)) ||
- ((@$yv == 1) && ($yv->[0] == 0)) )
+ if (@$yv == 1)
{
- @$xv = (0);
+ # shortcut for two small numbers, also handles $x == 0
+ if (@$xv == 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->[0] *= $yv->[0]) >= $MBASE)
+ {
+ $xv->[0] =
+ $xv->[0] - ($xv->[1] = int($xv->[0] / $MBASE)) * $MBASE;
+ };
+ 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 = int($i / $MBASE); $i -= $car * $MBASE;
+ }
+ 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 = [@$xv] if "$xv" eq "$yv"; # same references?
- if ($LEN_CONVERT != 0)
- {
- $c->_to_small($xv); $c->_to_small($yv);
- }
-
+ $yv = [@$xv] if $xv == $yv; # same references?
+
my @prod = (); my ($prod,$car,$cty,$xi,$yi);
for $xi (@$xv)
{
$xi = shift @prod || 0; # || 0 makes v5.005_3 happy
}
push @$xv, @prod;
- if ($LEN_CONVERT != 0)
- {
- $c->_to_large($yv);
- $c->_to_large($xv);
- }
- else
- {
- __strip_zeros($xv);
- }
+ __strip_zeros($xv);
$xv;
}
{
# ref to array, ref to array, modify first array and return remainder if
# in list context
+
+ # see comments in _div_use_div() for more explanations
+
my ($c,$x,$yorg) = @_;
+
+ # the general div algorithmn here is about O(N*N) and thus quite slow, so
+ # we first check for some special cases and use shortcuts to handle them.
+
+ # This works, because we store the numbers in a chunked format where each
+ # element contains 5..7 digits (depending on system).
+ # if both numbers have only one element:
if (@$x == 1 && @$yorg == 1)
{
- # shortcut, $y is smaller than $x
+ # shortcut, $yorg and $x are two small numbers
if (wantarray)
{
my $r = [ $x->[0] % $yorg->[0] ];
}
}
- my $y = [ @$yorg ];
- if ($LEN_CONVERT != 0)
+ # if x has more than one, but y has only one element:
+ if (@$yorg == 1)
{
- $c->_to_small($x); $c->_to_small($y);
+ my $rem;
+ $rem = _mod($c,[ @$x ],$yorg) if wantarray;
+
+ # shortcut, $y is < $BASE
+ my $j = scalar @$x; my $r = 0;
+ my $y = $yorg->[0]; my $b;
+ while ($j-- > 0)
+ {
+ $b = $r * $MBASE + $x->[$j];
+ $x->[$j] = int($b/$y);
+ $r = $b % $y;
+ }
+ pop @$x if @$x > 1 && $x->[-1] == 0; # splice up a leading 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 yet, the result will be 0
+ if (@$yorg > @$x)
+ {
+ my $rem;
+ $rem = [@$x] if wantarray; # make copy
+ splice (@$x,1); # keep ref to original array
+ $x->[0] = 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 $rem;
+ # if $yorg has more digits than $x (it's leading element is longer than
+ # the one from $x), the result will also be 0:
+ if (length(int($yorg->[-1])) > length(int($x->[-1])))
+ {
+ $rem = [@$x] if wantarray; # make copy
+ splice (@$x,1); # keep ref to org array
+ $x->[0] = 0; # set to 0
+ return ($x,$rem) if wantarray; # including remainder?
+ return $x;
+ }
+ # now calculate $x / $yorg
+ if (length(int($yorg->[-1])) == length(int($x->[-1])))
+ {
+ # same length, so make full compare, and if equal, return 1
+ # hm, same lengths, but same contents? So we need to check all parts:
+ my $a = 0; my $j = scalar @$x - 1;
+ # manual way (abort if unequal, good for early ne)
+ while ($j >= 0)
+ {
+ last if ($a = $x->[$j] - $yorg->[$j]); $j--;
+ }
+ # $a contains the result of the compare between X and Y
+ # a < 0: x < y, a == 0 => x == y, a > 0: x > y
+ if ($a <= 0)
+ {
+ if (wantarray)
+ {
+ $rem = [ 0 ]; # a = 0 => x == y => rem 1
+ $rem = [@$x] if $a != 0; # a < 0 => x < y => rem = x
+ }
+ splice(@$x,1); # keep single element
+ $x->[0] = 0; # if $a < 0
+ if ($a == 0)
+ {
+ # $x == $y
+ $x->[0] = 1;
+ }
+ return ($x,$rem) if wantarray;
+ return $x;
+ }
+ # $x >= $y, proceed normally
+ }
+ }
+
+ # all other cases:
+
+ my $y = [ @$yorg ]; # always make copy to preserve
+
my ($car,$bar,$prd,$dd,$xi,$yi,@q,$v2,$v1,@d,$tmp,$q,$u2,$u1,$u0);
$car = $bar = $prd = 0;
$u2 = 0 unless $u2;
#warn "oups v1 is 0, u0: $u0 $y->[-2] $y->[-1] l ",scalar @$y,"\n"
# if $v1 == 0;
- $q = (($u0 == $v1) ? $MAX_VAL : int(($u0*$MBASE+$u1)/$v1));
+ $q = (($u0 == $v1) ? $MAX_VAL : int(($u0*$MBASE+$u1)/$v1));
--$q while ($v2*$q > ($u0*$MBASE+$u1-$q*$v1)*$MBASE+$u2);
if ($q)
{
for ($yi = 0, $xi = $#$x-$#$y-1; $yi <= $#$y; ++$yi,++$xi)
{
$x->[$xi] -= $MBASE
- if ($car = (($x->[$xi] += $y->[$yi] + $car) > $MBASE));
+ if ($car = (($x->[$xi] += $y->[$yi] + $car) >= $MBASE));
}
}
}
- pop(@$x); unshift(@q, $q);
+ pop(@$x);
+ unshift(@q, $q);
}
if (wantarray)
{
}
@$x = @q;
my $d = \@d;
- if ($LEN_CONVERT != 0)
- {
- $c->_to_large($x); $c->_to_large($d);
- }
- else
- {
- __strip_zeros($x);
- __strip_zeros($d);
- }
+ __strip_zeros($x);
+ __strip_zeros($d);
return ($x,$d);
}
@$x = @q;
- if ($LEN_CONVERT != 0)
- {
- $c->_to_large($x);
- }
- else
- {
- __strip_zeros($x);
- }
+ __strip_zeros($x);
$x;
}
# in list context
my ($c,$x,$yorg) = @_;
+ # the general div algorithmn here is about O(N*N) and thus quite slow, so
+ # we first check for some special cases and use shortcuts to handle them.
+
+ # This works, because we store the numbers in a chunked format where each
+ # element contains 5..7 digits (depending on system).
+
+ # if both numbers have only one element:
if (@$x == 1 && @$yorg == 1)
{
- # shortcut, $y is smaller than $x
+ # shortcut, $yorg and $x are two small numbers
if (wantarray)
{
my $r = [ $x->[0] % $yorg->[0] ];
return $x;
}
}
+ # if x has more than one, but y has only one element:
+ if (@$yorg == 1)
+ {
+ my $rem;
+ $rem = _mod($c,[ @$x ],$yorg) if wantarray;
- my $y = [ @$yorg ];
- if ($LEN_CONVERT != 0)
+ # shortcut, $y is < $BASE
+ my $j = scalar @$x; my $r = 0;
+ my $y = $yorg->[0]; my $b;
+ while ($j-- > 0)
+ {
+ $b = $r * $MBASE + $x->[$j];
+ $x->[$j] = int($b/$y);
+ $r = $b % $y;
+ }
+ pop @$x if @$x > 1 && $x->[-1] == 0; # splice up a leading 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 yet, the result will be 0
+ if (@$yorg > @$x)
+ {
+ my $rem;
+ $rem = [@$x] if wantarray; # make copy
+ splice (@$x,1); # keep ref to original array
+ $x->[0] = 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)
{
- $c->_to_small($x); $c->_to_small($y);
+ my $rem;
+ # if $yorg has more digits than $x (it's leading element is longer than
+ # the one from $x), the result will also be 0:
+ if (length(int($yorg->[-1])) > length(int($x->[-1])))
+ {
+ $rem = [@$x] if wantarray; # make copy
+ splice (@$x,1); # keep ref to org array
+ $x->[0] = 0; # set to 0
+ return ($x,$rem) if wantarray; # including remainder?
+ return $x;
+ }
+ # now calculate $x / $yorg
+ if (length(int($yorg->[-1])) == length(int($x->[-1])))
+ {
+ # same length, so make full compare, and if equal, return 1
+ # hm, same lengths, but same contents? So we need to check all parts:
+ my $a = 0; my $j = scalar @$x - 1;
+ # manual way (abort if unequal, good for early ne)
+ while ($j >= 0)
+ {
+ last if ($a = $x->[$j] - $yorg->[$j]); $j--;
+ }
+ # $a contains the result of the compare between X and Y
+ # a < 0: x < y, a == 0 => x == y, a > 0: x > y
+ if ($a <= 0)
+ {
+ if (wantarray)
+ {
+ $rem = [ 0 ]; # a = 0 => x == y => rem 1
+ $rem = [@$x] if $a != 0; # a < 0 => x < y => rem = x
+ }
+ splice(@$x,1); # keep single element
+ $x->[0] = 0; # if $a < 0
+ if ($a == 0)
+ {
+ # $x == $y
+ $x->[0] = 1;
+ }
+ return ($x,$rem) if wantarray;
+ return $x;
+ }
+ # $x >= $y, so proceed normally
+ }
}
+
+ # all other cases:
+
+ my $y = [ @$yorg ]; # always make copy to preserve
my ($car,$bar,$prd,$dd,$xi,$yi,@q,$v2,$v1,@d,$tmp,$q,$u2,$u1,$u0);
{
push(@$x, 0);
}
+
+ # @q will accumulate the final result, $q contains the current computed
+ # part of the final result
+
@q = (); ($v2,$v1) = @$y[-2,-1];
$v2 = 0 unless $v2;
while ($#$x > $#$y)
$u2 = 0 unless $u2;
#warn "oups v1 is 0, u0: $u0 $y->[-2] $y->[-1] l ",scalar @$y,"\n"
# if $v1 == 0;
- $q = (($u0 == $v1) ? $MAX_VAL : int(($u0*$MBASE+$u1)/$v1));
+ $q = (($u0 == $v1) ? $MAX_VAL : int(($u0*$MBASE+$u1)/$v1));
--$q while ($v2*$q > ($u0*$MBASE+$u1-$q*$v1)*$MBASE+$u2);
if ($q)
{
for ($yi = 0, $xi = $#$x-$#$y-1; $yi <= $#$y; ++$yi,++$xi)
{
$x->[$xi] -= $MBASE
- if ($car = (($x->[$xi] += $y->[$yi] + $car) > $MBASE));
+ if ($car = (($x->[$xi] += $y->[$yi] + $car) >= $MBASE));
}
}
}
}
@$x = @q;
my $d = \@d;
- if ($LEN_CONVERT != 0)
- {
- $c->_to_large($x); $c->_to_large($d);
- }
- else
- {
- __strip_zeros($x);
- __strip_zeros($d);
- }
+ __strip_zeros($x);
+ __strip_zeros($d);
return ($x,$d);
}
@$x = @q;
- if ($LEN_CONVERT != 0)
- {
- $c->_to_large($x);
- }
- else
- {
- __strip_zeros($x);
- }
+ __strip_zeros($x);
$x;
}
my ($c,$cx,$cy) = @_;
- # fast comp based on array elements
+ # fast comp based on number of array elements (aka pseudo-length)
my $lxy = scalar @$cx - scalar @$cy;
return -1 if $lxy < 0; # already differs, ret
return 1 if $lxy > 0; # ditto
-
+
# now calculate length based on digits, not parts
- $lxy = _len($c,$cx) - _len($c,$cy); # difference
+ # we need only the length of the last element, since both array have the
+ # same number of parts
+ $lxy = length(int($cx->[-1])) - length(int($cy->[-1]));
return -1 if $lxy < 0;
return 1 if $lxy > 0;
- # hm, same lengths, but same contents?
- my $i = 0; my $a;
- # first way takes 5.49 sec instead of 4.87, but has the early out advantage
- # so grep is slightly faster, but more inflexible. hm. $_ instead of $k
- # yields 5.6 instead of 5.5 sec huh?
+ # hm, same lengths, but same contents? So we need to check all parts:
+ my $a; my $j = scalar @$cx - 1;
# manual way (abort if unequal, good for early ne)
- my $j = scalar @$cx - 1;
while ($j >= 0)
- {
- last if ($a = $cx->[$j] - $cy->[$j]); $j--;
- }
+ {
+ last if ($a = $cx->[$j] - $cy->[$j]); $j--;
+ }
return 1 if $a > 0;
return -1 if $a < 0;
- 0; # equal
-
- # while it early aborts, it is even slower than the manual variant
- #grep { return $a if ($a = $_ - $cy->[$i++]); } @$cx;
- # grep way, go trough all (bad for early ne)
- #grep { $a = $_ - $cy->[$i++]; } @$cx;
- #return $a;
+ 0; # numbers are equal
}
sub _len
{
- # compute number of digits in bigint, minus the sign
+ # compute number of digits
# 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];
- return (@$cx-1)*$BASE_LEN+length(int($cx->[-1]));
+ (@$cx-1)*$BASE_LEN+length(int($cx->[-1]));
}
sub _digit
my $elem = int($n / $BASE_LEN); # which array element
my $digit = $n % $BASE_LEN; # which digit in this element
$elem = '0000'.@$x[$elem]; # get element padded with 0's
- return substr($elem,-$digit-1,1);
+ substr($elem,-$digit-1,1);
}
sub _zeros
my ($xo,$rem) = _div($c,$x,$yo);
return $rem;
}
+
my $y = $yo->[0];
# both are single element arrays
if (scalar @$x == 1)
return $x;
}
- # @y is single element, but @x has more than one
+ # @y is a single element, but @x has more than one element
my $b = $BASE % $y;
if ($b == 0)
{
}
elsif ($b == 1)
{
- # else need to go trough all elements: O(N), but loop is a bit simplified
+ # else need to go trough all elements: O(N), but loop is a bit simplified
my $r = 0;
foreach (@$x)
{
- $r += $_ % $y;
- $r %= $y;
+ $r = ($r + $_) % $y; # not much faster, but heh...
+ #$r += $_ % $y; $r %= $y;
}
$r = 0 if $r == $y;
$x->[0] = $r;
my $r = 0; my $bm = 1;
foreach (@$x)
{
- $r += ($_ % $y) * $bm;
- $bm *= $b;
- $bm %= $y;
- $r %= $y;
+ $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;
# multiples of $BASE_LEN
my $dst = 0; # destination
my $src = _num($c,$y); # as normal int
+ my $xlen = (@$x-1)*$BASE_LEN+length(int($x->[-1])); # len of x in digits
+ 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)
$dst++;
}
splice (@$x,$dst) if $dst > 0; # kill left-over array elems
- pop @$x if $x->[-1] == 0; # kill last element if 0
+ pop @$x if $x->[-1] == 0 && @$x > 1; # kill last element if 0
} # else rem == 0
$x;
}
my ($c,$cx,$cy) = @_;
my $pow2 = _one();
- my $two = _two();
- my $y1 = _copy($c,$cy);
- while (!_is_one($c,$y1))
+
+ my $y_bin = ${_as_bin($c,$cy)}; $y_bin =~ s/^0b//;
+ my $len = length($y_bin);
+ while (--$len > 0)
{
- _mul($c,$pow2,$cx) if _is_odd($c,$y1);
- _div($c,$y1,$two);
+ _mul($c,$pow2,$cx) if substr($y_bin,$len,1) eq '1'; # is odd?
_mul($c,$cx,$cx);
}
- _mul($c,$cx,$pow2) unless _is_one($c,$pow2);
+
+ _mul($c,$cx,$pow2);
$cx;
}
$cx = [$last];
return $cx;
}
- my $n = _copy($c,$cx);
- $cx = [$last];
+ # now we must do the left over steps
- #$cx = _one();
- while (!(@$n == 1 && $n->[0] == $step))
+ # do so as long as n has more than one element
+ my $n = $cx->[0];
+ # 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;
+ $cx = [$last];
+ if (scalar @$cx == 1)
+ {
+ my $n = _copy($c,$cx);
+ # no need to test for $steps, since $steps is a scalar and we stop before
+ while (scalar @$n != 1)
+ {
+ if ($cx->[0] == 0)
+ {
+ $zero_elements ++; shift @$cx;
+ }
+ _mul($c,$cx,$n); _dec($c,$n);
+ }
+ $n = $n->[0]; # "convert" to scalar
+ }
+
+ # the left over steps will fit into a scalar, so we can speed it up
+ while ($n != $step)
+ {
+ if ($cx->[0] == 0)
+ {
+ $zero_elements ++; shift @$cx;
+ }
+ _mul($c,$cx,[$n]); $n--;
+ }
+ # multiply in the zeros again
+ while ($zero_elements-- > 0)
{
- _mul($c,$cx,$n); _dec($c,$n);
+ unshift @$cx, 0;
}
$cx;
}
-use constant DEBUG => 0;
-
-my $steps = 0;
-
-sub steps { $steps };
+# for debugging:
+ use constant DEBUG => 0;
+ my $steps = 0;
+ sub steps { $steps };
sub _sqrt
{
- # square-root of $x
- # ref to array, return ref to array
+ # square-root of $x in place
+ # Compute a guess of the result (by rule of thumb), then improve it via
+ # Newton's method.
my ($c,$x) = @_;
if (scalar @$x == 1)
{
- # fit's into one Perl scalar
+ # fit's into one Perl scalar, so result can be computed directly
$x->[0] = int(sqrt($x->[0]));
return $x;
}
# since our guess will "grow"
my $l = int((_len($c,$x)-1) / 2);
- my $lastelem = $x->[-1]; # for guess
+ my $lastelem = $x->[-1]; # for guess
my $elems = scalar @$x - 1;
# not enough digits, but could have more?
- if ((length($lastelem) <= 3) && ($elems > 1))
+ if ((length($lastelem) <= 3) && ($elems > 1))
{
# right-align with zero pad
my $len = length($lastelem) & 1;
print "$lastelem => " if DEBUG;
$lastelem .= substr($x->[-2] . '0' x $BASE_LEN,0,$BASE_LEN);
# former odd => make odd again, or former even to even again
- $lastelem = $lastelem / 10 if (length($lastelem) & 1) != $len;
+ $lastelem = $lastelem / 10 if (length($lastelem) & 1) != $len;
print "$lastelem\n" if DEBUG;
}
my $r = $l % $BASE_LEN; # 10000 00000 00000 00000 ($BASE_LEN=5)
$l = int($l / $BASE_LEN);
print "l = $l " if DEBUG;
-
- splice @$x,$l; # keep ref($x), but modify it
-
+
+ splice @$x,$l; # keep ref($x), but modify it
+
# we make the first part of the guess not '1000...0' but int(sqrt($lastelem))
# that gives us:
- # 14400 00000 => sqrt(14400) => 120
- # 144000 000000 => sqrt(144000) => 379
+ # 14400 00000 => sqrt(14400) => guess first digits to be 120
+ # 144000 000000 => sqrt(144000) => guess 379
- # $x->[$l--] = int('1' . '0' x $r); # old way of guessing
print "$lastelem (elems $elems) => " if DEBUG;
$lastelem = $lastelem / 10 if ($elems & 1 == 1); # odd or even?
my $g = sqrt($lastelem); $g =~ s/\.//; # 2.345 => 2345
$x->[$l--] = int(substr($g . '0' x $r,0,$r+1));
print "now ",$x->[-1] if DEBUG;
print " would have been ", int('1' . '0' x $r),"\n" if DEBUG;
-
+
# If @$x > 1, we could compute the second elem of the guess, too, to create
- # an even better guess. Not implemented yet.
+ # an even better guess. Not implemented yet. Does it improve performance?
$x->[$l--] = 0 while ($l >= 0); # all other digits of guess are zero
-
+
print "start x= ",${_str($c,$x)},"\n" if DEBUG;
my $two = _two();
my $last = _zero();
$last = _copy($c,$x);
_add($c,$x, _div($c,_copy($c,$y),$x));
_div($c,$x, $two );
- print " x= ",${_str($c,$x)},"\n" if DEBUG;
+ print " x= ",${_str($c,$x)},"\n" if DEBUG;
}
print "\nsteps in sqrt: $steps, " if DEBUG;
_dec($c,$x) if _acmp($c,$y,_mul($c,_copy($c,$x),$x)) < 0; # overshot?
$x;
}
+sub _root
+ {
+ # take n'th root of $x in place (n >= 3)
+ my ($c,$x,$n) = @_;
+
+ if (scalar @$x == 1)
+ {
+ if (scalar @$n > 1)
+ {
+ # result will always be smaller than 2 so trunc to 1 at once
+ $x->[0] = 1;
+ }
+ else
+ {
+ # fit's into one Perl scalar, so result can be computed directly
+ $x->[0] = int( $x->[0] ** (1 / $n->[0]) );
+ }
+ return $x;
+ }
+
+ # X is more than one element
+ # if $n is a power of two, we can repeatedly take sqrt($X) and find the
+ # proper result, because sqrt(sqrt($x)) == root($x,4)
+ my $b = _as_bin($c,$n);
+ if ($$b =~ /0b1(0+)/)
+ {
+ my $count = CORE::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.
+ _sqrt($c,$x);
+ }
+ # $x is now one element to big, so truncate result by removing it
+ splice (@$x,0,1);
+ }
+ else
+ {
+ # Should compute a guess of the result (by rule of thumb), then improve it
+ # via Newton's method or something similiar.
+ # XXX TODO
+ warn ('_root() not fully implemented in Calc.');
+ }
+ $x;
+ }
+
##############################################################################
# binary stuff
# convert a decimal number to hex (ref to array, return ref to string)
my ($c,$x) = @_;
+ # fit's into one element
+ if (@$x == 1)
+ {
+ my $t = '0x' . sprintf("%x",$x->[0]);
+ return \$t;
+ }
+
my $x1 = _copy($c,$x);
my $es = '';
- my $xr;
- my $x10000 = [ 0x10000 ];
+ my ($xr, $h, $x10000);
+ if ($] >= 5.006)
+ {
+ $x10000 = [ 0x10000 ]; $h = 'h4';
+ }
+ else
+ {
+ $x10000 = [ 0x1000 ]; $h = 'h3';
+ }
while (! _is_zero($c,$x1))
{
($x1, $xr) = _div($c,$x1,$x10000);
- $es .= unpack('h4',pack('v',$xr->[0]));
+ $es .= unpack($h,pack('v',$xr->[0]));
}
$es = reverse $es;
$es =~ s/^[0]+//; # strip leading zeros
# convert a decimal number to bin (ref to array, return ref to string)
my ($c,$x) = @_;
+ # fit's into one element
+ if (@$x == 1)
+ {
+ my $t = '0b' . sprintf("%b",$x->[0]);
+ return \$t;
+ }
my $x1 = _copy($c,$x);
my $es = '';
- my $xr;
- my $x10000 = [ 0x10000 ];
+ my ($xr, $b, $x10000);
+ if ($] >= 5.006)
+ {
+ $x10000 = [ 0x10000 ]; $b = 'b16';
+ }
+ else
+ {
+ $x10000 = [ 0x1000 ]; $b = 'b12';
+ }
while (! _is_zero($c,$x1))
{
($x1, $xr) = _div($c,$x1,$x10000);
- $es .= unpack('b16',pack('v',$xr->[0]));
+ $es .= unpack($b,pack('v',$xr->[0]));
}
$es = reverse $es;
$es =~ s/^[0]+//; # strip leading zeros
# convert a hex number to decimal (ref to string, return ref to array)
my ($c,$bs) = @_;
+ # instead of converting 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 = unpack('H*', pack ('B*', $hs)); # repack as hex
+ return $c->_from_hex(\('0x'.$h));
+
my $mul = _one();
my $m = [ 0x100 ]; # 8 bit at a time
my $x = _zero();
}
##############################################################################
+# special modulus functions
+
+sub _modinv
+ {
+ # modular inverse
+ my ($c,$x,$y) = @_;
+
+ my $u = _zero($c); my $u1 = _one($c);
+ my $a = _copy($c,$y); my $b = _copy($c,$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;
+ ($a, $q, $b) = ($b, _div($c,$a,$b)); # step 1
+ my $sign = 1;
+ while (!_is_zero($c,$b))
+ {
+ my $t = _add($c, # step 2:
+ _mul($c,_copy($c,$u1), $q) , # t = u1 * q
+ $u ); # + u
+ $u = $u1; # u = u1, u1 = t
+ $u1 = $t;
+ $sign = -$sign;
+ ($a, $q, $b) = ($b, _div($c,$a,$b)); # step 1
+ }
+
+ # if the gcd is not 1, then return NaN
+ return (undef,undef) unless _is_one($c,$a);
+
+ $sign = $sign == 1 ? '+' : '-';
+ ($u1,$sign);
+ }
+
+sub _modpow
+ {
+ # modulus of power ($x ** $y) % $z
+ my ($c,$num,$exp,$mod) = @_;
+
+ # in the trivial case,
+ if (_is_one($c,$mod))
+ {
+ splice @$num,0,1; $num->[0] = 0;
+ return $num;
+ }
+ if ((scalar @$num == 1) && (($num->[0] == 0) || ($num->[0] == 1)))
+ {
+ $num->[0] = 1;
+ return $num;
+ }
+
+# $num = _mod($c,$num,$mod); # this does not make it faster
+
+ my $acc = _copy($c,$num); my $t = _one();
+
+ my $expbin = ${_as_bin($c,$exp)}; $expbin =~ s/^0b//;
+ my $len = length($expbin);
+ while (--$len >= 0)
+ {
+ if ( substr($expbin,$len,1) eq '1') # is_odd
+ {
+ _mul($c,$t,$acc);
+ $t = _mod($c,$t,$mod);
+ }
+ _mul($c,$acc,$acc);
+ $acc = _mod($c,$acc,$mod);
+ }
+ @$num = @$t;
+ $num;
+ }
+
+##############################################################################
##############################################################################
1;
Provides support for big integer calculations. Not intended to be used by other
modules (except Math::BigInt::Cached). Other modules which sport the same
-functions can also be used to support Math::Bigint, like Math::BigInt::Pari.
+functions can also be used to support Math::BigInt, like Math::BigInt::Pari.
=head1 DESCRIPTION
'libname' is either the long name ('Math::BigInt::Pari'), or only the short
version like 'Pari'.
-=head1 EXPORT
+=head1 STORAGE
+
+=head1 METHODS
The following functions MUST be defined in order to support the use by
Math::BigInt:
In list context, returns (result,remainder).
NOTE: this is integer math, so no
fractional part will be returned.
+ The second operand will be not be 0, so no need to
+ check for that.
_sub(obj,obj) Simple subtraction of 1 object from another
a third, optional parameter indicates that the params
are swapped. In this case, the first param needs to
_or(obj1,obj2) OR (bit-wise) object 1 with object 2
_mod(obj,obj) Return remainder of div of the 1st by the 2nd object
- _sqrt(obj) return the square root of object (truncate to int)
+ _sqrt(obj) return the square root of object (truncated to int)
+ _root(obj) return the n'th (n >= 3) root of obj (truncated to int)
_fac(obj) return factorial of object 1 (1*2*3*4..)
_pow(obj,obj) return object 1 to the power of object 2
_gcd(obj,obj) return Greatest Common Divisor of two objects
_zeros(obj) return number of trailing decimal zeros
+ _modinv return inverse modulus
+ _modpow return modulus of power ($x ** $y) % $z
Input strings come in as unsigned but with prefix (i.e. as '123', '0xabc'
or '0b1101').
-Testing of input parameter validity is done by the caller, so you need not
-worry about underflow (f.i. in C<_sub()>, C<_dec()>) nor about division by
-zero or similar cases.
+So the library needs only to deal with unsigned big integers. Testing of input
+parameter validity is done by the caller, so you need not worry about
+underflow (f.i. in C<_sub()>, C<_dec()>) nor about division by zero or similar
+cases.
The first parameter can be modified, that includes the possibility that you
return a reference to a completely different object instead. Although keeping
the reference and just changing it's contents is prefered over creating and
returning a different reference.
-Return values are always references to objects or strings. Exceptions are
-C<_lsft()> and C<_rsft()>, which return undef if they can not shift the
-argument. This is used to delegate shifting of bases different than the one
-you can support back to Math::BigInt, which will use some generic code to
-calculate the result.
+Return values are always references to objects, strings, or true/false for
+comparisation routines.
+
+Exceptions are C<_lsft()> and C<_rsft()>, which return undef if they can not
+shift the argument. This is used to delegate shifting of bases different than
+the one you can support back to Math::BigInt, which will use some generic code
+to calculate the result.
=head1 WRAP YOUR OWN
=head1 AUTHORS
Original math code by Mark Biggar, rewritten by Tels L<http://bloodgate.com/>
-in late 2000, 2001.
+in late 2000.
Seperated from BigInt and shaped API with the help of John Peacock.
+Fixed/enhanced by Tels 2001-2002.
=head1 SEE ALSO
L<Math::BigInt>, L<Math::BigFloat>, L<Math::BigInt::BitVect>,
-L<Math::BigInt::GMP>, L<Math::BigInt::Cached> and L<Math::BigInt::Pari>.
+L<Math::BigInt::GMP>, L<Math::BigInt::FastCalc> and L<Math::BigInt::Pari>.
=cut