summaryrefslogtreecommitdiff
path: root/lib/Math/BigInt/Calc.pm
diff options
context:
space:
mode:
authorJarkko Hietaniemi <jhi@iki.fi>2002-02-21 20:02:27 +0000
committerJarkko Hietaniemi <jhi@iki.fi>2002-02-21 20:02:27 +0000
commitb3abae2aec672e5343915a64fe25c941cfd52764 (patch)
tree92e62933f9113093137a84ddde90397fe9b36640 /lib/Math/BigInt/Calc.pm
parent24520e9dce27cd3bbc70dc9e62923d7280f96457 (diff)
downloadperl-b3abae2aec672e5343915a64fe25c941cfd52764.tar.gz
Upgrade to Math::BigInt 1.51.
p4raw-id: //depot/perl@14817
Diffstat (limited to 'lib/Math/BigInt/Calc.pm')
-rw-r--r--lib/Math/BigInt/Calc.pm164
1 files changed, 134 insertions, 30 deletions
diff --git a/lib/Math/BigInt/Calc.pm b/lib/Math/BigInt/Calc.pm
index d91272e2d3..d76aa0980f 100644
--- a/lib/Math/BigInt/Calc.pm
+++ b/lib/Math/BigInt/Calc.pm
@@ -8,7 +8,7 @@ require Exporter;
use vars qw/@ISA $VERSION/;
@ISA = qw(Exporter);
-$VERSION = '0.20';
+$VERSION = '0.22';
# Package to store unsigned big integers in decimal and do math with them
@@ -330,6 +330,13 @@ sub _add
# This routine clobbers up array x, but not y.
my ($c,$x,$y) = @_;
+
+ return $x if (@$y == 1) && $y->[0] == 0; # $x + 0 => $x
+ if ((@$x == 1) && $x->[0] == 0) # 0 + $y => $y->copy
+ {
+ # twice as slow as $x = [ @$y ], but necc. to retain $x as ref :(
+ @$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
@@ -419,17 +426,24 @@ sub _mul_use_mul
# modifies first arg, second need not be different from first
my ($c,$xv,$yv) = @_;
- # shortcut for two very short numbers
- # +0 since part maybe string '00001' from new()
+ # 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)
- && (length($xv->[0]+0) <= $BASE_LEN2)
- && (length($yv->[0]+0) <= $BASE_LEN2))
- {
- $xv->[0] *= $yv->[0];
- return $xv;
- }
-
+ 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)) )
+ {
+ @$xv = (0);
+ return $xv;
+ }
+
# since multiplying $x with $x fails, make copy in this case
$yv = [@$xv] if "$xv" eq "$yv"; # same references?
if ($LEN_CONVERT != 0)
@@ -487,16 +501,25 @@ sub _mul_use_div
# modifies first arg, second need not be different from first
my ($c,$xv,$yv) = @_;
- # shortcut for two very short numbers
- # +0 since part maybe string '00001' from new()
+ # 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)
- && (length($xv->[0]+0) <= $BASE_LEN2)
- && (length($yv->[0]+0) <= $BASE_LEN2))
- {
- $xv->[0] *= $yv->[0];
- return $xv;
- }
+ 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)) )
+ {
+ @$xv = (0);
+ return $xv;
+ }
+
# since multiplying $x with $x fails, make copy in this case
$yv = [@$xv] if "$xv" eq "$yv"; # same references?
@@ -1122,7 +1145,50 @@ sub _pow
$cx;
}
-sub _sqrt1
+sub _fac
+ {
+ # factorial of $x
+ # ref to array, return ref to array
+ my ($c,$cx) = @_;
+
+ if ((@$cx == 1) && ($cx->[0] <= 2))
+ {
+ $cx->[0] = 1 * ($cx->[0]||1); # 0,1 => 1, 2 => 2
+ return $cx;
+ }
+
+ # go forward until $base is exceeded
+ # limit is either $x or $base (x == 100 means as result too high)
+ my $steps = 100; $steps = $cx->[0] if @$cx == 1;
+ my $r = 2; my $cf = 3; my $step = 1; my $last = $r;
+ while ($r < $BASE && $step < $steps)
+ {
+ $last = $r; $r *= $cf++; $step++;
+ }
+ if ((@$cx == 1) && ($step == $cx->[0]))
+ {
+ # completely done
+ $cx = [$last];
+ return $cx;
+ }
+ my $n = _copy($c,$cx);
+ $cx = [$last];
+
+ #$cx = _one();
+ while (!(@$n == 1 && $n->[0] == $step))
+ {
+ _mul($c,$cx,$n); _dec($c,$n);
+ }
+ $cx;
+ }
+
+use constant DEBUG => 0;
+
+my $steps = 0;
+
+sub steps { $steps };
+
+sub _sqrt
{
# square-root of $x
# ref to array, return ref to array
@@ -1135,31 +1201,68 @@ sub _sqrt1
return $x;
}
my $y = _copy($c,$x);
- my $l = _len($c,$x) / 2; # hopefully _len/2 is < $BASE
- # my $l2 = [ _len($c,$x) / 2 ]; # old way: hopefully _len/2 is < $BASE
-
- splice @$x,0; $x->[0] = 1; # keep ref($x), but modify it
-
- # old way
- # _lsft($c,$x,$l2,10);
+ # hopefully _len/2 is < $BASE, the -1 is to always undershot the guess
+ # since our guess will "grow"
+ my $l = int((_len($c,$x)-1) / 2);
+
+ my $lastelem = $x->[-1]; # for guess
+ my $elems = scalar @$x - 1;
+ # not enough digits, but could have more?
+ 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;
+ print "$lastelem\n" if DEBUG;
+ }
# construct $x (instead of _lsft($c,$x,$l,10)
my $r = $l % $BASE_LEN; # 10000 00000 00000 00000 ($BASE_LEN=5)
$l = int($l / $BASE_LEN);
- $x->[$l--] = int('1' . '0' x $r);
- $x->[$l--] = 0 while ($l >= 0);
+ print "l = $l " if DEBUG;
+
+ 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
+
+ # $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
+ $r -= 1 if $elems & 1 == 0; # 70 => 7
+
+ # padd with zeros if result is too short
+ $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.
+ $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();
my $lastlast = _zero();
+ $steps = 0 if DEBUG;
while (_acmp($c,$last,$x) != 0 && _acmp($c,$lastlast,$x) != 0)
{
+ $steps++ if DEBUG;
$lastlast = _copy($c,$last);
$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 "\nsteps in sqrt: $steps, " if DEBUG;
_dec($c,$x) if _acmp($c,$y,_mul($c,_copy($c,$x),$x)) < 0; # overshot?
+ print " final ",$x->[-1],"\n" if DEBUG;
$x;
}
@@ -1466,6 +1569,7 @@ slow) fallback routines to emulate these:
_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)
+ _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