1b8851fccSafresh1package Math::BigInt::Calc; 2b8851fccSafresh1 3b8851fccSafresh1use 5.006001; 4b8851fccSafresh1use strict; 5b8851fccSafresh1use warnings; 6b8851fccSafresh1 7b46d8ef2Safresh1use Carp qw< carp croak >; 89f11ffb7Safresh1use Math::BigInt::Lib; 99f11ffb7Safresh1 10*3d61058aSafresh1our $VERSION = '2.003002'; 11eac174f2Safresh1$VERSION =~ tr/_//d; 129f11ffb7Safresh1 139f11ffb7Safresh1our @ISA = ('Math::BigInt::Lib'); 14b8851fccSafresh1 15b8851fccSafresh1# Package to store unsigned big integers in decimal and do math with them 16eac174f2Safresh1# 17b8851fccSafresh1# Internally the numbers are stored in an array with at least 1 element, no 18b8851fccSafresh1# leading zero parts (except the first) and in base 1eX where X is determined 19b8851fccSafresh1# automatically at loading time to be the maximum possible value 20eac174f2Safresh1# 21b8851fccSafresh1# todo: 22b8851fccSafresh1# - fully remove funky $# stuff in div() (maybe - that code scares me...) 23b8851fccSafresh1 24b8851fccSafresh1############################################################################## 25b8851fccSafresh1# global constants, flags and accessory 26b8851fccSafresh1 27b8851fccSafresh1# constants for easier life 28eac174f2Safresh1 29eac174f2Safresh1my $MAX_EXP_F; # the maximum possible base 10 exponent with "no integer" 30eac174f2Safresh1my $MAX_EXP_I; # the maximum possible base 10 exponent with "use integer" 31eac174f2Safresh1 32eac174f2Safresh1my $MAX_BITS; # the maximum possible number of bits for $AND_BITS etc. 33eac174f2Safresh1 34eac174f2Safresh1my $BASE_LEN; # the current base exponent in use 35eac174f2Safresh1my $USE_INT; # whether "use integer" is used in the computations 36eac174f2Safresh1 37eac174f2Safresh1my $BASE; # the current base, e.g., 10000 if $BASE_LEN is 5 38eac174f2Safresh1my $MAX_VAL; # maximum value for an element, i.e., $BASE - 1 39eac174f2Safresh1 40eac174f2Safresh1my $AND_BITS; # maximum value used in binary and, e.g., 0xffff 41eac174f2Safresh1my $OR_BITS; # ditto for binary or 42eac174f2Safresh1my $XOR_BITS; # ditto for binary xor 43eac174f2Safresh1 44eac174f2Safresh1my $AND_MASK; # $AND_BITS + 1, e.g., 0x10000 if $AND_BITS is 0xffff 45eac174f2Safresh1my $OR_MASK; # ditto for binary or 46eac174f2Safresh1my $XOR_MASK; # ditto for binary xor 47eac174f2Safresh1 48eac174f2Safresh1sub config { 49eac174f2Safresh1 my $self = shift; 50eac174f2Safresh1 51eac174f2Safresh1 croak "Missing input argument" unless @_; 52eac174f2Safresh1 53eac174f2Safresh1 # Called as a getter. 54eac174f2Safresh1 55eac174f2Safresh1 if (@_ == 1) { 56eac174f2Safresh1 my $param = shift; 57eac174f2Safresh1 croak "Parameter name must be a non-empty string" 58eac174f2Safresh1 unless defined $param && length $param; 59eac174f2Safresh1 return $BASE_LEN if $param eq 'base_len'; 60eac174f2Safresh1 return $USE_INT if $param eq 'use_int'; 61eac174f2Safresh1 croak "Unknown parameter '$param'"; 62eac174f2Safresh1 } 63eac174f2Safresh1 64eac174f2Safresh1 # Called as a setter. 65eac174f2Safresh1 66eac174f2Safresh1 my $opts; 67eac174f2Safresh1 while (@_) { 68eac174f2Safresh1 my $param = shift; 69eac174f2Safresh1 croak "Parameter name must be a non-empty string" 70eac174f2Safresh1 unless defined $param && length $param; 71eac174f2Safresh1 croak "Missing value for parameter '$param'" 72eac174f2Safresh1 unless @_; 73eac174f2Safresh1 my $value = shift; 74eac174f2Safresh1 75eac174f2Safresh1 if ($param eq 'base_len' || $param eq 'use_int') { 76eac174f2Safresh1 $opts -> {$param} = $value; 77eac174f2Safresh1 next; 78eac174f2Safresh1 } 79eac174f2Safresh1 80eac174f2Safresh1 croak "Unknown parameter '$param'"; 81eac174f2Safresh1 } 82eac174f2Safresh1 83eac174f2Safresh1 $BASE_LEN = $opts -> {base_len} if exists $opts -> {base_len}; 84eac174f2Safresh1 $USE_INT = $opts -> {use_int} if exists $opts -> {use_int}; 85eac174f2Safresh1 __PACKAGE__ -> _base_len($BASE_LEN, $USE_INT); 86eac174f2Safresh1 87eac174f2Safresh1 return $self; 88eac174f2Safresh1} 89b8851fccSafresh1 909f11ffb7Safresh1sub _base_len { 91eac174f2Safresh1 #my $class = shift; # $class is not used 92eac174f2Safresh1 shift; 93b8851fccSafresh1 94eac174f2Safresh1 if (@_) { # if called as setter ... 95eac174f2Safresh1 my ($base_len, $use_int) = @_; 96eac174f2Safresh1 97eac174f2Safresh1 croak "The base length must be a positive integer" 98eac174f2Safresh1 unless defined($base_len) && $base_len == int($base_len) 99eac174f2Safresh1 && $base_len > 0; 100eac174f2Safresh1 101eac174f2Safresh1 if ( $use_int && ($base_len > $MAX_EXP_I) || 102eac174f2Safresh1 !$use_int && ($base_len > $MAX_EXP_F)) 103eac174f2Safresh1 { 104eac174f2Safresh1 croak "The maximum base length (exponent) is $MAX_EXP_I with", 105eac174f2Safresh1 " 'use integer' and $MAX_EXP_F without 'use integer'. The", 106eac174f2Safresh1 " requested settings, a base length of $base_len ", 107eac174f2Safresh1 $use_int ? "with" : "without", " 'use integer', is invalid."; 108eac174f2Safresh1 } 109eac174f2Safresh1 110eac174f2Safresh1 $BASE_LEN = $base_len; 111eac174f2Safresh1 $BASE = 0 + ("1" . ("0" x $BASE_LEN)); 112eac174f2Safresh1 $MAX_VAL = $BASE - 1; 113eac174f2Safresh1 $USE_INT = $use_int ? 1 : 0; 114eac174f2Safresh1 115eac174f2Safresh1 { 11656d68f1eSafresh1 no warnings "redefine"; 117eac174f2Safresh1 if ($use_int) { 118eac174f2Safresh1 *_mul = \&_mul_use_int; 119eac174f2Safresh1 *_div = \&_div_use_int; 120eac174f2Safresh1 } else { 121eac174f2Safresh1 *_mul = \&_mul_no_int; 122eac174f2Safresh1 *_div = \&_div_no_int; 123eac174f2Safresh1 } 124eac174f2Safresh1 } 125eac174f2Safresh1 } 126b8851fccSafresh1 127eac174f2Safresh1 # Find max bits. This is the largest power of two that is both no larger 128eac174f2Safresh1 # than $BASE and no larger than the maximum integer (i.e., ~0). We need 129eac174f2Safresh1 # this limitation because _and(), _or(), and _xor() only work on one 130eac174f2Safresh1 # element at a time. 131eac174f2Safresh1 132eac174f2Safresh1 my $umax = ~0; # largest unsigned integer 133eac174f2Safresh1 my $tmp = $umax < $BASE ? $umax : $BASE; 134eac174f2Safresh1 135eac174f2Safresh1 $MAX_BITS = 0; 136eac174f2Safresh1 while ($tmp >>= 1) { 137eac174f2Safresh1 $MAX_BITS++; 138eac174f2Safresh1 } 139eac174f2Safresh1 140eac174f2Safresh1 # Limit to 32 bits for portability. Is this really necessary? XXX 141eac174f2Safresh1 142eac174f2Safresh1 $MAX_BITS = 32 if $MAX_BITS > 32; 143eac174f2Safresh1 144eac174f2Safresh1 # Find out how many bits _and, _or and _xor can take (old default = 16). 145eac174f2Safresh1 # Are these tests really necessary? Can't we just use $MAX_BITS? XXX 146eac174f2Safresh1 147eac174f2Safresh1 for ($AND_BITS = $MAX_BITS ; $AND_BITS > 0 ; $AND_BITS--) { 148eac174f2Safresh1 my $x = CORE::oct('0b' . '1' x $AND_BITS); 149eac174f2Safresh1 my $y = $x & $x; 150eac174f2Safresh1 my $z = 2 * (2 ** ($AND_BITS - 1)) + 1; 151eac174f2Safresh1 last unless $AND_BITS < $MAX_BITS && $x == $z && $y == $x; 152eac174f2Safresh1 } 153eac174f2Safresh1 154eac174f2Safresh1 for ($XOR_BITS = $MAX_BITS ; $XOR_BITS > 0 ; $XOR_BITS--) { 155eac174f2Safresh1 my $x = CORE::oct('0b' . '1' x $XOR_BITS); 156eac174f2Safresh1 my $y = $x ^ $x; 157eac174f2Safresh1 my $z = 2 * (2 ** ($XOR_BITS - 1)) + 1; 158eac174f2Safresh1 last unless $XOR_BITS < $MAX_BITS && $x == $z && $y == $x; 159eac174f2Safresh1 } 160eac174f2Safresh1 161eac174f2Safresh1 for ($OR_BITS = $MAX_BITS ; $OR_BITS > 0 ; $OR_BITS--) { 162eac174f2Safresh1 my $x = CORE::oct('0b' . '1' x $OR_BITS); 163eac174f2Safresh1 my $y = $x | $x; 164eac174f2Safresh1 my $z = 2 * (2 ** ($OR_BITS - 1)) + 1; 165eac174f2Safresh1 last unless $OR_BITS < $MAX_BITS && $x == $z && $y == $x; 166eac174f2Safresh1 } 167eac174f2Safresh1 168eac174f2Safresh1 $AND_MASK = __PACKAGE__->_new(( 2 ** $AND_BITS )); 169eac174f2Safresh1 $XOR_MASK = __PACKAGE__->_new(( 2 ** $XOR_BITS )); 170eac174f2Safresh1 $OR_MASK = __PACKAGE__->_new(( 2 ** $OR_BITS )); 171eac174f2Safresh1 172b8851fccSafresh1 return $BASE_LEN unless wantarray; 173eac174f2Safresh1 return ($BASE_LEN, $BASE, $AND_BITS, $XOR_BITS, $OR_BITS, $BASE_LEN, $MAX_VAL, 174eac174f2Safresh1 $MAX_BITS, $MAX_EXP_F, $MAX_EXP_I, $USE_INT); 175b8851fccSafresh1} 176b8851fccSafresh1 177b8851fccSafresh1sub _new { 178b8851fccSafresh1 # Given a string representing an integer, returns a reference to an array 179b8851fccSafresh1 # of integers, where each integer represents a chunk of the original input 1809f11ffb7Safresh1 # integer. 181b8851fccSafresh1 1829f11ffb7Safresh1 my ($class, $str) = @_; 1839f11ffb7Safresh1 #unless ($str =~ /^([1-9]\d*|0)\z/) { 184b46d8ef2Safresh1 # croak("Invalid input string '$str'"); 1859f11ffb7Safresh1 #} 186b8851fccSafresh1 187b8851fccSafresh1 my $input_len = length($str) - 1; 188b8851fccSafresh1 189b8851fccSafresh1 # Shortcut for small numbers. 1909f11ffb7Safresh1 return bless [ $str ], $class if $input_len < $BASE_LEN; 191b8851fccSafresh1 192b8851fccSafresh1 my $format = "a" . (($input_len % $BASE_LEN) + 1); 193b8851fccSafresh1 $format .= $] < 5.008 ? "a$BASE_LEN" x int($input_len / $BASE_LEN) 194b8851fccSafresh1 : "(a$BASE_LEN)*"; 195b8851fccSafresh1 1969f11ffb7Safresh1 my $self = [ reverse(map { 0 + $_ } unpack($format, $str)) ]; 1979f11ffb7Safresh1 return bless $self, $class; 198b8851fccSafresh1} 199b8851fccSafresh1 2009f11ffb7Safresh1BEGIN { 201b8851fccSafresh1 202eac174f2Safresh1 # Compute $MAX_EXP_F, the maximum usable base 10 exponent. 203eac174f2Safresh1 204eac174f2Safresh1 # The largest element in base 10**$BASE_LEN is 10**$BASE_LEN-1. For instance, 205eac174f2Safresh1 # with $BASE_LEN = 5, the largest element is 99_999, and the largest carry is 206eac174f2Safresh1 # 207eac174f2Safresh1 # int( 99_999 * 99_999 / 100_000 ) = 99_998 208eac174f2Safresh1 # 209eac174f2Safresh1 # so make sure that 99_999 * 99_999 + 99_998 is within the range of integers 210eac174f2Safresh1 # that can be represented accuratly. 211eac174f2Safresh1 # 212eac174f2Safresh1 # Note that on some systems with quadmath support, the following is within 213eac174f2Safresh1 # the range of numbers that can be represented exactly, but it still gives 214eac174f2Safresh1 # the incorrect value $r = 2 (even though POSIX::fmod($x, $y) gives the 215eac174f2Safresh1 # correct value of 1: 216eac174f2Safresh1 # 217eac174f2Safresh1 # $x = 99999999999999999; 218eac174f2Safresh1 # $y = 100000000000000000; 219eac174f2Safresh1 # $r = $x * $x % $y; # should be 1 220eac174f2Safresh1 # 221eac174f2Safresh1 # so also check for this. 222eac174f2Safresh1 223eac174f2Safresh1 for ($MAX_EXP_F = 1 ; ; $MAX_EXP_F++) { # when $MAX_EXP_F = 5 224eac174f2Safresh1 my $MAX_EXP_FM1 = $MAX_EXP_F - 1; # = 4 225eac174f2Safresh1 my $bs = "1" . ("0" x $MAX_EXP_F); # = "100000" 226eac174f2Safresh1 my $xs = "9" x $MAX_EXP_F; # = "99999" 227eac174f2Safresh1 my $cs = ("9" x $MAX_EXP_FM1) . "8"; # = "99998" 228eac174f2Safresh1 my $ys = $cs . ("0" x $MAX_EXP_FM1) . "1"; # = "9999800001" 229eac174f2Safresh1 230eac174f2Safresh1 # Compute and check the product. 231eac174f2Safresh1 my $yn = $xs * $xs; # = 9999800001 232eac174f2Safresh1 last if $yn != $ys; 233eac174f2Safresh1 234eac174f2Safresh1 # Compute and check the remainder. 235eac174f2Safresh1 my $rn = $yn % $bs; # = 1 236eac174f2Safresh1 last if $rn != 1; 237eac174f2Safresh1 238eac174f2Safresh1 # Compute and check the carry. The division here is exact. 239eac174f2Safresh1 my $cn = ($yn - $rn) / $bs; # = 99998 240eac174f2Safresh1 last if $cn != $cs; 241eac174f2Safresh1 242eac174f2Safresh1 # Compute and check product plus carry. 243eac174f2Safresh1 my $zs = $cs . ("9" x $MAX_EXP_F); # = "9999899999" 244eac174f2Safresh1 my $zn = $yn + $cn; # = 99998999999 245eac174f2Safresh1 last if $zn != $zs; 246eac174f2Safresh1 last if $zn - ($zn - 1) != 1; 247b8851fccSafresh1 } 248eac174f2Safresh1 $MAX_EXP_F--; # last test failed, so retract one step 249eac174f2Safresh1 250eac174f2Safresh1 # Compute $MAX_EXP_I, the maximum usable base 10 exponent within the range 251eac174f2Safresh1 # of what is available with "use integer". On older versions of Perl, 252eac174f2Safresh1 # integers are converted to floating point numbers, even though they are 253eac174f2Safresh1 # within the range of what can be represented as integers. For example, on 254eac174f2Safresh1 # some 64 bit Perls, 999999999 * 999999999 becomes 999999998000000000, not 255eac174f2Safresh1 # 999999998000000001, even though the latter is less than the maximum value 256eac174f2Safresh1 # for a 64 bit integer, 18446744073709551615. 257eac174f2Safresh1 258eac174f2Safresh1 my $umax = ~0; # largest unsigned integer 259eac174f2Safresh1 for ($MAX_EXP_I = int(0.5 * log($umax) / log(10)); 260eac174f2Safresh1 $MAX_EXP_I > 0; 261eac174f2Safresh1 $MAX_EXP_I--) 262eac174f2Safresh1 { # when $MAX_EXP_I = 5 263eac174f2Safresh1 my $MAX_EXP_IM1 = $MAX_EXP_I - 1; # = 4 264eac174f2Safresh1 my $bs = "1" . ("0" x $MAX_EXP_I); # = "100000" 265eac174f2Safresh1 my $xs = "9" x $MAX_EXP_I; # = "99999" 266eac174f2Safresh1 my $cs = ("9" x $MAX_EXP_IM1) . "8"; # = "99998" 267eac174f2Safresh1 my $ys = $cs . ("0" x $MAX_EXP_IM1) . "1"; # = "9999800001" 268eac174f2Safresh1 269eac174f2Safresh1 # Compute and check the product. 270eac174f2Safresh1 my $yn = $xs * $xs; # = 9999800001 271eac174f2Safresh1 next if $yn != $ys; 272eac174f2Safresh1 273eac174f2Safresh1 # Compute and check the remainder. 274eac174f2Safresh1 my $rn = $yn % $bs; # = 1 275eac174f2Safresh1 next if $rn != 1; 276eac174f2Safresh1 277eac174f2Safresh1 # Compute and check the carry. The division here is exact. 278eac174f2Safresh1 my $cn = ($yn - $rn) / $bs; # = 99998 279eac174f2Safresh1 next if $cn != $cs; 280eac174f2Safresh1 281eac174f2Safresh1 # Compute and check product plus carry. 282eac174f2Safresh1 my $zs = $cs . ("9" x $MAX_EXP_I); # = "9999899999" 283eac174f2Safresh1 my $zn = $yn + $cn; # = 99998999999 284eac174f2Safresh1 next if $zn != $zs; 285eac174f2Safresh1 next if $zn - ($zn - 1) != 1; 286eac174f2Safresh1 last; 287b8851fccSafresh1 } 288b8851fccSafresh1 289eac174f2Safresh1 ($BASE_LEN, $USE_INT) = $MAX_EXP_F > $MAX_EXP_I 290eac174f2Safresh1 ? ($MAX_EXP_F, 0) : ($MAX_EXP_I, 1); 291b8851fccSafresh1 292eac174f2Safresh1 __PACKAGE__ -> _base_len($BASE_LEN, $USE_INT); 293b8851fccSafresh1} 294b8851fccSafresh1 295b8851fccSafresh1############################################################################### 296b8851fccSafresh1 2979f11ffb7Safresh1sub _zero { 298b8851fccSafresh1 # create a zero 2999f11ffb7Safresh1 my $class = shift; 3009f11ffb7Safresh1 return bless [ 0 ], $class; 301b8851fccSafresh1} 302b8851fccSafresh1 3039f11ffb7Safresh1sub _one { 304b8851fccSafresh1 # create a one 3059f11ffb7Safresh1 my $class = shift; 3069f11ffb7Safresh1 return bless [ 1 ], $class; 307b8851fccSafresh1} 308b8851fccSafresh1 3099f11ffb7Safresh1sub _two { 3109f11ffb7Safresh1 # create a two 3119f11ffb7Safresh1 my $class = shift; 3129f11ffb7Safresh1 return bless [ 2 ], $class; 313b8851fccSafresh1} 314b8851fccSafresh1 3159f11ffb7Safresh1sub _ten { 3169f11ffb7Safresh1 # create a 10 3179f11ffb7Safresh1 my $class = shift; 318eac174f2Safresh1 my $self = $BASE_LEN == 1 ? [ 0, 1 ] : [ 10 ]; 319eac174f2Safresh1 bless $self, $class; 320b8851fccSafresh1} 321b8851fccSafresh1 3229f11ffb7Safresh1sub _1ex { 323b8851fccSafresh1 # create a 1Ex 3249f11ffb7Safresh1 my $class = shift; 3259f11ffb7Safresh1 3269f11ffb7Safresh1 my $rem = $_[0] % $BASE_LEN; # remainder 327eac174f2Safresh1 my $div = ($_[0] - $rem) / $BASE_LEN; # parts 328b8851fccSafresh1 329eac174f2Safresh1 # With a $BASE_LEN of 6, 1e14 becomes 330eac174f2Safresh1 # [ 000000, 000000, 100 ] -> [ 0, 0, 100 ] 331eac174f2Safresh1 bless [ (0) x $div, 0 + ("1" . ("0" x $rem)) ], $class; 332b8851fccSafresh1} 333b8851fccSafresh1 3349f11ffb7Safresh1sub _copy { 335b8851fccSafresh1 # make a true copy 3369f11ffb7Safresh1 my $class = shift; 3379f11ffb7Safresh1 return bless [ @{ $_[0] } ], $class; 338b8851fccSafresh1} 339b8851fccSafresh1 340eac174f2Safresh1sub import { 341eac174f2Safresh1 my $self = shift; 342eac174f2Safresh1 343eac174f2Safresh1 my $opts; 344eac174f2Safresh1 my ($base_len, $use_int); 345eac174f2Safresh1 while (@_) { 346eac174f2Safresh1 my $param = shift; 347eac174f2Safresh1 croak "Parameter name must be a non-empty string" 348eac174f2Safresh1 unless defined $param && length $param; 349eac174f2Safresh1 croak "Missing value for parameter '$param'" 350eac174f2Safresh1 unless @_; 351eac174f2Safresh1 my $value = shift; 352eac174f2Safresh1 353eac174f2Safresh1 if ($param eq 'base_len' || $param eq 'use_int') { 354eac174f2Safresh1 $opts -> {$param} = $value; 355eac174f2Safresh1 next; 356eac174f2Safresh1 } 357eac174f2Safresh1 358eac174f2Safresh1 croak "Unknown parameter '$param'"; 359eac174f2Safresh1 } 360eac174f2Safresh1 361eac174f2Safresh1 $base_len = exists $opts -> {base_len} ? $opts -> {base_len} : $BASE_LEN; 362eac174f2Safresh1 $use_int = exists $opts -> {use_int} ? $opts -> {use_int} : $USE_INT; 363eac174f2Safresh1 __PACKAGE__ -> _base_len($base_len, $use_int); 364eac174f2Safresh1 365eac174f2Safresh1 return $self; 366eac174f2Safresh1} 367b8851fccSafresh1 368b8851fccSafresh1############################################################################## 369b8851fccSafresh1# convert back to string and number 370b8851fccSafresh1 371b8851fccSafresh1sub _str { 372b8851fccSafresh1 # Convert number from internal base 1eN format to string format. Internal 373b8851fccSafresh1 # format is always normalized, i.e., no leading zeros. 374b8851fccSafresh1 375b8851fccSafresh1 my $ary = $_[1]; 376b8851fccSafresh1 my $idx = $#$ary; # index of last element 377b8851fccSafresh1 378b8851fccSafresh1 if ($idx < 0) { # should not happen 379b46d8ef2Safresh1 croak("$_[1] has no elements"); 380b8851fccSafresh1 } 381b8851fccSafresh1 382b8851fccSafresh1 # Handle first one differently, since it should not have any leading zeros. 383b8851fccSafresh1 my $ret = int($ary->[$idx]); 384b8851fccSafresh1 if ($idx > 0) { 3859f11ffb7Safresh1 # Interestingly, the pre-padd method uses more time. 3869f11ffb7Safresh1 # The old grep variant takes longer (14 vs. 10 sec). 387b8851fccSafresh1 my $z = '0' x ($BASE_LEN - 1); 3889f11ffb7Safresh1 while (--$idx >= 0) { 389b8851fccSafresh1 $ret .= substr($z . $ary->[$idx], -$BASE_LEN); 390b8851fccSafresh1 } 391b8851fccSafresh1 } 392b8851fccSafresh1 $ret; 393b8851fccSafresh1} 394b8851fccSafresh1 3959f11ffb7Safresh1sub _num { 396b8851fccSafresh1 # Make a Perl scalar number (int/float) from a BigInt object. 397b8851fccSafresh1 my $x = $_[1]; 398b8851fccSafresh1 3999f11ffb7Safresh1 return $x->[0] if @$x == 1; # below $BASE 400b8851fccSafresh1 401b8851fccSafresh1 # Start with the most significant element and work towards the least 402b8851fccSafresh1 # significant element. Avoid multiplying "inf" (which happens if the number 403b8851fccSafresh1 # overflows) with "0" (if there are zero elements in $x) since this gives 404b8851fccSafresh1 # "nan" which propagates to the output. 405b8851fccSafresh1 406b8851fccSafresh1 my $num = 0; 407b8851fccSafresh1 for (my $i = $#$x ; $i >= 0 ; --$i) { 408b8851fccSafresh1 $num *= $BASE; 409b8851fccSafresh1 $num += $x -> [$i]; 410b8851fccSafresh1 } 411b8851fccSafresh1 return $num; 412b8851fccSafresh1} 413b8851fccSafresh1 414b8851fccSafresh1############################################################################## 415b8851fccSafresh1# actual math code 416b8851fccSafresh1 417b8851fccSafresh1sub _add { 418b8851fccSafresh1 # (ref to int_num_array, ref to int_num_array) 419b8851fccSafresh1 # 420b8851fccSafresh1 # Routine to add two base 1eX numbers stolen from Knuth Vol 2 Algorithm A 421b8851fccSafresh1 # pg 231. There are separate routines to add and sub as per Knuth pg 233. 422b8851fccSafresh1 # This routine modifies array x, but not y. 423b8851fccSafresh1 424b8851fccSafresh1 my ($c, $x, $y) = @_; 425b8851fccSafresh1 4269f11ffb7Safresh1 # $x + 0 => $x 4279f11ffb7Safresh1 4289f11ffb7Safresh1 return $x if @$y == 1 && $y->[0] == 0; 4299f11ffb7Safresh1 4309f11ffb7Safresh1 # 0 + $y => $y->copy 4319f11ffb7Safresh1 4329f11ffb7Safresh1 if (@$x == 1 && $x->[0] == 0) { 433b8851fccSafresh1 @$x = @$y; 434b8851fccSafresh1 return $x; 435b8851fccSafresh1 } 436b8851fccSafresh1 437b8851fccSafresh1 # For each in Y, add Y to X and carry. If after that, something is left in 438b8851fccSafresh1 # X, foreach in X add carry to X and then return X, carry. Trades one 439b8851fccSafresh1 # "$j++" for having to shift arrays. 440eac174f2Safresh1 441b8851fccSafresh1 my $car = 0; 442b8851fccSafresh1 my $j = 0; 443eac174f2Safresh1 for my $i (@$y) { 444b8851fccSafresh1 $x->[$j] -= $BASE if $car = (($x->[$j] += $i + $car) >= $BASE) ? 1 : 0; 445b8851fccSafresh1 $j++; 446b8851fccSafresh1 } 447b8851fccSafresh1 while ($car != 0) { 448b8851fccSafresh1 $x->[$j] -= $BASE if $car = (($x->[$j] += $car) >= $BASE) ? 1 : 0; 449b8851fccSafresh1 $j++; 450b8851fccSafresh1 } 451b8851fccSafresh1 $x; 452b8851fccSafresh1} 453b8851fccSafresh1 454b8851fccSafresh1sub _inc { 455b8851fccSafresh1 # (ref to int_num_array, ref to int_num_array) 456b8851fccSafresh1 # Add 1 to $x, modify $x in place 457b8851fccSafresh1 my ($c, $x) = @_; 458b8851fccSafresh1 459b8851fccSafresh1 for my $i (@$x) { 460b8851fccSafresh1 return $x if ($i += 1) < $BASE; # early out 461b8851fccSafresh1 $i = 0; # overflow, next 462b8851fccSafresh1 } 463b8851fccSafresh1 push @$x, 1 if $x->[-1] == 0; # last overflowed, so extend 464b8851fccSafresh1 $x; 465b8851fccSafresh1} 466b8851fccSafresh1 467b8851fccSafresh1sub _dec { 468b8851fccSafresh1 # (ref to int_num_array, ref to int_num_array) 469b8851fccSafresh1 # Sub 1 from $x, modify $x in place 470b8851fccSafresh1 my ($c, $x) = @_; 471b8851fccSafresh1 472b8851fccSafresh1 my $MAX = $BASE - 1; # since MAX_VAL based on BASE 473b8851fccSafresh1 for my $i (@$x) { 474b8851fccSafresh1 last if ($i -= 1) >= 0; # early out 475b8851fccSafresh1 $i = $MAX; # underflow, next 476b8851fccSafresh1 } 477b8851fccSafresh1 pop @$x if $x->[-1] == 0 && @$x > 1; # last underflowed (but leave 0) 478b8851fccSafresh1 $x; 479b8851fccSafresh1} 480b8851fccSafresh1 481b8851fccSafresh1sub _sub { 482b8851fccSafresh1 # (ref to int_num_array, ref to int_num_array, swap) 483b8851fccSafresh1 # 484b8851fccSafresh1 # Subtract base 1eX numbers -- stolen from Knuth Vol 2 pg 232, $x > $y 485b8851fccSafresh1 # subtract Y from X by modifying x in place 486b8851fccSafresh1 my ($c, $sx, $sy, $s) = @_; 487b8851fccSafresh1 488b8851fccSafresh1 my $car = 0; 489b8851fccSafresh1 my $j = 0; 490b8851fccSafresh1 if (!$s) { 491eac174f2Safresh1 for my $i (@$sx) { 492b8851fccSafresh1 last unless defined $sy->[$j] || $car; 493b8851fccSafresh1 $i += $BASE if $car = (($i -= ($sy->[$j] || 0) + $car) < 0); 494b8851fccSafresh1 $j++; 495b8851fccSafresh1 } 496b8851fccSafresh1 # might leave leading zeros, so fix that 497b8851fccSafresh1 return __strip_zeros($sx); 498b8851fccSafresh1 } 499eac174f2Safresh1 for my $i (@$sx) { 500b8851fccSafresh1 # We can't do an early out if $x < $y, since we need to copy the high 501b8851fccSafresh1 # chunks from $y. Found by Bob Mathews. 502b8851fccSafresh1 #last unless defined $sy->[$j] || $car; 503b8851fccSafresh1 $sy->[$j] += $BASE 504b8851fccSafresh1 if $car = ($sy->[$j] = $i - ($sy->[$j] || 0) - $car) < 0; 505b8851fccSafresh1 $j++; 506b8851fccSafresh1 } 507b8851fccSafresh1 # might leave leading zeros, so fix that 508b8851fccSafresh1 __strip_zeros($sy); 509b8851fccSafresh1} 510b8851fccSafresh1 511eac174f2Safresh1sub _mul_use_int { 512b8851fccSafresh1 # (ref to int_num_array, ref to int_num_array) 513b8851fccSafresh1 # multiply two numbers in internal representation 514b8851fccSafresh1 # modifies first arg, second need not be different from first 515b8851fccSafresh1 # works for 64 bit integer with "use integer" 516b8851fccSafresh1 my ($c, $xv, $yv) = @_; 517b8851fccSafresh1 use integer; 51856d68f1eSafresh1 5199f11ffb7Safresh1 if (@$yv == 1) { 52056d68f1eSafresh1 # shortcut for two very short numbers (improved by Nathan Zook) works 52156d68f1eSafresh1 # also if xv and yv are the same reference, and handles also $x == 0 5229f11ffb7Safresh1 if (@$xv == 1) { 5239f11ffb7Safresh1 if (($xv->[0] *= $yv->[0]) >= $BASE) { 524b8851fccSafresh1 $xv->[0] = 525b8851fccSafresh1 $xv->[0] - ($xv->[1] = $xv->[0] / $BASE) * $BASE; 5269f11ffb7Safresh1 } 527b8851fccSafresh1 return $xv; 528b8851fccSafresh1 } 529b8851fccSafresh1 # $x * 0 => 0 5309f11ffb7Safresh1 if ($yv->[0] == 0) { 531b8851fccSafresh1 @$xv = (0); 532b8851fccSafresh1 return $xv; 533b8851fccSafresh1 } 53456d68f1eSafresh1 535b8851fccSafresh1 # multiply a large number a by a single element one, so speed up 5369f11ffb7Safresh1 my $y = $yv->[0]; 5379f11ffb7Safresh1 my $car = 0; 5389f11ffb7Safresh1 foreach my $i (@$xv) { 539b8851fccSafresh1 #$i = $i * $y + $car; $car = $i / $BASE; $i -= $car * $BASE; 5409f11ffb7Safresh1 $i = $i * $y + $car; 5419f11ffb7Safresh1 $i -= ($car = $i / $BASE) * $BASE; 542b8851fccSafresh1 } 543b8851fccSafresh1 push @$xv, $car if $car != 0; 544b8851fccSafresh1 return $xv; 545b8851fccSafresh1 } 54656d68f1eSafresh1 547b8851fccSafresh1 # shortcut for result $x == 0 => result = 0 54856d68f1eSafresh1 return $xv if @$xv == 1 && $xv->[0] == 0; 549b8851fccSafresh1 550b8851fccSafresh1 # since multiplying $x with $x fails, make copy in this case 5519f11ffb7Safresh1 $yv = $c->_copy($xv) if $xv == $yv; # same references? 552b8851fccSafresh1 5539f11ffb7Safresh1 my @prod = (); 554eac174f2Safresh1 my ($prod, $car, $cty); 555eac174f2Safresh1 for my $xi (@$xv) { 5569f11ffb7Safresh1 $car = 0; 5579f11ffb7Safresh1 $cty = 0; 558b8851fccSafresh1 # looping through this if $xi == 0 is silly - so optimize it away! 55956d68f1eSafresh1 $xi = (shift(@prod) || 0), next if $xi == 0; 560eac174f2Safresh1 for my $yi (@$yv) { 561b8851fccSafresh1 $prod = $xi * $yi + ($prod[$cty] || 0) + $car; 562b8851fccSafresh1 $prod[$cty++] = $prod - ($car = $prod / $BASE) * $BASE; 563b8851fccSafresh1 } 564b8851fccSafresh1 $prod[$cty] += $car if $car; # need really to check for 0? 56556d68f1eSafresh1 $xi = shift(@prod) || 0; # || 0 makes v5.005_3 happy 566b8851fccSafresh1 } 567b8851fccSafresh1 push @$xv, @prod; 568b8851fccSafresh1 $xv; 569b8851fccSafresh1} 570b8851fccSafresh1 571eac174f2Safresh1sub _mul_no_int { 572b8851fccSafresh1 # (ref to int_num_array, ref to int_num_array) 573b8851fccSafresh1 # multiply two numbers in internal representation 574b8851fccSafresh1 # modifies first arg, second need not be different from first 575b8851fccSafresh1 my ($c, $xv, $yv) = @_; 576b8851fccSafresh1 5779f11ffb7Safresh1 if (@$yv == 1) { 57856d68f1eSafresh1 # shortcut for two very short numbers (improved by Nathan Zook) works 57956d68f1eSafresh1 # also if xv and yv are the same reference, and handles also $x == 0 5809f11ffb7Safresh1 if (@$xv == 1) { 5819f11ffb7Safresh1 if (($xv->[0] *= $yv->[0]) >= $BASE) { 58256d68f1eSafresh1 my $rem = $xv->[0] % $BASE; 58356d68f1eSafresh1 $xv->[1] = ($xv->[0] - $rem) / $BASE; 58456d68f1eSafresh1 $xv->[0] = $rem; 5859f11ffb7Safresh1 } 586b8851fccSafresh1 return $xv; 587b8851fccSafresh1 } 588b8851fccSafresh1 # $x * 0 => 0 5899f11ffb7Safresh1 if ($yv->[0] == 0) { 590b8851fccSafresh1 @$xv = (0); 591b8851fccSafresh1 return $xv; 592b8851fccSafresh1 } 59356d68f1eSafresh1 594b8851fccSafresh1 # multiply a large number a by a single element one, so speed up 5959f11ffb7Safresh1 my $y = $yv->[0]; 5969f11ffb7Safresh1 my $car = 0; 59756d68f1eSafresh1 my $rem; 5989f11ffb7Safresh1 foreach my $i (@$xv) { 5999f11ffb7Safresh1 $i = $i * $y + $car; 60056d68f1eSafresh1 $rem = $i % $BASE; 60156d68f1eSafresh1 $car = ($i - $rem) / $BASE; 60256d68f1eSafresh1 $i = $rem; 603b8851fccSafresh1 } 604b8851fccSafresh1 push @$xv, $car if $car != 0; 605b8851fccSafresh1 return $xv; 606b8851fccSafresh1 } 60756d68f1eSafresh1 608b8851fccSafresh1 # shortcut for result $x == 0 => result = 0 60956d68f1eSafresh1 return $xv if @$xv == 1 && $xv->[0] == 0; 610b8851fccSafresh1 611b8851fccSafresh1 # since multiplying $x with $x fails, make copy in this case 6129f11ffb7Safresh1 $yv = $c->_copy($xv) if $xv == $yv; # same references? 613b8851fccSafresh1 6149f11ffb7Safresh1 my @prod = (); 615eac174f2Safresh1 my ($prod, $rem, $car, $cty); 616eac174f2Safresh1 for my $xi (@$xv) { 6179f11ffb7Safresh1 $car = 0; 6189f11ffb7Safresh1 $cty = 0; 619b8851fccSafresh1 # looping through this if $xi == 0 is silly - so optimize it away! 62056d68f1eSafresh1 $xi = (shift(@prod) || 0), next if $xi == 0; 621eac174f2Safresh1 for my $yi (@$yv) { 622b8851fccSafresh1 $prod = $xi * $yi + ($prod[$cty] || 0) + $car; 62356d68f1eSafresh1 $rem = $prod % $BASE; 62456d68f1eSafresh1 $car = ($prod - $rem) / $BASE; 62556d68f1eSafresh1 $prod[$cty++] = $rem; 626b8851fccSafresh1 } 627b8851fccSafresh1 $prod[$cty] += $car if $car; # need really to check for 0? 62856d68f1eSafresh1 $xi = shift(@prod) || 0; # || 0 makes v5.005_3 happy 629b8851fccSafresh1 } 630b8851fccSafresh1 push @$xv, @prod; 631b8851fccSafresh1 $xv; 632b8851fccSafresh1} 633b8851fccSafresh1 634eac174f2Safresh1sub _div_use_int { 635b8851fccSafresh1 # ref to array, ref to array, modify first array and return remainder if 636b8851fccSafresh1 # in list context 63756d68f1eSafresh1 63856d68f1eSafresh1 # This version works on integers 63956d68f1eSafresh1 use integer; 64056d68f1eSafresh1 641b8851fccSafresh1 my ($c, $x, $yorg) = @_; 642b8851fccSafresh1 643b8851fccSafresh1 # the general div algorithm here is about O(N*N) and thus quite slow, so 644b8851fccSafresh1 # we first check for some special cases and use shortcuts to handle them. 645b8851fccSafresh1 646b8851fccSafresh1 # if both numbers have only one element: 6479f11ffb7Safresh1 if (@$x == 1 && @$yorg == 1) { 648b8851fccSafresh1 # shortcut, $yorg and $x are two small numbers 6499f11ffb7Safresh1 if (wantarray) { 6509f11ffb7Safresh1 my $rem = [ $x->[0] % $yorg->[0] ]; 6519f11ffb7Safresh1 bless $rem, $c; 65256d68f1eSafresh1 $x->[0] = $x->[0] / $yorg->[0]; 6539f11ffb7Safresh1 return ($x, $rem); 6549f11ffb7Safresh1 } else { 65556d68f1eSafresh1 $x->[0] = $x->[0] / $yorg->[0]; 656b8851fccSafresh1 return $x; 657b8851fccSafresh1 } 658b8851fccSafresh1 } 65956d68f1eSafresh1 660b8851fccSafresh1 # if x has more than one, but y has only one element: 6619f11ffb7Safresh1 if (@$yorg == 1) { 662b8851fccSafresh1 my $rem; 6639f11ffb7Safresh1 $rem = $c->_mod($c->_copy($x), $yorg) if wantarray; 664b8851fccSafresh1 665b8851fccSafresh1 # shortcut, $y is < $BASE 6669f11ffb7Safresh1 my $j = @$x; 6679f11ffb7Safresh1 my $r = 0; 6689f11ffb7Safresh1 my $y = $yorg->[0]; 6699f11ffb7Safresh1 my $b; 6709f11ffb7Safresh1 while ($j-- > 0) { 671b8851fccSafresh1 $b = $r * $BASE + $x->[$j]; 672b8851fccSafresh1 $r = $b % $y; 67356d68f1eSafresh1 $x->[$j] = $b / $y; 674b8851fccSafresh1 } 67556d68f1eSafresh1 pop(@$x) if @$x > 1 && $x->[-1] == 0; # remove any trailing zero 676b8851fccSafresh1 return ($x, $rem) if wantarray; 677b8851fccSafresh1 return $x; 678b8851fccSafresh1 } 67956d68f1eSafresh1 680b8851fccSafresh1 # now x and y have more than one element 681b8851fccSafresh1 68256d68f1eSafresh1 # check whether y has more elements than x, if so, the result is 0 6839f11ffb7Safresh1 if (@$yorg > @$x) { 684b8851fccSafresh1 my $rem; 6859f11ffb7Safresh1 $rem = $c->_copy($x) if wantarray; # make copy 6869f11ffb7Safresh1 @$x = 0; # set to 0 687b8851fccSafresh1 return ($x, $rem) if wantarray; # including remainder? 688b8851fccSafresh1 return $x; # only x, which is [0] now 689b8851fccSafresh1 } 69056d68f1eSafresh1 691b8851fccSafresh1 # check whether the numbers have the same number of elements, in that case 692b8851fccSafresh1 # the result will fit into one element and can be computed efficiently 6939f11ffb7Safresh1 if (@$yorg == @$x) { 69456d68f1eSafresh1 my $cmp = 0; 69556d68f1eSafresh1 for (my $j = $#$x ; $j >= 0 ; --$j) { 69656d68f1eSafresh1 last if $cmp = $x->[$j] - $yorg->[$j]; 69756d68f1eSafresh1 } 69856d68f1eSafresh1 69956d68f1eSafresh1 if ($cmp == 0) { # x = y 70056d68f1eSafresh1 @$x = 1; 70156d68f1eSafresh1 return $x, $c->_zero() if wantarray; 702b8851fccSafresh1 return $x; 703b8851fccSafresh1 } 704b8851fccSafresh1 70556d68f1eSafresh1 if ($cmp < 0) { # x < y 70656d68f1eSafresh1 if (wantarray) { 70756d68f1eSafresh1 my $rem = $c->_copy($x); 70856d68f1eSafresh1 @$x = 0; 70956d68f1eSafresh1 return $x, $rem; 710b8851fccSafresh1 } 71156d68f1eSafresh1 @$x = 0; 712b8851fccSafresh1 return $x; 713b8851fccSafresh1 } 714b8851fccSafresh1 } 715b8851fccSafresh1 716b8851fccSafresh1 # all other cases: 717b8851fccSafresh1 7189f11ffb7Safresh1 my $y = $c->_copy($yorg); # always make copy to preserve 719b8851fccSafresh1 72056d68f1eSafresh1 my $tmp; 72156d68f1eSafresh1 my $dd = $BASE / ($y->[-1] + 1); 72256d68f1eSafresh1 if ($dd != 1) { 72356d68f1eSafresh1 my $car = 0; 72456d68f1eSafresh1 for my $xi (@$x) { 725b8851fccSafresh1 $xi = $xi * $dd + $car; 72656d68f1eSafresh1 $xi -= ($car = $xi / $BASE) * $BASE; 727b8851fccSafresh1 } 7289f11ffb7Safresh1 push(@$x, $car); 7299f11ffb7Safresh1 $car = 0; 73056d68f1eSafresh1 for my $yi (@$y) { 731b8851fccSafresh1 $yi = $yi * $dd + $car; 73256d68f1eSafresh1 $yi -= ($car = $yi / $BASE) * $BASE; 733b8851fccSafresh1 } 7349f11ffb7Safresh1 } else { 735b8851fccSafresh1 push(@$x, 0); 736b8851fccSafresh1 } 737b8851fccSafresh1 738b8851fccSafresh1 # @q will accumulate the final result, $q contains the current computed 739b8851fccSafresh1 # part of the final result 740b8851fccSafresh1 74156d68f1eSafresh1 my @q = (); 74256d68f1eSafresh1 my ($v2, $v1) = @$y[-2, -1]; 743b8851fccSafresh1 $v2 = 0 unless $v2; 7449f11ffb7Safresh1 while ($#$x > $#$y) { 74556d68f1eSafresh1 my ($u2, $u1, $u0) = @$x[-3 .. -1]; 746b8851fccSafresh1 $u2 = 0 unless $u2; 747b8851fccSafresh1 #warn "oups v1 is 0, u0: $u0 $y->[-2] $y->[-1] l ",scalar @$y,"\n" 748b8851fccSafresh1 # if $v1 == 0; 74956d68f1eSafresh1 my $tmp = $u0 * $BASE + $u1; 75056d68f1eSafresh1 my $rem = $tmp % $v1; 75156d68f1eSafresh1 my $q = $u0 == $v1 ? $MAX_VAL : (($tmp - $rem) / $v1); 75256d68f1eSafresh1 --$q while $v2 * $q > ($u0 * $BASE + $u1 - $q * $v1) * $BASE + $u2; 7539f11ffb7Safresh1 if ($q) { 75456d68f1eSafresh1 my $prd; 75556d68f1eSafresh1 my ($car, $bar) = (0, 0); 75656d68f1eSafresh1 for (my $yi = 0, my $xi = $#$x - $#$y - 1; $yi <= $#$y; ++$yi, ++$xi) { 757b8851fccSafresh1 $prd = $q * $y->[$yi] + $car; 758b8851fccSafresh1 $prd -= ($car = int($prd / $BASE)) * $BASE; 75956d68f1eSafresh1 $x->[$xi] += $BASE if $bar = (($x->[$xi] -= $prd + $bar) < 0); 760b8851fccSafresh1 } 7619f11ffb7Safresh1 if ($x->[-1] < $car + $bar) { 7629f11ffb7Safresh1 $car = 0; 7639f11ffb7Safresh1 --$q; 76456d68f1eSafresh1 for (my $yi = 0, my $xi = $#$x - $#$y - 1; $yi <= $#$y; ++$yi, ++$xi) { 765b8851fccSafresh1 $x->[$xi] -= $BASE 76656d68f1eSafresh1 if $car = (($x->[$xi] += $y->[$yi] + $car) >= $BASE); 767b8851fccSafresh1 } 768b8851fccSafresh1 } 769b8851fccSafresh1 } 7709f11ffb7Safresh1 pop(@$x); 7719f11ffb7Safresh1 unshift(@q, $q); 772b8851fccSafresh1 } 77356d68f1eSafresh1 7749f11ffb7Safresh1 if (wantarray) { 7759f11ffb7Safresh1 my $d = bless [], $c; 7769f11ffb7Safresh1 if ($dd != 1) { 77756d68f1eSafresh1 my $car = 0; 77856d68f1eSafresh1 my $prd; 77956d68f1eSafresh1 for my $xi (reverse @$x) { 780b8851fccSafresh1 $prd = $car * $BASE + $xi; 78156d68f1eSafresh1 $car = $prd - ($tmp = $prd / $dd) * $dd; 78256d68f1eSafresh1 unshift @$d, $tmp; 783b8851fccSafresh1 } 7849f11ffb7Safresh1 } else { 7859f11ffb7Safresh1 @$d = @$x; 786b8851fccSafresh1 } 787b8851fccSafresh1 @$x = @q; 788b8851fccSafresh1 __strip_zeros($x); 789b8851fccSafresh1 __strip_zeros($d); 790b8851fccSafresh1 return ($x, $d); 791b8851fccSafresh1 } 792b8851fccSafresh1 @$x = @q; 793b8851fccSafresh1 __strip_zeros($x); 794b8851fccSafresh1 $x; 795b8851fccSafresh1} 796b8851fccSafresh1 797eac174f2Safresh1sub _div_no_int { 798b8851fccSafresh1 # ref to array, ref to array, modify first array and return remainder if 799b8851fccSafresh1 # in list context 80056d68f1eSafresh1 801b8851fccSafresh1 my ($c, $x, $yorg) = @_; 802b8851fccSafresh1 803b8851fccSafresh1 # the general div algorithm here is about O(N*N) and thus quite slow, so 804b8851fccSafresh1 # we first check for some special cases and use shortcuts to handle them. 805b8851fccSafresh1 806b8851fccSafresh1 # if both numbers have only one element: 8079f11ffb7Safresh1 if (@$x == 1 && @$yorg == 1) { 808b8851fccSafresh1 # shortcut, $yorg and $x are two small numbers 8099f11ffb7Safresh1 my $rem = [ $x->[0] % $yorg->[0] ]; 8109f11ffb7Safresh1 bless $rem, $c; 81156d68f1eSafresh1 $x->[0] = ($x->[0] - $rem->[0]) / $yorg->[0]; 81256d68f1eSafresh1 return ($x, $rem) if wantarray; 813b8851fccSafresh1 return $x; 814b8851fccSafresh1 } 81556d68f1eSafresh1 816b8851fccSafresh1 # if x has more than one, but y has only one element: 8179f11ffb7Safresh1 if (@$yorg == 1) { 818b8851fccSafresh1 my $rem; 8199f11ffb7Safresh1 $rem = $c->_mod($c->_copy($x), $yorg) if wantarray; 820b8851fccSafresh1 821b8851fccSafresh1 # shortcut, $y is < $BASE 8229f11ffb7Safresh1 my $j = @$x; 8239f11ffb7Safresh1 my $r = 0; 8249f11ffb7Safresh1 my $y = $yorg->[0]; 8259f11ffb7Safresh1 my $b; 8269f11ffb7Safresh1 while ($j-- > 0) { 827b8851fccSafresh1 $b = $r * $BASE + $x->[$j]; 828b8851fccSafresh1 $r = $b % $y; 82956d68f1eSafresh1 $x->[$j] = ($b - $r) / $y; 830b8851fccSafresh1 } 83156d68f1eSafresh1 pop(@$x) if @$x > 1 && $x->[-1] == 0; # remove any trailing zero 832b8851fccSafresh1 return ($x, $rem) if wantarray; 833b8851fccSafresh1 return $x; 834b8851fccSafresh1 } 83556d68f1eSafresh1 836b8851fccSafresh1 # now x and y have more than one element 837b8851fccSafresh1 83856d68f1eSafresh1 # check whether y has more elements than x, if so, the result is 0 8399f11ffb7Safresh1 if (@$yorg > @$x) { 840b8851fccSafresh1 my $rem; 8419f11ffb7Safresh1 $rem = $c->_copy($x) if wantarray; # make copy 8429f11ffb7Safresh1 @$x = 0; # set to 0 843b8851fccSafresh1 return ($x, $rem) if wantarray; # including remainder? 844b8851fccSafresh1 return $x; # only x, which is [0] now 845b8851fccSafresh1 } 84656d68f1eSafresh1 847b8851fccSafresh1 # check whether the numbers have the same number of elements, in that case 848b8851fccSafresh1 # the result will fit into one element and can be computed efficiently 8499f11ffb7Safresh1 if (@$yorg == @$x) { 85056d68f1eSafresh1 my $cmp = 0; 85156d68f1eSafresh1 for (my $j = $#$x ; $j >= 0 ; --$j) { 85256d68f1eSafresh1 last if $cmp = $x->[$j] - $yorg->[$j]; 85356d68f1eSafresh1 } 85456d68f1eSafresh1 85556d68f1eSafresh1 if ($cmp == 0) { # x = y 85656d68f1eSafresh1 @$x = 1; 85756d68f1eSafresh1 return $x, $c->_zero() if wantarray; 858b8851fccSafresh1 return $x; 859b8851fccSafresh1 } 860b8851fccSafresh1 86156d68f1eSafresh1 if ($cmp < 0) { # x < y 86256d68f1eSafresh1 if (wantarray) { 86356d68f1eSafresh1 my $rem = $c->_copy($x); 8649f11ffb7Safresh1 @$x = 0; 86556d68f1eSafresh1 return $x, $rem; 866b8851fccSafresh1 } 86756d68f1eSafresh1 @$x = 0; 86856d68f1eSafresh1 return $x; 869b8851fccSafresh1 } 870b8851fccSafresh1 } 871b8851fccSafresh1 872b8851fccSafresh1 # all other cases: 873b8851fccSafresh1 8749f11ffb7Safresh1 my $y = $c->_copy($yorg); # always make copy to preserve 875b8851fccSafresh1 87656d68f1eSafresh1 my $tmp = $y->[-1] + 1; 87756d68f1eSafresh1 my $rem = $BASE % $tmp; 87856d68f1eSafresh1 my $dd = ($BASE - $rem) / $tmp; 87956d68f1eSafresh1 if ($dd != 1) { 88056d68f1eSafresh1 my $car = 0; 88156d68f1eSafresh1 for my $xi (@$x) { 882b8851fccSafresh1 $xi = $xi * $dd + $car; 88356d68f1eSafresh1 $rem = $xi % $BASE; 88456d68f1eSafresh1 $car = ($xi - $rem) / $BASE; 88556d68f1eSafresh1 $xi = $rem; 886b8851fccSafresh1 } 8879f11ffb7Safresh1 push(@$x, $car); 8889f11ffb7Safresh1 $car = 0; 88956d68f1eSafresh1 for my $yi (@$y) { 890b8851fccSafresh1 $yi = $yi * $dd + $car; 89156d68f1eSafresh1 $rem = $yi % $BASE; 89256d68f1eSafresh1 $car = ($yi - $rem) / $BASE; 89356d68f1eSafresh1 $yi = $rem; 894b8851fccSafresh1 } 8959f11ffb7Safresh1 } else { 896b8851fccSafresh1 push(@$x, 0); 897b8851fccSafresh1 } 898b8851fccSafresh1 899b8851fccSafresh1 # @q will accumulate the final result, $q contains the current computed 900b8851fccSafresh1 # part of the final result 901b8851fccSafresh1 90256d68f1eSafresh1 my @q = (); 90356d68f1eSafresh1 my ($v2, $v1) = @$y[-2, -1]; 904b8851fccSafresh1 $v2 = 0 unless $v2; 9059f11ffb7Safresh1 while ($#$x > $#$y) { 90656d68f1eSafresh1 my ($u2, $u1, $u0) = @$x[-3 .. -1]; 907b8851fccSafresh1 $u2 = 0 unless $u2; 908b8851fccSafresh1 #warn "oups v1 is 0, u0: $u0 $y->[-2] $y->[-1] l ",scalar @$y,"\n" 909b8851fccSafresh1 # if $v1 == 0; 91056d68f1eSafresh1 my $tmp = $u0 * $BASE + $u1; 91156d68f1eSafresh1 my $rem = $tmp % $v1; 91256d68f1eSafresh1 my $q = $u0 == $v1 ? $MAX_VAL : (($tmp - $rem) / $v1); 91356d68f1eSafresh1 --$q while $v2 * $q > ($u0 * $BASE + $u1 - $q * $v1) * $BASE + $u2; 9149f11ffb7Safresh1 if ($q) { 91556d68f1eSafresh1 my $prd; 91656d68f1eSafresh1 my ($car, $bar) = (0, 0); 91756d68f1eSafresh1 for (my $yi = 0, my $xi = $#$x - $#$y - 1; $yi <= $#$y; ++$yi, ++$xi) { 918b8851fccSafresh1 $prd = $q * $y->[$yi] + $car; 91956d68f1eSafresh1 $rem = $prd % $BASE; 92056d68f1eSafresh1 $car = ($prd - $rem) / $BASE; 92156d68f1eSafresh1 $prd -= $car * $BASE; 92256d68f1eSafresh1 $x->[$xi] += $BASE if $bar = (($x->[$xi] -= $prd + $bar) < 0); 923b8851fccSafresh1 } 9249f11ffb7Safresh1 if ($x->[-1] < $car + $bar) { 9259f11ffb7Safresh1 $car = 0; 9269f11ffb7Safresh1 --$q; 92756d68f1eSafresh1 for (my $yi = 0, my $xi = $#$x - $#$y - 1; $yi <= $#$y; ++$yi, ++$xi) { 928b8851fccSafresh1 $x->[$xi] -= $BASE 92956d68f1eSafresh1 if $car = (($x->[$xi] += $y->[$yi] + $car) >= $BASE); 930b8851fccSafresh1 } 931b8851fccSafresh1 } 932b8851fccSafresh1 } 9339f11ffb7Safresh1 pop(@$x); 9349f11ffb7Safresh1 unshift(@q, $q); 935b8851fccSafresh1 } 93656d68f1eSafresh1 9379f11ffb7Safresh1 if (wantarray) { 9389f11ffb7Safresh1 my $d = bless [], $c; 9399f11ffb7Safresh1 if ($dd != 1) { 94056d68f1eSafresh1 my $car = 0; 94156d68f1eSafresh1 my ($prd, $rem); 94256d68f1eSafresh1 for my $xi (reverse @$x) { 943b8851fccSafresh1 $prd = $car * $BASE + $xi; 94456d68f1eSafresh1 $rem = $prd % $dd; 94556d68f1eSafresh1 $tmp = ($prd - $rem) / $dd; 94656d68f1eSafresh1 $car = $rem; 94756d68f1eSafresh1 unshift @$d, $tmp; 948b8851fccSafresh1 } 9499f11ffb7Safresh1 } else { 9509f11ffb7Safresh1 @$d = @$x; 951b8851fccSafresh1 } 952b8851fccSafresh1 @$x = @q; 953b8851fccSafresh1 __strip_zeros($x); 954b8851fccSafresh1 __strip_zeros($d); 955b8851fccSafresh1 return ($x, $d); 956b8851fccSafresh1 } 957b8851fccSafresh1 @$x = @q; 958b8851fccSafresh1 __strip_zeros($x); 959b8851fccSafresh1 $x; 960b8851fccSafresh1} 961b8851fccSafresh1 962b8851fccSafresh1############################################################################## 963b8851fccSafresh1# testing 964b8851fccSafresh1 965b8851fccSafresh1sub _acmp { 966b8851fccSafresh1 # Internal absolute post-normalized compare (ignore signs) 967b8851fccSafresh1 # ref to array, ref to array, return <0, 0, >0 968b8851fccSafresh1 # Arrays must have at least one entry; this is not checked for. 969b8851fccSafresh1 my ($c, $cx, $cy) = @_; 970b8851fccSafresh1 971b8851fccSafresh1 # shortcut for short numbers 972b8851fccSafresh1 return (($cx->[0] <=> $cy->[0]) <=> 0) 9739f11ffb7Safresh1 if @$cx == 1 && @$cy == 1; 974b8851fccSafresh1 975b8851fccSafresh1 # fast comp based on number of array elements (aka pseudo-length) 976b8851fccSafresh1 my $lxy = (@$cx - @$cy) 977b8851fccSafresh1 # or length of first element if same number of elements (aka difference 0) 978b8851fccSafresh1 || 979b8851fccSafresh1 # need int() here because sometimes the last element is '00018' vs '18' 980b8851fccSafresh1 (length(int($cx->[-1])) - length(int($cy->[-1]))); 981b8851fccSafresh1 982b8851fccSafresh1 return -1 if $lxy < 0; # already differs, ret 983b8851fccSafresh1 return 1 if $lxy > 0; # ditto 984b8851fccSafresh1 985b8851fccSafresh1 # manual way (abort if unequal, good for early ne) 986b8851fccSafresh1 my $a; 987b8851fccSafresh1 my $j = @$cx; 988b8851fccSafresh1 while (--$j >= 0) { 989b8851fccSafresh1 last if $a = $cx->[$j] - $cy->[$j]; 990b8851fccSafresh1 } 991b8851fccSafresh1 $a <=> 0; 992b8851fccSafresh1} 993b8851fccSafresh1 994b8851fccSafresh1sub _len { 995b8851fccSafresh1 # compute number of digits in base 10 996b8851fccSafresh1 997b8851fccSafresh1 # int() because add/sub sometimes leaves strings (like '00005') instead of 998b8851fccSafresh1 # '5' in this place, thus causing length() to report wrong length 999b8851fccSafresh1 my $cx = $_[1]; 1000b8851fccSafresh1 1001b8851fccSafresh1 (@$cx - 1) * $BASE_LEN + length(int($cx->[-1])); 1002b8851fccSafresh1} 1003b8851fccSafresh1 1004b8851fccSafresh1sub _digit { 1005b8851fccSafresh1 # Return the nth digit. Zero is rightmost, so _digit(123, 0) gives 3. 1006b8851fccSafresh1 # Negative values count from the left, so _digit(123, -1) gives 1. 1007b8851fccSafresh1 my ($c, $x, $n) = @_; 1008b8851fccSafresh1 1009b8851fccSafresh1 my $len = _len('', $x); 1010b8851fccSafresh1 1011b8851fccSafresh1 $n += $len if $n < 0; # -1 last, -2 second-to-last 10129f11ffb7Safresh1 10139f11ffb7Safresh1 # Math::BigInt::Calc returns 0 if N is out of range, but this is not done 10149f11ffb7Safresh1 # by the other backend libraries. 10159f11ffb7Safresh1 1016b8851fccSafresh1 return "0" if $n < 0 || $n >= $len; # return 0 for digits out of range 1017b8851fccSafresh1 10189f11ffb7Safresh1 my $elem = int($n / $BASE_LEN); # index of array element 10199f11ffb7Safresh1 my $digit = $n % $BASE_LEN; # index of digit within the element 10209f11ffb7Safresh1 substr("0" x $BASE_LEN . "$x->[$elem]", -1 - $digit, 1); 1021b8851fccSafresh1} 1022b8851fccSafresh1 1023b8851fccSafresh1sub _zeros { 1024b8851fccSafresh1 # Return number of trailing zeros in decimal. 1025b8851fccSafresh1 # Check each array element for having 0 at end as long as elem == 0 1026b8851fccSafresh1 # Upon finding a elem != 0, stop. 1027b8851fccSafresh1 1028b8851fccSafresh1 my $x = $_[1]; 1029b8851fccSafresh1 1030b8851fccSafresh1 return 0 if @$x == 1 && $x->[0] == 0; 1031b8851fccSafresh1 1032b8851fccSafresh1 my $zeros = 0; 10339f11ffb7Safresh1 foreach my $elem (@$x) { 10349f11ffb7Safresh1 if ($elem != 0) { 10359f11ffb7Safresh1 $elem =~ /[^0](0*)\z/; 10369f11ffb7Safresh1 $zeros += length($1); # count trailing zeros 1037b8851fccSafresh1 last; # early out 1038b8851fccSafresh1 } 10399f11ffb7Safresh1 $zeros += $BASE_LEN; 1040b8851fccSafresh1 } 1041b8851fccSafresh1 $zeros; 1042b8851fccSafresh1} 1043b8851fccSafresh1 1044b8851fccSafresh1############################################################################## 1045b8851fccSafresh1# _is_* routines 1046b8851fccSafresh1 1047b8851fccSafresh1sub _is_zero { 1048b8851fccSafresh1 # return true if arg is zero 1049b8851fccSafresh1 @{$_[1]} == 1 && $_[1]->[0] == 0 ? 1 : 0; 1050b8851fccSafresh1} 1051b8851fccSafresh1 1052b8851fccSafresh1sub _is_even { 1053b8851fccSafresh1 # return true if arg is even 1054eac174f2Safresh1 $_[1]->[0] % 2 ? 0 : 1; 1055b8851fccSafresh1} 1056b8851fccSafresh1 1057b8851fccSafresh1sub _is_odd { 1058b8851fccSafresh1 # return true if arg is odd 1059eac174f2Safresh1 $_[1]->[0] % 2 ? 1 : 0; 1060b8851fccSafresh1} 1061b8851fccSafresh1 1062b8851fccSafresh1sub _is_one { 1063b8851fccSafresh1 # return true if arg is one 1064b8851fccSafresh1 @{$_[1]} == 1 && $_[1]->[0] == 1 ? 1 : 0; 1065b8851fccSafresh1} 1066b8851fccSafresh1 1067b8851fccSafresh1sub _is_two { 1068b8851fccSafresh1 # return true if arg is two 1069b8851fccSafresh1 @{$_[1]} == 1 && $_[1]->[0] == 2 ? 1 : 0; 1070b8851fccSafresh1} 1071b8851fccSafresh1 1072b8851fccSafresh1sub _is_ten { 1073b8851fccSafresh1 # return true if arg is ten 1074eac174f2Safresh1 if ($BASE_LEN == 1) { 1075eac174f2Safresh1 @{$_[1]} == 2 && $_[1]->[0] == 0 && $_[1]->[1] == 1 ? 1 : 0; 1076eac174f2Safresh1 } else { 1077b8851fccSafresh1 @{$_[1]} == 1 && $_[1]->[0] == 10 ? 1 : 0; 1078b8851fccSafresh1 } 1079eac174f2Safresh1} 1080b8851fccSafresh1 1081b8851fccSafresh1sub __strip_zeros { 1082b8851fccSafresh1 # Internal normalization function that strips leading zeros from the array. 1083b8851fccSafresh1 # Args: ref to array 10849f11ffb7Safresh1 my $x = shift; 1085b8851fccSafresh1 10869f11ffb7Safresh1 push @$x, 0 if @$x == 0; # div might return empty results, so fix it 10879f11ffb7Safresh1 return $x if @$x == 1; # early out 1088b8851fccSafresh1 1089b8851fccSafresh1 #print "strip: cnt $cnt i $i\n"; 1090b8851fccSafresh1 # '0', '3', '4', '0', '0', 1091b8851fccSafresh1 # 0 1 2 3 4 1092b8851fccSafresh1 # cnt = 5, i = 4 1093b8851fccSafresh1 # i = 4 1094b8851fccSafresh1 # i = 3 1095b8851fccSafresh1 # => fcnt = cnt - i (5-2 => 3, cnt => 5-1 = 4, throw away from 4th pos) 1096b8851fccSafresh1 # >= 1: skip first part (this can be zero) 10979f11ffb7Safresh1 10989f11ffb7Safresh1 my $i = $#$x; 1099b8851fccSafresh1 while ($i > 0) { 11009f11ffb7Safresh1 last if $x->[$i] != 0; 1101b8851fccSafresh1 $i--; 1102b8851fccSafresh1 } 1103b8851fccSafresh1 $i++; 11049f11ffb7Safresh1 splice(@$x, $i) if $i < @$x; 11059f11ffb7Safresh1 $x; 1106b8851fccSafresh1} 1107b8851fccSafresh1 1108b8851fccSafresh1############################################################################### 1109b8851fccSafresh1# check routine to test internal state for corruptions 1110b8851fccSafresh1 1111b8851fccSafresh1sub _check { 1112b8851fccSafresh1 # used by the test suite 11139f11ffb7Safresh1 my ($class, $x) = @_; 1114b8851fccSafresh1 11159f11ffb7Safresh1 my $msg = $class -> SUPER::_check($x); 11169f11ffb7Safresh1 return $msg if $msg; 1117b8851fccSafresh1 11189f11ffb7Safresh1 my $n; 11199f11ffb7Safresh1 eval { $n = @$x }; 11209f11ffb7Safresh1 return "Not an array reference" unless $@ eq ''; 11219f11ffb7Safresh1 11229f11ffb7Safresh1 return "Reference to an empty array" unless $n > 0; 11239f11ffb7Safresh1 11249f11ffb7Safresh1 # The following fails with Math::BigInt::FastCalc because a 11259f11ffb7Safresh1 # Math::BigInt::FastCalc "object" is an unblessed array ref. 11269f11ffb7Safresh1 # 11279f11ffb7Safresh1 #return 0 unless ref($x) eq $class; 11289f11ffb7Safresh1 11299f11ffb7Safresh1 for (my $i = 0 ; $i <= $#$x ; ++ $i) { 11309f11ffb7Safresh1 my $e = $x -> [$i]; 11319f11ffb7Safresh1 11329f11ffb7Safresh1 return "Element at index $i is undefined" 11339f11ffb7Safresh1 unless defined $e; 11349f11ffb7Safresh1 11359f11ffb7Safresh1 return "Element at index $i is a '" . ref($e) . 11369f11ffb7Safresh1 "', which is not a scalar" 11379f11ffb7Safresh1 unless ref($e) eq ""; 11389f11ffb7Safresh1 11399f11ffb7Safresh1 # It would be better to use the regex /^([1-9]\d*|0)\z/, but that fails 11409f11ffb7Safresh1 # in Math::BigInt::FastCalc, because it sometimes creates array 11419f11ffb7Safresh1 # elements like "000000". 11429f11ffb7Safresh1 return "Element at index $i is '$e', which does not look like an" . 11439f11ffb7Safresh1 " normal integer" unless $e =~ /^\d+\z/; 11449f11ffb7Safresh1 11459f11ffb7Safresh1 return "Element at index $i is '$e', which is not smaller than" . 11469f11ffb7Safresh1 " the base '$BASE'" if $e >= $BASE; 11479f11ffb7Safresh1 11489f11ffb7Safresh1 return "Element at index $i (last element) is zero" 11499f11ffb7Safresh1 if $#$x > 0 && $i == $#$x && $e == 0; 1150b8851fccSafresh1 } 11519f11ffb7Safresh1 11529f11ffb7Safresh1 return 0; 1153b8851fccSafresh1} 1154b8851fccSafresh1 1155b8851fccSafresh1############################################################################### 1156b8851fccSafresh1 1157b8851fccSafresh1sub _mod { 1158b8851fccSafresh1 # if possible, use mod shortcut 1159b8851fccSafresh1 my ($c, $x, $yo) = @_; 1160b8851fccSafresh1 1161b8851fccSafresh1 # slow way since $y too big 1162b8851fccSafresh1 if (@$yo > 1) { 11639f11ffb7Safresh1 my ($xo, $rem) = $c->_div($x, $yo); 1164b8851fccSafresh1 @$x = @$rem; 1165b8851fccSafresh1 return $x; 1166b8851fccSafresh1 } 1167b8851fccSafresh1 1168b8851fccSafresh1 my $y = $yo->[0]; 1169b8851fccSafresh1 1170b8851fccSafresh1 # if both are single element arrays 11719f11ffb7Safresh1 if (@$x == 1) { 1172b8851fccSafresh1 $x->[0] %= $y; 1173b8851fccSafresh1 return $x; 1174b8851fccSafresh1 } 1175b8851fccSafresh1 1176b8851fccSafresh1 # if @$x has more than one element, but @$y is a single element 1177b8851fccSafresh1 my $b = $BASE % $y; 1178b8851fccSafresh1 if ($b == 0) { 1179b8851fccSafresh1 # when BASE % Y == 0 then (B * BASE) % Y == 0 1180b8851fccSafresh1 # (B * BASE) % $y + A % Y => A % Y 1181b8851fccSafresh1 # so need to consider only last element: O(1) 1182b8851fccSafresh1 $x->[0] %= $y; 1183b8851fccSafresh1 } elsif ($b == 1) { 1184b8851fccSafresh1 # else need to go through all elements in @$x: O(N), but loop is a bit 1185b8851fccSafresh1 # simplified 1186b8851fccSafresh1 my $r = 0; 1187b8851fccSafresh1 foreach (@$x) { 1188b8851fccSafresh1 $r = ($r + $_) % $y; # not much faster, but heh... 1189b8851fccSafresh1 #$r += $_ % $y; $r %= $y; 1190b8851fccSafresh1 } 1191b8851fccSafresh1 $r = 0 if $r == $y; 1192b8851fccSafresh1 $x->[0] = $r; 1193b8851fccSafresh1 } else { 1194b8851fccSafresh1 # else need to go through all elements in @$x: O(N) 1195b8851fccSafresh1 my $r = 0; 1196b8851fccSafresh1 my $bm = 1; 1197b8851fccSafresh1 foreach (@$x) { 1198b8851fccSafresh1 $r = ($_ * $bm + $r) % $y; 1199b8851fccSafresh1 $bm = ($bm * $b) % $y; 1200b8851fccSafresh1 1201b8851fccSafresh1 #$r += ($_ % $y) * $bm; 1202b8851fccSafresh1 #$bm *= $b; 1203b8851fccSafresh1 #$bm %= $y; 1204b8851fccSafresh1 #$r %= $y; 1205b8851fccSafresh1 } 1206b8851fccSafresh1 $r = 0 if $r == $y; 1207b8851fccSafresh1 $x->[0] = $r; 1208b8851fccSafresh1 } 1209b8851fccSafresh1 @$x = $x->[0]; # keep one element of @$x 1210b8851fccSafresh1 return $x; 1211b8851fccSafresh1} 1212b8851fccSafresh1 1213b8851fccSafresh1############################################################################## 1214b8851fccSafresh1# shifts 1215b8851fccSafresh1 1216b8851fccSafresh1sub _rsft { 1217eac174f2Safresh1 my ($c, $x, $n, $b) = @_; 1218eac174f2Safresh1 return $x if $c->_is_zero($x) || $c->_is_zero($n); 1219b8851fccSafresh1 1220eac174f2Safresh1 # For backwards compatibility, allow the base $b to be a scalar. 1221eac174f2Safresh1 1222eac174f2Safresh1 $b = $c->_new($b) unless ref $b; 1223eac174f2Safresh1 1224eac174f2Safresh1 if ($c -> _acmp($b, $c -> _ten())) { 1225eac174f2Safresh1 return scalar $c->_div($x, $c->_pow($c->_copy($b), $n)); 1226b8851fccSafresh1 } 1227b8851fccSafresh1 1228b8851fccSafresh1 # shortcut (faster) for shifting by 10) 1229b8851fccSafresh1 # multiples of $BASE_LEN 1230b8851fccSafresh1 my $dst = 0; # destination 1231eac174f2Safresh1 my $src = $c->_num($n); # as normal int 1232b8851fccSafresh1 my $xlen = (@$x - 1) * $BASE_LEN + length(int($x->[-1])); 1233b8851fccSafresh1 if ($src >= $xlen or ($src == $xlen and !defined $x->[1])) { 1234b8851fccSafresh1 # 12345 67890 shifted right by more than 10 digits => 0 1235b8851fccSafresh1 splice(@$x, 1); # leave only one element 1236b8851fccSafresh1 $x->[0] = 0; # set to zero 1237b8851fccSafresh1 return $x; 1238b8851fccSafresh1 } 1239b8851fccSafresh1 my $rem = $src % $BASE_LEN; # remainder to shift 1240b8851fccSafresh1 $src = int($src / $BASE_LEN); # source 1241b8851fccSafresh1 if ($rem == 0) { 1242b8851fccSafresh1 splice(@$x, 0, $src); # even faster, 38.4 => 39.3 1243b8851fccSafresh1 } else { 1244b8851fccSafresh1 my $len = @$x - $src; # elems to go 1245b8851fccSafresh1 my $vd; 1246b8851fccSafresh1 my $z = '0' x $BASE_LEN; 1247b8851fccSafresh1 $x->[ @$x ] = 0; # avoid || 0 test inside loop 1248b8851fccSafresh1 while ($dst < $len) { 1249b8851fccSafresh1 $vd = $z . $x->[$src]; 1250b8851fccSafresh1 $vd = substr($vd, -$BASE_LEN, $BASE_LEN - $rem); 1251b8851fccSafresh1 $src++; 1252b8851fccSafresh1 $vd = substr($z . $x->[$src], -$rem, $rem) . $vd; 1253b8851fccSafresh1 $vd = substr($vd, -$BASE_LEN, $BASE_LEN) if length($vd) > $BASE_LEN; 1254b8851fccSafresh1 $x->[$dst] = int($vd); 1255b8851fccSafresh1 $dst++; 1256b8851fccSafresh1 } 1257b8851fccSafresh1 splice(@$x, $dst) if $dst > 0; # kill left-over array elems 125856d68f1eSafresh1 pop(@$x) if $x->[-1] == 0 && @$x > 1; # kill last element if 0 1259b8851fccSafresh1 } # else rem == 0 1260b8851fccSafresh1 $x; 1261b8851fccSafresh1} 1262b8851fccSafresh1 1263b8851fccSafresh1sub _lsft { 12649f11ffb7Safresh1 my ($c, $x, $n, $b) = @_; 1265b8851fccSafresh1 126656d68f1eSafresh1 return $x if $c->_is_zero($x) || $c->_is_zero($n); 12679f11ffb7Safresh1 126856d68f1eSafresh1 # For backwards compatibility, allow the base $b to be a scalar. 12699f11ffb7Safresh1 127056d68f1eSafresh1 $b = $c->_new($b) unless ref $b; 1271b8851fccSafresh1 127256d68f1eSafresh1 # If the base is a power of 10, use shifting, since the internal 127356d68f1eSafresh1 # representation is in base 10eX. 127456d68f1eSafresh1 127556d68f1eSafresh1 my $bstr = $c->_str($b); 127656d68f1eSafresh1 if ($bstr =~ /^1(0+)\z/) { 127756d68f1eSafresh1 127856d68f1eSafresh1 # Adjust $n so that we're shifting in base 10. Do this by multiplying 127956d68f1eSafresh1 # $n by the base 10 logarithm of $b: $b ** $n = 10 ** (log10($b) * $n). 128056d68f1eSafresh1 128156d68f1eSafresh1 my $log10b = length($1); 128256d68f1eSafresh1 $n = $c->_mul($c->_new($log10b), $n); 128356d68f1eSafresh1 $n = $c->_num($n); # shift-len as normal int 128456d68f1eSafresh1 128556d68f1eSafresh1 # $q is the number of places to shift the elements within the array, 128656d68f1eSafresh1 # and $r is the number of places to shift the values within the 128756d68f1eSafresh1 # elements. 128856d68f1eSafresh1 128956d68f1eSafresh1 my $r = $n % $BASE_LEN; 129056d68f1eSafresh1 my $q = ($n - $r) / $BASE_LEN; 129156d68f1eSafresh1 129256d68f1eSafresh1 # If we must shift the values within the elements ... 129356d68f1eSafresh1 129456d68f1eSafresh1 if ($r) { 129556d68f1eSafresh1 my $i = @$x; # index 129656d68f1eSafresh1 $x->[$i] = 0; # initialize most significant element 1297b8851fccSafresh1 my $z = '0' x $BASE_LEN; 129856d68f1eSafresh1 my $vd; 129956d68f1eSafresh1 while ($i >= 0) { 130056d68f1eSafresh1 $vd = $x->[$i]; 1301b8851fccSafresh1 $vd = $z . $vd; 130256d68f1eSafresh1 $vd = substr($vd, $r - $BASE_LEN, $BASE_LEN - $r); 130356d68f1eSafresh1 $vd .= $i > 0 ? substr($z . $x->[$i - 1], -$BASE_LEN, $r) 130456d68f1eSafresh1 : '0' x $r; 1305b8851fccSafresh1 $vd = substr($vd, -$BASE_LEN, $BASE_LEN) if length($vd) > $BASE_LEN; 130656d68f1eSafresh1 $x->[$i] = int($vd); # e.g., "0...048" -> 48 etc. 130756d68f1eSafresh1 $i--; 1308b8851fccSafresh1 } 130956d68f1eSafresh1 131056d68f1eSafresh1 pop(@$x) if $x->[-1] == 0; # if most significant element is zero 1311b8851fccSafresh1 } 131256d68f1eSafresh1 131356d68f1eSafresh1 # If we must shift the elements within the array ... 131456d68f1eSafresh1 131556d68f1eSafresh1 if ($q) { 131656d68f1eSafresh1 unshift @$x, (0) x $q; 131756d68f1eSafresh1 } 131856d68f1eSafresh1 13199f11ffb7Safresh1 } else { 132056d68f1eSafresh1 $x = $c->_mul($x, $c->_pow($b, $n)); 13219f11ffb7Safresh1 } 132256d68f1eSafresh1 132356d68f1eSafresh1 return $x; 1324b8851fccSafresh1} 1325b8851fccSafresh1 1326b8851fccSafresh1sub _pow { 1327b8851fccSafresh1 # power of $x to $y 1328b8851fccSafresh1 # ref to array, ref to array, return ref to array 1329b8851fccSafresh1 my ($c, $cx, $cy) = @_; 1330b8851fccSafresh1 1331b8851fccSafresh1 if (@$cy == 1 && $cy->[0] == 0) { 1332b8851fccSafresh1 splice(@$cx, 1); 1333b8851fccSafresh1 $cx->[0] = 1; # y == 0 => x => 1 1334b8851fccSafresh1 return $cx; 1335b8851fccSafresh1 } 1336b8851fccSafresh1 1337b8851fccSafresh1 if ((@$cx == 1 && $cx->[0] == 1) || # x == 1 1338b8851fccSafresh1 (@$cy == 1 && $cy->[0] == 1)) # or y == 1 1339b8851fccSafresh1 { 1340b8851fccSafresh1 return $cx; 1341b8851fccSafresh1 } 1342b8851fccSafresh1 1343b8851fccSafresh1 if (@$cx == 1 && $cx->[0] == 0) { 1344b8851fccSafresh1 splice (@$cx, 1); 1345b8851fccSafresh1 $cx->[0] = 0; # 0 ** y => 0 (if not y <= 0) 1346b8851fccSafresh1 return $cx; 1347b8851fccSafresh1 } 1348b8851fccSafresh1 13499f11ffb7Safresh1 my $pow2 = $c->_one(); 1350b8851fccSafresh1 13519f11ffb7Safresh1 my $y_bin = $c->_as_bin($cy); 1352b8851fccSafresh1 $y_bin =~ s/^0b//; 1353b8851fccSafresh1 my $len = length($y_bin); 1354b8851fccSafresh1 while (--$len > 0) { 13559f11ffb7Safresh1 $c->_mul($pow2, $cx) if substr($y_bin, $len, 1) eq '1'; # is odd? 13569f11ffb7Safresh1 $c->_mul($cx, $cx); 1357b8851fccSafresh1 } 1358b8851fccSafresh1 13599f11ffb7Safresh1 $c->_mul($cx, $pow2); 1360b8851fccSafresh1 $cx; 1361b8851fccSafresh1} 1362b8851fccSafresh1 1363b8851fccSafresh1sub _nok { 1364b8851fccSafresh1 # Return binomial coefficient (n over k). 1365b8851fccSafresh1 # Given refs to arrays, return ref to array. 1366b8851fccSafresh1 # First input argument is modified. 1367b8851fccSafresh1 1368b8851fccSafresh1 my ($c, $n, $k) = @_; 1369b8851fccSafresh1 1370b8851fccSafresh1 # If k > n/2, or, equivalently, 2*k > n, compute nok(n, k) as 1371b8851fccSafresh1 # nok(n, n-k), to minimize the number if iterations in the loop. 1372b8851fccSafresh1 1373b8851fccSafresh1 { 13749f11ffb7Safresh1 my $twok = $c->_mul($c->_two(), $c->_copy($k)); # 2 * k 13759f11ffb7Safresh1 if ($c->_acmp($twok, $n) > 0) { # if 2*k > n 13769f11ffb7Safresh1 $k = $c->_sub($c->_copy($n), $k); # k = n - k 1377b8851fccSafresh1 } 1378b8851fccSafresh1 } 1379b8851fccSafresh1 1380b8851fccSafresh1 # Example: 1381b8851fccSafresh1 # 1382b8851fccSafresh1 # / 7 \ 7! 1*2*3*4 * 5*6*7 5 * 6 * 7 6 7 1383b8851fccSafresh1 # | | = --------- = --------------- = --------- = 5 * - * - 1384b8851fccSafresh1 # \ 3 / (7-3)! 3! 1*2*3*4 * 1*2*3 1 * 2 * 3 2 3 1385b8851fccSafresh1 13869f11ffb7Safresh1 if ($c->_is_zero($k)) { 1387b8851fccSafresh1 @$n = 1; 13889f11ffb7Safresh1 } else { 1389b8851fccSafresh1 1390b8851fccSafresh1 # Make a copy of the original n, since we'll be modifying n in-place. 1391b8851fccSafresh1 13929f11ffb7Safresh1 my $n_orig = $c->_copy($n); 1393b8851fccSafresh1 1394b8851fccSafresh1 # n = 5, f = 6, d = 2 (cf. example above) 1395b8851fccSafresh1 13969f11ffb7Safresh1 $c->_sub($n, $k); 13979f11ffb7Safresh1 $c->_inc($n); 1398b8851fccSafresh1 13999f11ffb7Safresh1 my $f = $c->_copy($n); 14009f11ffb7Safresh1 $c->_inc($f); 1401b8851fccSafresh1 14029f11ffb7Safresh1 my $d = $c->_two(); 1403b8851fccSafresh1 1404b8851fccSafresh1 # while f <= n (the original n, that is) ... 1405b8851fccSafresh1 14069f11ffb7Safresh1 while ($c->_acmp($f, $n_orig) <= 0) { 1407b8851fccSafresh1 1408b8851fccSafresh1 # n = (n * f / d) == 5 * 6 / 2 (cf. example above) 1409b8851fccSafresh1 14109f11ffb7Safresh1 $c->_mul($n, $f); 14119f11ffb7Safresh1 $c->_div($n, $d); 1412b8851fccSafresh1 1413b8851fccSafresh1 # f = 7, d = 3 (cf. example above) 1414b8851fccSafresh1 14159f11ffb7Safresh1 $c->_inc($f); 14169f11ffb7Safresh1 $c->_inc($d); 1417b8851fccSafresh1 } 1418b8851fccSafresh1 1419b8851fccSafresh1 } 1420b8851fccSafresh1 1421b8851fccSafresh1 return $n; 1422b8851fccSafresh1} 1423b8851fccSafresh1 14249f11ffb7Safresh1sub _fac { 1425b8851fccSafresh1 # factorial of $x 1426b8851fccSafresh1 # ref to array, return ref to array 1427b8851fccSafresh1 my ($c, $cx) = @_; 1428b8851fccSafresh1 1429eac174f2Safresh1 # We cache the smallest values. Don't assume that a single element has a 1430eac174f2Safresh1 # value larger than 9 or else it won't work with a $BASE_LEN of 1. 1431eac174f2Safresh1 1432eac174f2Safresh1 if (@$cx == 1) { 1433eac174f2Safresh1 my @factorials = 1434eac174f2Safresh1 ( 1435eac174f2Safresh1 '1', 1436eac174f2Safresh1 '1', 1437eac174f2Safresh1 '2', 1438eac174f2Safresh1 '6', 1439eac174f2Safresh1 '24', 1440eac174f2Safresh1 '120', 1441eac174f2Safresh1 '720', 1442eac174f2Safresh1 '5040', 1443eac174f2Safresh1 '40320', 1444eac174f2Safresh1 '362880', 1445eac174f2Safresh1 ); 1446eac174f2Safresh1 if ($cx->[0] <= $#factorials) { 1447eac174f2Safresh1 my $tmp = $c -> _new($factorials[ $cx->[0] ]); 1448eac174f2Safresh1 @$cx = @$tmp; 1449b8851fccSafresh1 return $cx; 1450b8851fccSafresh1 } 1451eac174f2Safresh1 } 1452eac174f2Safresh1 1453eac174f2Safresh1 # The old code further below doesn't work for small values of $BASE_LEN. 1454eac174f2Safresh1 # Alas, I have not been able to (or taken the time to) decipher it, so for 1455eac174f2Safresh1 # the case when $BASE_LEN is small, we call the parent class. This code 1456eac174f2Safresh1 # works in for every value of $x and $BASE_LEN. We could use this code for 1457eac174f2Safresh1 # all cases, but it is a little slower than the code further below, so at 1458eac174f2Safresh1 # least for now we keep the code below. 1459eac174f2Safresh1 1460eac174f2Safresh1 if ($BASE_LEN <= 2) { 1461eac174f2Safresh1 my $tmp = $c -> SUPER::_fac($cx); 1462eac174f2Safresh1 @$cx = @$tmp; 1463eac174f2Safresh1 return $cx; 1464eac174f2Safresh1 } 1465eac174f2Safresh1 1466eac174f2Safresh1 # This code does not work for small values of $BASE_LEN. 1467b8851fccSafresh1 1468b8851fccSafresh1 if ((@$cx == 1) && # we do this only if $x >= 12 and $x <= 7000 14699f11ffb7Safresh1 ($cx->[0] >= 12 && $cx->[0] < 7000)) { 1470b8851fccSafresh1 1471b8851fccSafresh1 # Calculate (k-j) * (k-j+1) ... k .. (k+j-1) * (k + j) 1472b8851fccSafresh1 # See http://blogten.blogspot.com/2007/01/calculating-n.html 1473b8851fccSafresh1 # The above series can be expressed as factors: 1474b8851fccSafresh1 # k * k - (j - i) * 2 1475b8851fccSafresh1 # We cache k*k, and calculate (j * j) as the sum of the first j odd integers 1476b8851fccSafresh1 1477b8851fccSafresh1 # This will not work when N exceeds the storage of a Perl scalar, however, 14789f11ffb7Safresh1 # in this case the algorithm would be way too slow to terminate, anyway. 1479b8851fccSafresh1 1480b8851fccSafresh1 # As soon as the last element of $cx is 0, we split it up and remember 1481b8851fccSafresh1 # how many zeors we got so far. The reason is that n! will accumulate 1482b8851fccSafresh1 # zeros at the end rather fast. 1483b8851fccSafresh1 my $zero_elements = 0; 1484b8851fccSafresh1 1485b8851fccSafresh1 # If n is even, set n = n -1 14869f11ffb7Safresh1 my $k = $c->_num($cx); 14879f11ffb7Safresh1 my $even = 1; 14889f11ffb7Safresh1 if (($k & 1) == 0) { 14899f11ffb7Safresh1 $even = $k; 14909f11ffb7Safresh1 $k --; 1491b8851fccSafresh1 } 1492b8851fccSafresh1 # set k to the center point 1493b8851fccSafresh1 $k = ($k + 1) / 2; 1494b8851fccSafresh1 # print "k $k even: $even\n"; 1495b8851fccSafresh1 # now calculate k * k 1496b8851fccSafresh1 my $k2 = $k * $k; 14979f11ffb7Safresh1 my $odd = 1; 14989f11ffb7Safresh1 my $sum = 1; 1499b8851fccSafresh1 my $i = $k - 1; 1500b8851fccSafresh1 # keep reference to x 15019f11ffb7Safresh1 my $new_x = $c->_new($k * $even); 1502b8851fccSafresh1 @$cx = @$new_x; 15039f11ffb7Safresh1 if ($cx->[0] == 0) { 15049f11ffb7Safresh1 $zero_elements ++; 15059f11ffb7Safresh1 shift @$cx; 1506b8851fccSafresh1 } 15079f11ffb7Safresh1 # print STDERR "x = ", $c->_str($cx), "\n"; 1508b8851fccSafresh1 my $BASE2 = int(sqrt($BASE))-1; 1509b8851fccSafresh1 my $j = 1; 15109f11ffb7Safresh1 while ($j <= $i) { 15119f11ffb7Safresh1 my $m = ($k2 - $sum); 15129f11ffb7Safresh1 $odd += 2; 15139f11ffb7Safresh1 $sum += $odd; 15149f11ffb7Safresh1 $j++; 15159f11ffb7Safresh1 while ($j <= $i && ($m < $BASE2) && (($k2 - $sum) < $BASE2)) { 1516b8851fccSafresh1 $m *= ($k2 - $sum); 15179f11ffb7Safresh1 $odd += 2; 15189f11ffb7Safresh1 $sum += $odd; 15199f11ffb7Safresh1 $j++; 1520b8851fccSafresh1 # print STDERR "\n k2 $k2 m $m sum $sum odd $odd\n"; sleep(1); 1521b8851fccSafresh1 } 15229f11ffb7Safresh1 if ($m < $BASE) { 15239f11ffb7Safresh1 $c->_mul($cx, [$m]); 15249f11ffb7Safresh1 } else { 15259f11ffb7Safresh1 $c->_mul($cx, $c->_new($m)); 1526b8851fccSafresh1 } 15279f11ffb7Safresh1 if ($cx->[0] == 0) { 15289f11ffb7Safresh1 $zero_elements ++; 15299f11ffb7Safresh1 shift @$cx; 1530b8851fccSafresh1 } 15319f11ffb7Safresh1 # print STDERR "Calculate $k2 - $sum = $m (x = ", $c->_str($cx), ")\n"; 1532b8851fccSafresh1 } 1533b8851fccSafresh1 # multiply in the zeros again 1534b8851fccSafresh1 unshift @$cx, (0) x $zero_elements; 1535b8851fccSafresh1 return $cx; 1536b8851fccSafresh1 } 1537b8851fccSafresh1 15389f11ffb7Safresh1 # go forward until $base is exceeded limit is either $x steps (steps == 100 15399f11ffb7Safresh1 # means a result always too high) or $base. 15409f11ffb7Safresh1 my $steps = 100; 15419f11ffb7Safresh1 $steps = $cx->[0] if @$cx == 1; 15429f11ffb7Safresh1 my $r = 2; 15439f11ffb7Safresh1 my $cf = 3; 15449f11ffb7Safresh1 my $step = 2; 15459f11ffb7Safresh1 my $last = $r; 15469f11ffb7Safresh1 while ($r * $cf < $BASE && $step < $steps) { 15479f11ffb7Safresh1 $last = $r; 15489f11ffb7Safresh1 $r *= $cf++; 15499f11ffb7Safresh1 $step++; 1550b8851fccSafresh1 } 15519f11ffb7Safresh1 if ((@$cx == 1) && $step == $cx->[0]) { 1552b8851fccSafresh1 # completely done, so keep reference to $x and return 1553b8851fccSafresh1 $cx->[0] = $r; 1554b8851fccSafresh1 return $cx; 1555b8851fccSafresh1 } 1556b8851fccSafresh1 1557b8851fccSafresh1 # now we must do the left over steps 1558b8851fccSafresh1 my $n; # steps still to do 15599f11ffb7Safresh1 if (@$cx == 1) { 1560b8851fccSafresh1 $n = $cx->[0]; 15619f11ffb7Safresh1 } else { 15629f11ffb7Safresh1 $n = $c->_copy($cx); 1563b8851fccSafresh1 } 1564b8851fccSafresh1 1565b8851fccSafresh1 # Set $cx to the last result below $BASE (but keep ref to $x) 15669f11ffb7Safresh1 $cx->[0] = $last; 15679f11ffb7Safresh1 splice (@$cx, 1); 1568b8851fccSafresh1 # As soon as the last element of $cx is 0, we split it up and remember 1569b8851fccSafresh1 # how many zeors we got so far. The reason is that n! will accumulate 1570b8851fccSafresh1 # zeros at the end rather fast. 1571b8851fccSafresh1 my $zero_elements = 0; 1572b8851fccSafresh1 1573b8851fccSafresh1 # do left-over steps fit into a scalar? 15749f11ffb7Safresh1 if (ref $n eq 'ARRAY') { 1575b8851fccSafresh1 # No, so use slower inc() & cmp() 1576b8851fccSafresh1 # ($n is at least $BASE here) 1577b8851fccSafresh1 my $base_2 = int(sqrt($BASE)) - 1; 1578b8851fccSafresh1 #print STDERR "base_2: $base_2\n"; 15799f11ffb7Safresh1 while ($step < $base_2) { 15809f11ffb7Safresh1 if ($cx->[0] == 0) { 15819f11ffb7Safresh1 $zero_elements ++; 15829f11ffb7Safresh1 shift @$cx; 1583b8851fccSafresh1 } 15849f11ffb7Safresh1 my $b = $step * ($step + 1); 15859f11ffb7Safresh1 $step += 2; 15869f11ffb7Safresh1 $c->_mul($cx, [$b]); 1587b8851fccSafresh1 } 1588b8851fccSafresh1 $step = [$step]; 15899f11ffb7Safresh1 while ($c->_acmp($step, $n) <= 0) { 15909f11ffb7Safresh1 if ($cx->[0] == 0) { 15919f11ffb7Safresh1 $zero_elements ++; 15929f11ffb7Safresh1 shift @$cx; 1593b8851fccSafresh1 } 15949f11ffb7Safresh1 $c->_mul($cx, $step); 15959f11ffb7Safresh1 $c->_inc($step); 1596b8851fccSafresh1 } 15979f11ffb7Safresh1 } else { 1598b8851fccSafresh1 # Yes, so we can speed it up slightly 1599b8851fccSafresh1 1600b8851fccSafresh1 # print "# left over steps $n\n"; 1601b8851fccSafresh1 1602b8851fccSafresh1 my $base_4 = int(sqrt(sqrt($BASE))) - 2; 1603b8851fccSafresh1 #print STDERR "base_4: $base_4\n"; 1604b8851fccSafresh1 my $n4 = $n - 4; 16059f11ffb7Safresh1 while ($step < $n4 && $step < $base_4) { 16069f11ffb7Safresh1 if ($cx->[0] == 0) { 16079f11ffb7Safresh1 $zero_elements ++; 16089f11ffb7Safresh1 shift @$cx; 1609b8851fccSafresh1 } 16109f11ffb7Safresh1 my $b = $step * ($step + 1); 16119f11ffb7Safresh1 $step += 2; 16129f11ffb7Safresh1 $b *= $step * ($step + 1); 16139f11ffb7Safresh1 $step += 2; 16149f11ffb7Safresh1 $c->_mul($cx, [$b]); 1615b8851fccSafresh1 } 1616b8851fccSafresh1 my $base_2 = int(sqrt($BASE)) - 1; 1617b8851fccSafresh1 my $n2 = $n - 2; 1618b8851fccSafresh1 #print STDERR "base_2: $base_2\n"; 16199f11ffb7Safresh1 while ($step < $n2 && $step < $base_2) { 16209f11ffb7Safresh1 if ($cx->[0] == 0) { 16219f11ffb7Safresh1 $zero_elements ++; 16229f11ffb7Safresh1 shift @$cx; 1623b8851fccSafresh1 } 16249f11ffb7Safresh1 my $b = $step * ($step + 1); 16259f11ffb7Safresh1 $step += 2; 16269f11ffb7Safresh1 $c->_mul($cx, [$b]); 1627b8851fccSafresh1 } 1628b8851fccSafresh1 # do what's left over 16299f11ffb7Safresh1 while ($step <= $n) { 16309f11ffb7Safresh1 $c->_mul($cx, [$step]); 16319f11ffb7Safresh1 $step++; 16329f11ffb7Safresh1 if ($cx->[0] == 0) { 16339f11ffb7Safresh1 $zero_elements ++; 16349f11ffb7Safresh1 shift @$cx; 1635b8851fccSafresh1 } 1636b8851fccSafresh1 } 1637b8851fccSafresh1 } 1638b8851fccSafresh1 # multiply in the zeros again 1639b8851fccSafresh1 unshift @$cx, (0) x $zero_elements; 1640b8851fccSafresh1 $cx; # return result 1641b8851fccSafresh1} 1642b8851fccSafresh1 16439f11ffb7Safresh1sub _log_int { 1644b8851fccSafresh1 # calculate integer log of $x to base $base 1645b8851fccSafresh1 # ref to array, ref to array - return ref to array 1646b8851fccSafresh1 my ($c, $x, $base) = @_; 1647b8851fccSafresh1 1648b8851fccSafresh1 # X == 0 => NaN 16499f11ffb7Safresh1 return if @$x == 1 && $x->[0] == 0; 16509f11ffb7Safresh1 1651b8851fccSafresh1 # BASE 0 or 1 => NaN 16529f11ffb7Safresh1 return if @$base == 1 && $base->[0] < 2; 16539f11ffb7Safresh1 16549f11ffb7Safresh1 # X == 1 => 0 (is exact) 16559f11ffb7Safresh1 if (@$x == 1 && $x->[0] == 1) { 16569f11ffb7Safresh1 @$x = 0; 16579f11ffb7Safresh1 return $x, 1; 1658b8851fccSafresh1 } 1659b8851fccSafresh1 16609f11ffb7Safresh1 my $cmp = $c->_acmp($x, $base); 16619f11ffb7Safresh1 16629f11ffb7Safresh1 # X == BASE => 1 (is exact) 16639f11ffb7Safresh1 if ($cmp == 0) { 16649f11ffb7Safresh1 @$x = 1; 16659f11ffb7Safresh1 return $x, 1; 16669f11ffb7Safresh1 } 16679f11ffb7Safresh1 16689f11ffb7Safresh1 # 1 < X < BASE => 0 (is truncated) 16699f11ffb7Safresh1 if ($cmp < 0) { 16709f11ffb7Safresh1 @$x = 0; 16719f11ffb7Safresh1 return $x, 0; 16729f11ffb7Safresh1 } 16739f11ffb7Safresh1 16749f11ffb7Safresh1 my $x_org = $c->_copy($x); # preserve x 1675b8851fccSafresh1 1676b8851fccSafresh1 # Compute a guess for the result based on: 1677b8851fccSafresh1 # $guess = int ( length_in_base_10(X) / ( log(base) / log(10) ) ) 16789f11ffb7Safresh1 my $len = $c->_len($x_org); 1679b8851fccSafresh1 my $log = log($base->[-1]) / log(10); 1680b8851fccSafresh1 1681b8851fccSafresh1 # for each additional element in $base, we add $BASE_LEN to the result, 1682b8851fccSafresh1 # based on the observation that log($BASE, 10) is BASE_LEN and 1683b8851fccSafresh1 # log(x*y) == log(x) + log(y): 16849f11ffb7Safresh1 $log += (@$base - 1) * $BASE_LEN; 1685b8851fccSafresh1 1686b8851fccSafresh1 # calculate now a guess based on the values obtained above: 1687eac174f2Safresh1 my $res = $c->_new(int($len / $log)); 1688b8851fccSafresh1 1689eac174f2Safresh1 @$x = @$res; 16909f11ffb7Safresh1 my $trial = $c->_pow($c->_copy($base), $x); 16919f11ffb7Safresh1 my $acmp = $c->_acmp($trial, $x_org); 1692b8851fccSafresh1 16939f11ffb7Safresh1 # Did we get the exact result? 1694b8851fccSafresh1 16959f11ffb7Safresh1 return $x, 1 if $acmp == 0; 1696b8851fccSafresh1 16979f11ffb7Safresh1 # Too small? 16989f11ffb7Safresh1 16999f11ffb7Safresh1 while ($acmp < 0) { 17009f11ffb7Safresh1 $c->_mul($trial, $base); 17019f11ffb7Safresh1 $c->_inc($x); 17029f11ffb7Safresh1 $acmp = $c->_acmp($trial, $x_org); 1703b8851fccSafresh1 } 1704b8851fccSafresh1 17059f11ffb7Safresh1 # Too big? 1706b8851fccSafresh1 17079f11ffb7Safresh1 while ($acmp > 0) { 17089f11ffb7Safresh1 $c->_div($trial, $base); 17099f11ffb7Safresh1 $c->_dec($x); 17109f11ffb7Safresh1 $acmp = $c->_acmp($trial, $x_org); 1711b8851fccSafresh1 } 1712b8851fccSafresh1 17139f11ffb7Safresh1 return $x, 1 if $acmp == 0; # result is exact 17149f11ffb7Safresh1 return $x, 0; # result is too small 1715b8851fccSafresh1} 1716b8851fccSafresh1 1717*3d61058aSafresh1sub _ilog2 { 1718*3d61058aSafresh1 # calculate int(log2($x)) 1719*3d61058aSafresh1 1720*3d61058aSafresh1 # There is virtually nothing to gain from computing this any differently 1721*3d61058aSafresh1 # than _log_int(), but it is important that we don't use the method 1722*3d61058aSafresh1 # inherited from the parent, because that method is very slow for backend 1723*3d61058aSafresh1 # libraries whose internal representation uses base 10. 1724*3d61058aSafresh1 1725*3d61058aSafresh1 my ($c, $x) = @_; 1726*3d61058aSafresh1 ($x, my $is_exact) = $c -> _log_int($x, $c -> _two()); 1727*3d61058aSafresh1 return wantarray ? ($x, $is_exact) : $x; 1728*3d61058aSafresh1} 1729*3d61058aSafresh1 1730*3d61058aSafresh1sub _ilog10 { 1731*3d61058aSafresh1 # calculate int(log10($x)) 1732*3d61058aSafresh1 1733*3d61058aSafresh1 my ($c, $x) = @_; 1734*3d61058aSafresh1 1735*3d61058aSafresh1 # X == 0 => NaN 1736*3d61058aSafresh1 return if @$x == 1 && $x->[0] == 0; 1737*3d61058aSafresh1 1738*3d61058aSafresh1 # X == 1 => 0 (is exact) 1739*3d61058aSafresh1 if (@$x == 1 && $x->[0] == 1) { 1740*3d61058aSafresh1 @$x = 0; 1741*3d61058aSafresh1 return wantarray ? ($x, 1) : $x; 1742*3d61058aSafresh1 } 1743*3d61058aSafresh1 1744*3d61058aSafresh1 my $x_orig = $c -> _copy($x); 1745*3d61058aSafresh1 my $nm1 = $c -> _len($x) - 1; 1746*3d61058aSafresh1 1747*3d61058aSafresh1 my $xtmp = $c -> _new($nm1); 1748*3d61058aSafresh1 @$x = @$xtmp; 1749*3d61058aSafresh1 1750*3d61058aSafresh1 return $x unless wantarray; 1751*3d61058aSafresh1 1752*3d61058aSafresh1 # See if the original $x is an exact power of 10, in which case all but the 1753*3d61058aSafresh1 # most significan chunks are 0, and the most significant chunk is a power 1754*3d61058aSafresh1 # of 10. 1755*3d61058aSafresh1 1756*3d61058aSafresh1 my $is_pow10 = 1; 1757*3d61058aSafresh1 for my $i (0 .. $#$x_orig - 1) { 1758*3d61058aSafresh1 last unless $is_pow10 = $x_orig->[$i] == 0; 1759*3d61058aSafresh1 } 1760*3d61058aSafresh1 $is_pow10 &&= $x_orig->[-1] == 10**int(0.5 + log($x_orig->[-1]) / log(10)); 1761*3d61058aSafresh1 1762*3d61058aSafresh1 return wantarray ? ($x, 1) : $x if $is_pow10; 1763*3d61058aSafresh1 return wantarray ? ($x, 0) : $x; 1764*3d61058aSafresh1} 1765*3d61058aSafresh1 1766*3d61058aSafresh1sub _clog2 { 1767*3d61058aSafresh1 # calculate ceil(log2($x)) 1768*3d61058aSafresh1 1769*3d61058aSafresh1 my ($c, $x) = @_; 1770*3d61058aSafresh1 1771*3d61058aSafresh1 # X == 0 => NaN 1772*3d61058aSafresh1 1773*3d61058aSafresh1 return if @$x == 1 && $x->[0] == 0; 1774*3d61058aSafresh1 1775*3d61058aSafresh1 # X == 1 => 0 (is exact) 1776*3d61058aSafresh1 1777*3d61058aSafresh1 if (@$x == 1 && $x->[0] == 1) { 1778*3d61058aSafresh1 @$x = 0; 1779*3d61058aSafresh1 return wantarray ? ($x, 1) : $x; 1780*3d61058aSafresh1 } 1781*3d61058aSafresh1 1782*3d61058aSafresh1 my $base = $c -> _two(); 1783*3d61058aSafresh1 my $acmp = $c -> _acmp($x, $base); 1784*3d61058aSafresh1 1785*3d61058aSafresh1 # X == BASE => 1 (is exact) 1786*3d61058aSafresh1 1787*3d61058aSafresh1 if ($acmp == 0) { 1788*3d61058aSafresh1 @$x = 1; 1789*3d61058aSafresh1 return wantarray ? ($x, 1) : $x; 1790*3d61058aSafresh1 } 1791*3d61058aSafresh1 1792*3d61058aSafresh1 # 1 < X < BASE => 0 (is truncated) 1793*3d61058aSafresh1 1794*3d61058aSafresh1 if ($acmp < 0) { 1795*3d61058aSafresh1 @$x = 0; 1796*3d61058aSafresh1 return wantarray ? ($x, 0) : $x; 1797*3d61058aSafresh1 } 1798*3d61058aSafresh1 1799*3d61058aSafresh1 # Compute a guess for the result based on: 1800*3d61058aSafresh1 # $guess = int( length_in_base_10(X) / (log(base) / log(10)) ) 1801*3d61058aSafresh1 1802*3d61058aSafresh1 my $len = $c -> _len($x); 1803*3d61058aSafresh1 my $log = log(2) / log(10); 1804*3d61058aSafresh1 my $guess = $c -> _new(int($len / $log)); 1805*3d61058aSafresh1 my $x_orig = $c -> _copy($x); 1806*3d61058aSafresh1 @$x = @$guess; 1807*3d61058aSafresh1 1808*3d61058aSafresh1 my $trial = $c -> _pow($c -> _copy($base), $x); 1809*3d61058aSafresh1 $acmp = $c -> _acmp($trial, $x_orig); 1810*3d61058aSafresh1 1811*3d61058aSafresh1 # Too big? 1812*3d61058aSafresh1 1813*3d61058aSafresh1 while ($acmp > 0) { 1814*3d61058aSafresh1 $c -> _div($trial, $base); 1815*3d61058aSafresh1 $c -> _dec($x); 1816*3d61058aSafresh1 $acmp = $c -> _acmp($trial, $x_orig); 1817*3d61058aSafresh1 } 1818*3d61058aSafresh1 1819*3d61058aSafresh1 # Too small? 1820*3d61058aSafresh1 1821*3d61058aSafresh1 while ($acmp < 0) { 1822*3d61058aSafresh1 $c -> _mul($trial, $base); 1823*3d61058aSafresh1 $c -> _inc($x); 1824*3d61058aSafresh1 $acmp = $c -> _acmp($trial, $x_orig); 1825*3d61058aSafresh1 } 1826*3d61058aSafresh1 1827*3d61058aSafresh1 return wantarray ? ($x, 1) : $x if $acmp == 0; # result is exact 1828*3d61058aSafresh1 return wantarray ? ($x, 0) : $x; # result is too small 1829*3d61058aSafresh1} 1830*3d61058aSafresh1 1831*3d61058aSafresh1sub _clog10 { 1832*3d61058aSafresh1 # calculate ceil(log2($x)) 1833*3d61058aSafresh1 my ($c, $x) = @_; 1834*3d61058aSafresh1 1835*3d61058aSafresh1 # X == 0 => NaN 1836*3d61058aSafresh1 return if @$x == 1 && $x->[0] == 0; 1837*3d61058aSafresh1 1838*3d61058aSafresh1 # X == 1 => 0 (is exact) 1839*3d61058aSafresh1 if (@$x == 1 && $x->[0] == 1) { 1840*3d61058aSafresh1 @$x = 0; 1841*3d61058aSafresh1 return wantarray ? ($x, 1) : $x; 1842*3d61058aSafresh1 } 1843*3d61058aSafresh1 1844*3d61058aSafresh1 # Get the number of base 10 digits. $n is the desired output, except when 1845*3d61058aSafresh1 # $x is an exact power of 10, in which case $n is 1 too big. 1846*3d61058aSafresh1 1847*3d61058aSafresh1 my $n = $c -> _len($x); 1848*3d61058aSafresh1 1849*3d61058aSafresh1 # See if $x is an exact power of 10, in which case all but the most 1850*3d61058aSafresh1 # significan chunks are 0, and the most significant chunk is a power of 10. 1851*3d61058aSafresh1 1852*3d61058aSafresh1 my $is_pow10 = 1; 1853*3d61058aSafresh1 for my $i (0 .. $#$x - 1) { 1854*3d61058aSafresh1 last unless $is_pow10 = $x->[$i] == 0; 1855*3d61058aSafresh1 } 1856*3d61058aSafresh1 $is_pow10 &&= $x->[-1] == 10**int(0.5 + log($x->[-1]) / log(10)); 1857*3d61058aSafresh1 1858*3d61058aSafresh1 $n-- if $is_pow10; 1859*3d61058aSafresh1 1860*3d61058aSafresh1 my $xtmp = $c ->_new($n); 1861*3d61058aSafresh1 @$x = @$xtmp; 1862*3d61058aSafresh1 1863*3d61058aSafresh1 return wantarray ? ($x, 1) : $x if $is_pow10; # result is exact 1864*3d61058aSafresh1 return wantarray ? ($x, 0) : $x; # result is too small 1865*3d61058aSafresh1} 1866*3d61058aSafresh1 1867b8851fccSafresh1# for debugging: 1868b8851fccSafresh1use constant DEBUG => 0; 1869b8851fccSafresh1my $steps = 0; 1870b8851fccSafresh1sub steps { $steps }; 1871b8851fccSafresh1 18729f11ffb7Safresh1sub _sqrt { 1873eac174f2Safresh1 # square-root of $x in-place 1874eac174f2Safresh1 1875b8851fccSafresh1 my ($c, $x) = @_; 1876b8851fccSafresh1 18779f11ffb7Safresh1 if (@$x == 1) { 1878b8851fccSafresh1 # fits into one Perl scalar, so result can be computed directly 1879b8851fccSafresh1 $x->[0] = int(sqrt($x->[0])); 1880b8851fccSafresh1 return $x; 1881b8851fccSafresh1 } 1882b8851fccSafresh1 1883eac174f2Safresh1 # Create an initial guess for the square root. 1884eac174f2Safresh1 1885eac174f2Safresh1 my $s; 1886eac174f2Safresh1 if (@$x % 2) { 1887eac174f2Safresh1 $s = [ (0) x ((@$x - 1) / 2), int(sqrt($x->[-1])) ]; 1888eac174f2Safresh1 } else { 1889eac174f2Safresh1 $s = [ (0) x ((@$x - 2) / 2), int(sqrt($x->[-2] + $x->[-1] * $BASE)) ]; 1890b8851fccSafresh1 } 1891b8851fccSafresh1 1892eac174f2Safresh1 # Newton's method for the square root of y: 1893eac174f2Safresh1 # 1894eac174f2Safresh1 # x(n) * x(n) - y 1895eac174f2Safresh1 # x(n+1) = x(n) - ----------------- 1896eac174f2Safresh1 # 2 * x(n) 1897b8851fccSafresh1 1898eac174f2Safresh1 my $cmp; 1899eac174f2Safresh1 while (1) { 1900eac174f2Safresh1 my $sq = $c -> _mul($c -> _copy($s), $s); 1901eac174f2Safresh1 $cmp = $c -> _acmp($sq, $x); 1902b8851fccSafresh1 1903eac174f2Safresh1 # If x(n)*x(n) > y, compute 1904eac174f2Safresh1 # 1905eac174f2Safresh1 # x(n) * x(n) - y 1906eac174f2Safresh1 # x(n+1) = x(n) - ----------------- 1907eac174f2Safresh1 # 2 * x(n) 1908b8851fccSafresh1 1909eac174f2Safresh1 if ($cmp > 0) { 1910eac174f2Safresh1 my $num = $c -> _sub($c -> _copy($sq), $x); 1911eac174f2Safresh1 my $den = $c -> _mul($c -> _two(), $s); 1912eac174f2Safresh1 my $delta = $c -> _div($num, $den); 1913eac174f2Safresh1 last if $c -> _is_zero($delta); 1914eac174f2Safresh1 $s = $c -> _sub($s, $delta); 1915b8851fccSafresh1 } 1916eac174f2Safresh1 1917eac174f2Safresh1 # If x(n)*x(n) < y, compute 1918eac174f2Safresh1 # 1919eac174f2Safresh1 # y - x(n) * x(n) 1920eac174f2Safresh1 # x(n+1) = x(n) + ----------------- 1921eac174f2Safresh1 # 2 * x(n) 1922eac174f2Safresh1 1923eac174f2Safresh1 elsif ($cmp < 0) { 1924eac174f2Safresh1 my $num = $c -> _sub($c -> _copy($x), $sq); 1925eac174f2Safresh1 my $den = $c -> _mul($c -> _two(), $s); 1926eac174f2Safresh1 my $delta = $c -> _div($num, $den); 1927eac174f2Safresh1 last if $c -> _is_zero($delta); 1928eac174f2Safresh1 $s = $c -> _add($s, $delta); 1929eac174f2Safresh1 } 1930eac174f2Safresh1 1931eac174f2Safresh1 # If x(n)*x(n) = y, we have the exact result. 1932eac174f2Safresh1 1933eac174f2Safresh1 else { 1934eac174f2Safresh1 last; 1935eac174f2Safresh1 } 1936eac174f2Safresh1 } 1937eac174f2Safresh1 1938eac174f2Safresh1 $s = $c -> _dec($s) if $cmp > 0; # never overshoot 1939eac174f2Safresh1 @$x = @$s; 1940eac174f2Safresh1 return $x; 1941b8851fccSafresh1} 1942b8851fccSafresh1 19439f11ffb7Safresh1sub _root { 19449f11ffb7Safresh1 # Take n'th root of $x in place. 19459f11ffb7Safresh1 1946b8851fccSafresh1 my ($c, $x, $n) = @_; 1947b8851fccSafresh1 19489f11ffb7Safresh1 # Small numbers. 19499f11ffb7Safresh1 1950eac174f2Safresh1 if (@$x == 1) { 1951eac174f2Safresh1 return $x if $x -> [0] == 0 || $x -> [0] == 1; 1952eac174f2Safresh1 1953eac174f2Safresh1 if (@$n == 1) { 1954eac174f2Safresh1 # Result can be computed directly. Adjust initial result for 1955eac174f2Safresh1 # numerical errors, e.g., int(1000**(1/3)) is 2, not 3. 19569f11ffb7Safresh1 my $y = int($x->[0] ** (1 / $n->[0])); 19579f11ffb7Safresh1 my $yp1 = $y + 1; 19589f11ffb7Safresh1 $y = $yp1 if $yp1 ** $n->[0] == $x->[0]; 19599f11ffb7Safresh1 $x->[0] = $y; 1960b8851fccSafresh1 return $x; 1961b8851fccSafresh1 } 1962eac174f2Safresh1 } 1963b8851fccSafresh1 19649f11ffb7Safresh1 # If x <= n, the result is always (truncated to) 1. 1965b8851fccSafresh1 19669f11ffb7Safresh1 if ((@$x > 1 || $x -> [0] > 0) && # if x is non-zero ... 19679f11ffb7Safresh1 $c -> _acmp($x, $n) <= 0) # ... and x <= n 1968b8851fccSafresh1 { 1969eac174f2Safresh1 my $one = $c -> _one(); 19709f11ffb7Safresh1 @$x = @$one; 19719f11ffb7Safresh1 return $x; 19729f11ffb7Safresh1 } 19739f11ffb7Safresh1 19749f11ffb7Safresh1 # If $n is a power of two, take sqrt($x) repeatedly, e.g., root($x, 4) = 19759f11ffb7Safresh1 # sqrt(sqrt($x)), root($x, 8) = sqrt(sqrt(sqrt($x))). 19769f11ffb7Safresh1 19779f11ffb7Safresh1 my $b = $c -> _as_bin($n); 19789f11ffb7Safresh1 if ($b =~ /0b1(0+)$/) { 19799f11ffb7Safresh1 my $count = length($1); # 0b100 => len('00') => 2 1980b8851fccSafresh1 my $cnt = $count; # counter for loop 19819f11ffb7Safresh1 unshift @$x, 0; # add one element, together with one 1982b8851fccSafresh1 # more below in the loop this makes 2 19839f11ffb7Safresh1 while ($cnt-- > 0) { 19849f11ffb7Safresh1 # 'Inflate' $x by adding one element, basically computing 19859f11ffb7Safresh1 # $x * $BASE * $BASE. This gives us more $BASE_LEN digits for 19869f11ffb7Safresh1 # result since len(sqrt($X)) approx == len($x) / 2. 19879f11ffb7Safresh1 unshift @$x, 0; 19889f11ffb7Safresh1 # Calculate sqrt($x), $x is now one element to big, again. In the 19899f11ffb7Safresh1 # next round we make that two, again. 19909f11ffb7Safresh1 $c -> _sqrt($x); 1991b8851fccSafresh1 } 1992b8851fccSafresh1 19939f11ffb7Safresh1 # $x is now one element too big, so truncate result by removing it. 19949f11ffb7Safresh1 shift @$x; 19959f11ffb7Safresh1 1996b8851fccSafresh1 return $x; 1997b8851fccSafresh1 } 1998b8851fccSafresh1 19999f11ffb7Safresh1 my $DEBUG = 0; 2000b8851fccSafresh1 20019f11ffb7Safresh1 # Now the general case. This works by finding an initial guess. If this 20029f11ffb7Safresh1 # guess is incorrect, a relatively small delta is chosen. This delta is 20039f11ffb7Safresh1 # used to find a lower and upper limit for the correct value. The delta is 20049f11ffb7Safresh1 # doubled in each iteration. When a lower and upper limit is found, 20059f11ffb7Safresh1 # bisection is applied to narrow down the region until we have the correct 20069f11ffb7Safresh1 # value. 20079f11ffb7Safresh1 20089f11ffb7Safresh1 # Split x into mantissa and exponent in base 10, so that 20099f11ffb7Safresh1 # 20109f11ffb7Safresh1 # x = xm * 10^xe, where 0 < xm < 1 and xe is an integer 20119f11ffb7Safresh1 20129f11ffb7Safresh1 my $x_str = $c -> _str($x); 20139f11ffb7Safresh1 my $xm = "." . $x_str; 20149f11ffb7Safresh1 my $xe = length($x_str); 20159f11ffb7Safresh1 20169f11ffb7Safresh1 # From this we compute the base 10 logarithm of x 20179f11ffb7Safresh1 # 20189f11ffb7Safresh1 # log_10(x) = log_10(xm) + log_10(xe^10) 20199f11ffb7Safresh1 # = log(xm)/log(10) + xe 20209f11ffb7Safresh1 # 20219f11ffb7Safresh1 # and then the base 10 logarithm of y, where y = x^(1/n) 20229f11ffb7Safresh1 # 20239f11ffb7Safresh1 # log_10(y) = log_10(x)/n 20249f11ffb7Safresh1 20259f11ffb7Safresh1 my $log10x = log($xm) / log(10) + $xe; 20269f11ffb7Safresh1 my $log10y = $log10x / $c -> _num($n); 20279f11ffb7Safresh1 20289f11ffb7Safresh1 # And from this we compute ym and ye, the mantissa and exponent (in 20299f11ffb7Safresh1 # base 10) of y, where 1 < ym <= 10 and ye is an integer. 20309f11ffb7Safresh1 20319f11ffb7Safresh1 my $ye = int $log10y; 20329f11ffb7Safresh1 my $ym = 10 ** ($log10y - $ye); 20339f11ffb7Safresh1 20349f11ffb7Safresh1 # Finally, we scale the mantissa and exponent to incraese the integer 20359f11ffb7Safresh1 # part of ym, before building the string representing our guess of y. 20369f11ffb7Safresh1 20379f11ffb7Safresh1 if ($DEBUG) { 20389f11ffb7Safresh1 print "\n"; 20399f11ffb7Safresh1 print "xm = $xm\n"; 20409f11ffb7Safresh1 print "xe = $xe\n"; 20419f11ffb7Safresh1 print "log10x = $log10x\n"; 20429f11ffb7Safresh1 print "log10y = $log10y\n"; 20439f11ffb7Safresh1 print "ym = $ym\n"; 20449f11ffb7Safresh1 print "ye = $ye\n"; 20459f11ffb7Safresh1 print "\n"; 2046b8851fccSafresh1 } 2047b8851fccSafresh1 20489f11ffb7Safresh1 my $d = $ye < 15 ? $ye : 15; 20499f11ffb7Safresh1 $ym *= 10 ** $d; 20509f11ffb7Safresh1 $ye -= $d; 20519f11ffb7Safresh1 20529f11ffb7Safresh1 my $y_str = sprintf('%.0f', $ym) . "0" x $ye; 20539f11ffb7Safresh1 my $y = $c -> _new($y_str); 20549f11ffb7Safresh1 20559f11ffb7Safresh1 if ($DEBUG) { 20569f11ffb7Safresh1 print "ym = $ym\n"; 20579f11ffb7Safresh1 print "ye = $ye\n"; 20589f11ffb7Safresh1 print "\n"; 20599f11ffb7Safresh1 print "y_str = $y_str (initial guess)\n"; 20609f11ffb7Safresh1 print "\n"; 2061b8851fccSafresh1 } 2062b8851fccSafresh1 20639f11ffb7Safresh1 # See if our guess y is correct. 2064b8851fccSafresh1 20659f11ffb7Safresh1 my $trial = $c -> _pow($c -> _copy($y), $n); 20669f11ffb7Safresh1 my $acmp = $c -> _acmp($trial, $x); 20679f11ffb7Safresh1 20689f11ffb7Safresh1 if ($acmp == 0) { 20699f11ffb7Safresh1 @$x = @$y; 2070b8851fccSafresh1 return $x; 2071b8851fccSafresh1 } 20729f11ffb7Safresh1 20739f11ffb7Safresh1 # Find a lower and upper limit for the correct value of y. Start off with a 20749f11ffb7Safresh1 # delta value that is approximately the size of the accuracy of the guess. 20759f11ffb7Safresh1 20769f11ffb7Safresh1 my $lower; 20779f11ffb7Safresh1 my $upper; 20789f11ffb7Safresh1 20799f11ffb7Safresh1 my $delta = $c -> _new("1" . ("0" x $ye)); 20809f11ffb7Safresh1 my $two = $c -> _two(); 20819f11ffb7Safresh1 20829f11ffb7Safresh1 if ($acmp < 0) { 20839f11ffb7Safresh1 $lower = $y; 20849f11ffb7Safresh1 while ($acmp < 0) { 20859f11ffb7Safresh1 $upper = $c -> _add($c -> _copy($lower), $delta); 20869f11ffb7Safresh1 20879f11ffb7Safresh1 if ($DEBUG) { 20889f11ffb7Safresh1 print "lower = $lower\n"; 20899f11ffb7Safresh1 print "upper = $upper\n"; 20909f11ffb7Safresh1 print "delta = $delta\n"; 20919f11ffb7Safresh1 print "\n"; 20929f11ffb7Safresh1 } 20939f11ffb7Safresh1 $acmp = $c -> _acmp($c -> _pow($c -> _copy($upper), $n), $x); 20949f11ffb7Safresh1 if ($acmp == 0) { 20959f11ffb7Safresh1 @$x = @$upper; 20969f11ffb7Safresh1 return $x; 20979f11ffb7Safresh1 } 20989f11ffb7Safresh1 $delta = $c -> _mul($delta, $two); 20999f11ffb7Safresh1 } 21009f11ffb7Safresh1 } 21019f11ffb7Safresh1 21029f11ffb7Safresh1 elsif ($acmp > 0) { 21039f11ffb7Safresh1 $upper = $y; 21049f11ffb7Safresh1 while ($acmp > 0) { 21059f11ffb7Safresh1 if ($c -> _acmp($upper, $delta) <= 0) { 21069f11ffb7Safresh1 $lower = $c -> _zero(); 21079f11ffb7Safresh1 last; 21089f11ffb7Safresh1 } 21099f11ffb7Safresh1 $lower = $c -> _sub($c -> _copy($upper), $delta); 21109f11ffb7Safresh1 21119f11ffb7Safresh1 if ($DEBUG) { 21129f11ffb7Safresh1 print "lower = $lower\n"; 21139f11ffb7Safresh1 print "upper = $upper\n"; 21149f11ffb7Safresh1 print "delta = $delta\n"; 21159f11ffb7Safresh1 print "\n"; 21169f11ffb7Safresh1 } 21179f11ffb7Safresh1 $acmp = $c -> _acmp($c -> _pow($c -> _copy($lower), $n), $x); 21189f11ffb7Safresh1 if ($acmp == 0) { 21199f11ffb7Safresh1 @$x = @$lower; 21209f11ffb7Safresh1 return $x; 21219f11ffb7Safresh1 } 21229f11ffb7Safresh1 $delta = $c -> _mul($delta, $two); 21239f11ffb7Safresh1 } 21249f11ffb7Safresh1 } 21259f11ffb7Safresh1 21269f11ffb7Safresh1 # Use bisection to narrow down the interval. 21279f11ffb7Safresh1 21289f11ffb7Safresh1 my $one = $c -> _one(); 21299f11ffb7Safresh1 { 21309f11ffb7Safresh1 21319f11ffb7Safresh1 $delta = $c -> _sub($c -> _copy($upper), $lower); 21329f11ffb7Safresh1 if ($c -> _acmp($delta, $one) <= 0) { 21339f11ffb7Safresh1 @$x = @$lower; 21349f11ffb7Safresh1 return $x; 21359f11ffb7Safresh1 } 21369f11ffb7Safresh1 21379f11ffb7Safresh1 if ($DEBUG) { 21389f11ffb7Safresh1 print "lower = $lower\n"; 21399f11ffb7Safresh1 print "upper = $upper\n"; 21409f11ffb7Safresh1 print "delta = $delta\n"; 21419f11ffb7Safresh1 print "\n"; 21429f11ffb7Safresh1 } 21439f11ffb7Safresh1 21449f11ffb7Safresh1 $delta = $c -> _div($delta, $two); 21459f11ffb7Safresh1 my $middle = $c -> _add($c -> _copy($lower), $delta); 21469f11ffb7Safresh1 21479f11ffb7Safresh1 $acmp = $c -> _acmp($c -> _pow($c -> _copy($middle), $n), $x); 21489f11ffb7Safresh1 if ($acmp < 0) { 21499f11ffb7Safresh1 $lower = $middle; 21509f11ffb7Safresh1 } elsif ($acmp > 0) { 21519f11ffb7Safresh1 $upper = $middle; 21529f11ffb7Safresh1 } else { 21539f11ffb7Safresh1 @$x = @$middle; 21549f11ffb7Safresh1 return $x; 21559f11ffb7Safresh1 } 21569f11ffb7Safresh1 21579f11ffb7Safresh1 redo; 21589f11ffb7Safresh1 } 21599f11ffb7Safresh1 2160b8851fccSafresh1 $x; 2161b8851fccSafresh1} 2162b8851fccSafresh1 2163b8851fccSafresh1############################################################################## 2164b8851fccSafresh1# binary stuff 2165b8851fccSafresh1 21669f11ffb7Safresh1sub _and { 2167b8851fccSafresh1 my ($c, $x, $y) = @_; 2168b8851fccSafresh1 2169b8851fccSafresh1 # the shortcut makes equal, large numbers _really_ fast, and makes only a 2170b8851fccSafresh1 # very small performance drop for small numbers (e.g. something with less 2171b8851fccSafresh1 # than 32 bit) Since we optimize for large numbers, this is enabled. 21729f11ffb7Safresh1 return $x if $c->_acmp($x, $y) == 0; # shortcut 2173b8851fccSafresh1 21749f11ffb7Safresh1 my $m = $c->_one(); 21759f11ffb7Safresh1 my ($xr, $yr); 2176b8851fccSafresh1 my $mask = $AND_MASK; 2177b8851fccSafresh1 21789f11ffb7Safresh1 my $x1 = $c->_copy($x); 21799f11ffb7Safresh1 my $y1 = $c->_copy($y); 21809f11ffb7Safresh1 my $z = $c->_zero(); 21819f11ffb7Safresh1 2182b8851fccSafresh1 use integer; 21839f11ffb7Safresh1 until ($c->_is_zero($x1) || $c->_is_zero($y1)) { 21849f11ffb7Safresh1 ($x1, $xr) = $c->_div($x1, $mask); 21859f11ffb7Safresh1 ($y1, $yr) = $c->_div($y1, $mask); 2186b8851fccSafresh1 21879f11ffb7Safresh1 $c->_add($z, $c->_mul([ 0 + $xr->[0] & 0 + $yr->[0] ], $m)); 21889f11ffb7Safresh1 $c->_mul($m, $mask); 2189b8851fccSafresh1 } 2190b8851fccSafresh1 21919f11ffb7Safresh1 @$x = @$z; 21929f11ffb7Safresh1 return $x; 21939f11ffb7Safresh1} 21949f11ffb7Safresh1 21959f11ffb7Safresh1sub _xor { 2196b8851fccSafresh1 my ($c, $x, $y) = @_; 2197b8851fccSafresh1 21989f11ffb7Safresh1 return $c->_zero() if $c->_acmp($x, $y) == 0; # shortcut (see -and) 2199b8851fccSafresh1 22009f11ffb7Safresh1 my $m = $c->_one(); 22019f11ffb7Safresh1 my ($xr, $yr); 2202b8851fccSafresh1 my $mask = $XOR_MASK; 2203b8851fccSafresh1 22049f11ffb7Safresh1 my $x1 = $c->_copy($x); 22059f11ffb7Safresh1 my $y1 = $c->_copy($y); # make copy 22069f11ffb7Safresh1 my $z = $c->_zero(); 22079f11ffb7Safresh1 2208b8851fccSafresh1 use integer; 22099f11ffb7Safresh1 until ($c->_is_zero($x1) || $c->_is_zero($y1)) { 22109f11ffb7Safresh1 ($x1, $xr) = $c->_div($x1, $mask); 22119f11ffb7Safresh1 ($y1, $yr) = $c->_div($y1, $mask); 2212b8851fccSafresh1 # make ints() from $xr, $yr (see _and()) 2213b8851fccSafresh1 #$b = 1; $xrr = 0; foreach (@$xr) { $xrr += $_ * $b; $b *= $BASE; } 2214b8851fccSafresh1 #$b = 1; $yrr = 0; foreach (@$yr) { $yrr += $_ * $b; $b *= $BASE; } 22159f11ffb7Safresh1 #$c->_add($x, $c->_mul($c->_new($xrr ^ $yrr)), $m) ); 2216b8851fccSafresh1 22179f11ffb7Safresh1 $c->_add($z, $c->_mul([ 0 + $xr->[0] ^ 0 + $yr->[0] ], $m)); 22189f11ffb7Safresh1 $c->_mul($m, $mask); 2219b8851fccSafresh1 } 2220b8851fccSafresh1 # the loop stops when the shorter of the two numbers is exhausted 2221b8851fccSafresh1 # the remainder of the longer one will survive bit-by-bit, so we simple 2222b8851fccSafresh1 # multiply-add it in 22239f11ffb7Safresh1 $c->_add($z, $c->_mul($x1, $m) ) if !$c->_is_zero($x1); 22249f11ffb7Safresh1 $c->_add($z, $c->_mul($y1, $m) ) if !$c->_is_zero($y1); 2225b8851fccSafresh1 22269f11ffb7Safresh1 @$x = @$z; 22279f11ffb7Safresh1 return $x; 2228b8851fccSafresh1} 2229b8851fccSafresh1 22309f11ffb7Safresh1sub _or { 2231b8851fccSafresh1 my ($c, $x, $y) = @_; 2232b8851fccSafresh1 22339f11ffb7Safresh1 return $x if $c->_acmp($x, $y) == 0; # shortcut (see _and) 2234b8851fccSafresh1 22359f11ffb7Safresh1 my $m = $c->_one(); 22369f11ffb7Safresh1 my ($xr, $yr); 2237b8851fccSafresh1 my $mask = $OR_MASK; 2238b8851fccSafresh1 22399f11ffb7Safresh1 my $x1 = $c->_copy($x); 22409f11ffb7Safresh1 my $y1 = $c->_copy($y); # make copy 22419f11ffb7Safresh1 my $z = $c->_zero(); 22429f11ffb7Safresh1 2243b8851fccSafresh1 use integer; 22449f11ffb7Safresh1 until ($c->_is_zero($x1) || $c->_is_zero($y1)) { 22459f11ffb7Safresh1 ($x1, $xr) = $c->_div($x1, $mask); 22469f11ffb7Safresh1 ($y1, $yr) = $c->_div($y1, $mask); 2247b8851fccSafresh1 # make ints() from $xr, $yr (see _and()) 2248b8851fccSafresh1 # $b = 1; $xrr = 0; foreach (@$xr) { $xrr += $_ * $b; $b *= $BASE; } 2249b8851fccSafresh1 # $b = 1; $yrr = 0; foreach (@$yr) { $yrr += $_ * $b; $b *= $BASE; } 22509f11ffb7Safresh1 # $c->_add($x, $c->_mul(_new( $c, ($xrr | $yrr) ), $m) ); 22519f11ffb7Safresh1 $c->_add($z, $c->_mul([ 0 + $xr->[0] | 0 + $yr->[0] ], $m)); 22529f11ffb7Safresh1 $c->_mul($m, $mask); 2253b8851fccSafresh1 } 2254b8851fccSafresh1 # the loop stops when the shorter of the two numbers is exhausted 2255b8851fccSafresh1 # the remainder of the longer one will survive bit-by-bit, so we simple 2256b8851fccSafresh1 # multiply-add it in 22579f11ffb7Safresh1 $c->_add($z, $c->_mul($x1, $m) ) if !$c->_is_zero($x1); 22589f11ffb7Safresh1 $c->_add($z, $c->_mul($y1, $m) ) if !$c->_is_zero($y1); 2259b8851fccSafresh1 22609f11ffb7Safresh1 @$x = @$z; 22619f11ffb7Safresh1 return $x; 2262b8851fccSafresh1} 2263b8851fccSafresh1 22649f11ffb7Safresh1sub _as_hex { 2265b8851fccSafresh1 # convert a decimal number to hex (ref to array, return ref to string) 2266b8851fccSafresh1 my ($c, $x) = @_; 2267b8851fccSafresh1 2268eac174f2Safresh1 return "0x0" if @$x == 1 && $x->[0] == 0; 2269b8851fccSafresh1 22709f11ffb7Safresh1 my $x1 = $c->_copy($x); 2271b8851fccSafresh1 2272eac174f2Safresh1 my $x10000 = [ 0x10000 ]; 2273eac174f2Safresh1 2274b8851fccSafresh1 my $es = ''; 2275eac174f2Safresh1 my $xr; 2276eac174f2Safresh1 until (@$x1 == 1 && $x1->[0] == 0) { # _is_zero() 22779f11ffb7Safresh1 ($x1, $xr) = $c->_div($x1, $x10000); 2278eac174f2Safresh1 $es = sprintf('%04x', $xr->[0]) . $es; 2279b8851fccSafresh1 } 2280eac174f2Safresh1 #$es = reverse $es; 2281eac174f2Safresh1 $es =~ s/^0*/0x/; 2282eac174f2Safresh1 return $es; 2283b8851fccSafresh1} 2284b8851fccSafresh1 22859f11ffb7Safresh1sub _as_bin { 2286b8851fccSafresh1 # convert a decimal number to bin (ref to array, return ref to string) 2287b8851fccSafresh1 my ($c, $x) = @_; 2288b8851fccSafresh1 2289eac174f2Safresh1 return "0b0" if @$x == 1 && $x->[0] == 0; 2290eac174f2Safresh1 22919f11ffb7Safresh1 my $x1 = $c->_copy($x); 2292b8851fccSafresh1 2293eac174f2Safresh1 my $x10000 = [ 0x10000 ]; 2294eac174f2Safresh1 2295b8851fccSafresh1 my $es = ''; 2296eac174f2Safresh1 my $xr; 2297eac174f2Safresh1 2298eac174f2Safresh1 until (@$x1 == 1 && $x1->[0] == 0) { # _is_zero() 22999f11ffb7Safresh1 ($x1, $xr) = $c->_div($x1, $x10000); 2300eac174f2Safresh1 $es = sprintf('%016b', $xr->[0]) . $es; 2301b8851fccSafresh1 } 2302eac174f2Safresh1 $es =~ s/^0*/0b/; 2303eac174f2Safresh1 return $es; 2304b8851fccSafresh1} 2305b8851fccSafresh1 23069f11ffb7Safresh1sub _as_oct { 2307b8851fccSafresh1 # convert a decimal number to octal (ref to array, return ref to string) 2308b8851fccSafresh1 my ($c, $x) = @_; 2309b8851fccSafresh1 2310eac174f2Safresh1 return "00" if @$x == 1 && $x->[0] == 0; 2311b8851fccSafresh1 23129f11ffb7Safresh1 my $x1 = $c->_copy($x); 2313b8851fccSafresh1 2314eac174f2Safresh1 my $x1000 = [ 1 << 15 ]; # 15 bits = 32768 = 0100000 2315eac174f2Safresh1 2316b8851fccSafresh1 my $es = ''; 2317b8851fccSafresh1 my $xr; 2318eac174f2Safresh1 until (@$x1 == 1 && $x1->[0] == 0) { # _is_zero() 23199f11ffb7Safresh1 ($x1, $xr) = $c->_div($x1, $x1000); 2320eac174f2Safresh1 $es = sprintf("%05o", $xr->[0]) . $es; 2321b8851fccSafresh1 } 2322eac174f2Safresh1 $es =~ s/^0*/0/; # excactly one leading zero 2323eac174f2Safresh1 return $es; 2324b8851fccSafresh1} 2325b8851fccSafresh1 23269f11ffb7Safresh1sub _from_oct { 2327b8851fccSafresh1 # convert a octal number to decimal (string, return ref to array) 2328b8851fccSafresh1 my ($c, $os) = @_; 2329b8851fccSafresh1 2330eac174f2Safresh1 my $m = $c->_new(1 << 30); # 30 bits at a time (<32 bits!) 2331eac174f2Safresh1 my $d = 10; # 10 octal digits at a time 2332b8851fccSafresh1 23339f11ffb7Safresh1 my $mul = $c->_one(); 23349f11ffb7Safresh1 my $x = $c->_zero(); 2335b8851fccSafresh1 2336b8851fccSafresh1 my $len = int((length($os) - 1) / $d); # $d digit parts, w/o the '0' 23379f11ffb7Safresh1 my $val; 23389f11ffb7Safresh1 my $i = -$d; 23399f11ffb7Safresh1 while ($len >= 0) { 2340b8851fccSafresh1 $val = substr($os, $i, $d); # get oct digits 2341b8851fccSafresh1 $val = CORE::oct($val); 23429f11ffb7Safresh1 $i -= $d; 23439f11ffb7Safresh1 $len --; 2344eac174f2Safresh1 my $adder = $c -> _new($val); 23459f11ffb7Safresh1 $c->_add($x, $c->_mul($adder, $mul)) if $val != 0; 23469f11ffb7Safresh1 $c->_mul($mul, $m) if $len >= 0; # skip last mul 2347b8851fccSafresh1 } 2348b8851fccSafresh1 $x; 2349b8851fccSafresh1} 2350b8851fccSafresh1 23519f11ffb7Safresh1sub _from_hex { 2352b8851fccSafresh1 # convert a hex number to decimal (string, return ref to array) 2353b8851fccSafresh1 my ($c, $hs) = @_; 2354b8851fccSafresh1 23559f11ffb7Safresh1 my $m = $c->_new(0x10000000); # 28 bit at a time (<32 bit!) 2356eac174f2Safresh1 my $d = 7; # 7 hexadecimal digits at a time 23579f11ffb7Safresh1 my $mul = $c->_one(); 23589f11ffb7Safresh1 my $x = $c->_zero(); 2359b8851fccSafresh1 2360b8851fccSafresh1 my $len = int((length($hs) - 2) / $d); # $d digit parts, w/o the '0x' 23619f11ffb7Safresh1 my $val; 23629f11ffb7Safresh1 my $i = -$d; 23639f11ffb7Safresh1 while ($len >= 0) { 2364b8851fccSafresh1 $val = substr($hs, $i, $d); # get hex digits 2365b8851fccSafresh1 $val =~ s/^0x// if $len == 0; # for last part only because 2366b8851fccSafresh1 $val = CORE::hex($val); # hex does not like wrong chars 23679f11ffb7Safresh1 $i -= $d; 23689f11ffb7Safresh1 $len --; 2369eac174f2Safresh1 my $adder = $c->_new($val); 2370b8851fccSafresh1 # if the resulting number was to big to fit into one element, create a 2371b8851fccSafresh1 # two-element version (bug found by Mark Lakata - Thanx!) 23729f11ffb7Safresh1 if (CORE::length($val) > $BASE_LEN) { 23739f11ffb7Safresh1 $adder = $c->_new($val); 2374b8851fccSafresh1 } 23759f11ffb7Safresh1 $c->_add($x, $c->_mul($adder, $mul)) if $val != 0; 23769f11ffb7Safresh1 $c->_mul($mul, $m) if $len >= 0; # skip last mul 2377b8851fccSafresh1 } 2378b8851fccSafresh1 $x; 2379b8851fccSafresh1} 2380b8851fccSafresh1 23819f11ffb7Safresh1sub _from_bin { 2382b8851fccSafresh1 # convert a hex number to decimal (string, return ref to array) 2383b8851fccSafresh1 my ($c, $bs) = @_; 2384b8851fccSafresh1 2385b8851fccSafresh1 # instead of converting X (8) bit at a time, it is faster to "convert" the 2386b8851fccSafresh1 # number to hex, and then call _from_hex. 2387b8851fccSafresh1 2388b8851fccSafresh1 my $hs = $bs; 2389b8851fccSafresh1 $hs =~ s/^[+-]?0b//; # remove sign and 0b 2390b8851fccSafresh1 my $l = length($hs); # bits 2391b8851fccSafresh1 $hs = '0' x (8 - ($l % 8)) . $hs if ($l % 8) != 0; # padd left side w/ 0 2392b8851fccSafresh1 my $h = '0x' . unpack('H*', pack ('B*', $hs)); # repack as hex 2393b8851fccSafresh1 2394b8851fccSafresh1 $c->_from_hex($h); 2395b8851fccSafresh1} 2396b8851fccSafresh1 2397b8851fccSafresh1############################################################################## 2398b8851fccSafresh1# special modulus functions 2399b8851fccSafresh1 24009f11ffb7Safresh1sub _modinv { 2401e0680481Safresh1 2402b8851fccSafresh1 # modular multiplicative inverse 2403b8851fccSafresh1 my ($c, $x, $y) = @_; 2404b8851fccSafresh1 2405b8851fccSafresh1 # modulo zero 24069f11ffb7Safresh1 if ($c->_is_zero($y)) { 2407eac174f2Safresh1 return; 2408b8851fccSafresh1 } 2409b8851fccSafresh1 2410b8851fccSafresh1 # modulo one 24119f11ffb7Safresh1 if ($c->_is_one($y)) { 24129f11ffb7Safresh1 return $c->_zero(), '+'; 2413b8851fccSafresh1 } 2414b8851fccSafresh1 24159f11ffb7Safresh1 my $u = $c->_zero(); 24169f11ffb7Safresh1 my $v = $c->_one(); 24179f11ffb7Safresh1 my $a = $c->_copy($y); 24189f11ffb7Safresh1 my $b = $c->_copy($x); 2419b8851fccSafresh1 2420b8851fccSafresh1 # Euclid's Algorithm for bgcd(), only that we calc bgcd() ($a) and the result 2421b8851fccSafresh1 # ($u) at the same time. See comments in BigInt for why this works. 2422b8851fccSafresh1 my $q; 2423b8851fccSafresh1 my $sign = 1; 2424b8851fccSafresh1 { 24259f11ffb7Safresh1 ($a, $q, $b) = ($b, $c->_div($a, $b)); # step 1 24269f11ffb7Safresh1 last if $c->_is_zero($b); 2427b8851fccSafresh1 24289f11ffb7Safresh1 my $t = $c->_add( # step 2: 24299f11ffb7Safresh1 $c->_mul($c->_copy($v), $q), # t = v * q 2430b8851fccSafresh1 $u); # + u 2431b8851fccSafresh1 $u = $v; # u = v 2432b8851fccSafresh1 $v = $t; # v = t 2433b8851fccSafresh1 $sign = -$sign; 2434b8851fccSafresh1 redo; 2435b8851fccSafresh1 } 2436b8851fccSafresh1 2437b8851fccSafresh1 # if the gcd is not 1, then return NaN 2438eac174f2Safresh1 return unless $c->_is_one($a); 2439b8851fccSafresh1 2440b8851fccSafresh1 ($v, $sign == 1 ? '+' : '-'); 2441b8851fccSafresh1} 2442b8851fccSafresh1 24439f11ffb7Safresh1sub _modpow { 2444b8851fccSafresh1 # modulus of power ($x ** $y) % $z 2445b8851fccSafresh1 my ($c, $num, $exp, $mod) = @_; 2446b8851fccSafresh1 2447b8851fccSafresh1 # a^b (mod 1) = 0 for all a and b 24489f11ffb7Safresh1 if ($c->_is_one($mod)) { 2449b8851fccSafresh1 @$num = 0; 2450b8851fccSafresh1 return $num; 2451b8851fccSafresh1 } 2452b8851fccSafresh1 2453b8851fccSafresh1 # 0^a (mod m) = 0 if m != 0, a != 0 2454b8851fccSafresh1 # 0^0 (mod m) = 1 if m != 0 24559f11ffb7Safresh1 if ($c->_is_zero($num)) { 24569f11ffb7Safresh1 if ($c->_is_zero($exp)) { 2457b8851fccSafresh1 @$num = 1; 2458b8851fccSafresh1 } else { 2459b8851fccSafresh1 @$num = 0; 2460b8851fccSafresh1 } 2461b8851fccSafresh1 return $num; 2462b8851fccSafresh1 } 2463b8851fccSafresh1 24649f11ffb7Safresh1 # $num = $c->_mod($num, $mod); # this does not make it faster 2465b8851fccSafresh1 24669f11ffb7Safresh1 my $acc = $c->_copy($num); 24679f11ffb7Safresh1 my $t = $c->_one(); 2468b8851fccSafresh1 24699f11ffb7Safresh1 my $expbin = $c->_as_bin($exp); 24709f11ffb7Safresh1 $expbin =~ s/^0b//; 2471b8851fccSafresh1 my $len = length($expbin); 24729f11ffb7Safresh1 while (--$len >= 0) { 24739f11ffb7Safresh1 if (substr($expbin, $len, 1) eq '1') { # is_odd 24749f11ffb7Safresh1 $t = $c->_mul($t, $acc); 24759f11ffb7Safresh1 $t = $c->_mod($t, $mod); 2476b8851fccSafresh1 } 24779f11ffb7Safresh1 $acc = $c->_mul($acc, $acc); 24789f11ffb7Safresh1 $acc = $c->_mod($acc, $mod); 2479b8851fccSafresh1 } 2480b8851fccSafresh1 @$num = @$t; 2481b8851fccSafresh1 $num; 2482b8851fccSafresh1} 2483b8851fccSafresh1 2484b8851fccSafresh1sub _gcd { 2485b8851fccSafresh1 # Greatest common divisor. 2486b8851fccSafresh1 2487b8851fccSafresh1 my ($c, $x, $y) = @_; 2488b8851fccSafresh1 2489b8851fccSafresh1 # gcd(0, 0) = 0 2490b8851fccSafresh1 # gcd(0, a) = a, if a != 0 2491b8851fccSafresh1 2492b8851fccSafresh1 if (@$x == 1 && $x->[0] == 0) { 2493b8851fccSafresh1 if (@$y == 1 && $y->[0] == 0) { 2494b8851fccSafresh1 @$x = 0; 2495b8851fccSafresh1 } else { 2496b8851fccSafresh1 @$x = @$y; 2497b8851fccSafresh1 } 2498b8851fccSafresh1 return $x; 2499b8851fccSafresh1 } 2500b8851fccSafresh1 2501b8851fccSafresh1 # Until $y is zero ... 2502b8851fccSafresh1 2503b8851fccSafresh1 until (@$y == 1 && $y->[0] == 0) { 2504b8851fccSafresh1 2505b8851fccSafresh1 # Compute remainder. 2506b8851fccSafresh1 25079f11ffb7Safresh1 $c->_mod($x, $y); 2508b8851fccSafresh1 2509b8851fccSafresh1 # Swap $x and $y. 2510b8851fccSafresh1 25119f11ffb7Safresh1 my $tmp = $c->_copy($x); 2512b8851fccSafresh1 @$x = @$y; 2513b8851fccSafresh1 $y = $tmp; # no deref here; that would modify input $y 2514b8851fccSafresh1 } 2515b8851fccSafresh1 2516b8851fccSafresh1 return $x; 2517b8851fccSafresh1} 2518b8851fccSafresh1 2519b8851fccSafresh11; 2520b8851fccSafresh1 2521b8851fccSafresh1=pod 2522b8851fccSafresh1 2523b8851fccSafresh1=head1 NAME 2524b8851fccSafresh1 2525eac174f2Safresh1Math::BigInt::Calc - pure Perl module to support Math::BigInt 2526b8851fccSafresh1 2527b8851fccSafresh1=head1 SYNOPSIS 2528b8851fccSafresh1 25299f11ffb7Safresh1 # to use it with Math::BigInt 25309f11ffb7Safresh1 use Math::BigInt lib => 'Calc'; 25319f11ffb7Safresh1 25329f11ffb7Safresh1 # to use it with Math::BigFloat 25339f11ffb7Safresh1 use Math::BigFloat lib => 'Calc'; 25349f11ffb7Safresh1 25359f11ffb7Safresh1 # to use it with Math::BigRat 25369f11ffb7Safresh1 use Math::BigRat lib => 'Calc'; 2537b8851fccSafresh1 2538eac174f2Safresh1 # explicitly set base length and whether to "use integer" 2539eac174f2Safresh1 use Math::BigInt::Calc base_len => 4, use_int => 1; 2540eac174f2Safresh1 use Math::BigInt lib => 'Calc'; 2541eac174f2Safresh1 2542b8851fccSafresh1=head1 DESCRIPTION 2543b8851fccSafresh1 25449f11ffb7Safresh1Math::BigInt::Calc inherits from Math::BigInt::Lib. 25459f11ffb7Safresh1 2546eac174f2Safresh1In this library, the numbers are represented interenally in base B = 10**N, 2547eac174f2Safresh1where N is the largest possible integer that does not cause overflow in the 2548eac174f2Safresh1intermediate computations. The base B elements are stored in an array, with the 2549eac174f2Safresh1least significant element stored in array element zero. There are no leading 2550eac174f2Safresh1zero elements, except a single zero element when the number is zero. For 2551eac174f2Safresh1instance, if B = 10000, the number 1234567890 is represented internally as 2552eac174f2Safresh1[7890, 3456, 12]. 2553b8851fccSafresh1 2554eac174f2Safresh1=head1 OPTIONS 2555eac174f2Safresh1 2556eac174f2Safresh1When the module is loaded, it computes the maximum exponent, i.e., power of 10, 2557eac174f2Safresh1that can be used with and without "use integer" in the computations. The default 2558eac174f2Safresh1is to use this maximum exponent. If the combination of the 'base_len' value and 2559eac174f2Safresh1the 'use_int' value exceeds the maximum value, an error is thrown. 2560eac174f2Safresh1 2561eac174f2Safresh1=over 4 2562eac174f2Safresh1 2563eac174f2Safresh1=item base_len 2564eac174f2Safresh1 2565eac174f2Safresh1The base length can be specified explicitly with the 'base_len' option. The 2566eac174f2Safresh1value must be a positive integer. 2567eac174f2Safresh1 2568eac174f2Safresh1 use Math::BigInt::Calc base_len => 4; # use 10000 as internal base 2569eac174f2Safresh1 2570eac174f2Safresh1=item use_int 2571eac174f2Safresh1 2572eac174f2Safresh1This option is used to specify whether "use integer" should be used in the 2573eac174f2Safresh1internal computations. The value is interpreted as a boolean value, so use 0 or 2574eac174f2Safresh1"" for false and anything else for true. If the 'base_len' is not specified 2575eac174f2Safresh1together with 'use_int', the current value for the base length is used. 2576eac174f2Safresh1 2577eac174f2Safresh1 use Math::BigInt::Calc use_int => 1; # use "use integer" internally 2578eac174f2Safresh1 2579eac174f2Safresh1=back 2580eac174f2Safresh1 2581eac174f2Safresh1=head1 METHODS 2582eac174f2Safresh1 2583eac174f2Safresh1This overview constains only the methods that are specific to 2584eac174f2Safresh1C<Math::BigInt::Calc>. For the other methods, see L<Math::BigInt::Lib>. 2585eac174f2Safresh1 2586eac174f2Safresh1=over 4 2587eac174f2Safresh1 2588eac174f2Safresh1=item _base_len() 2589eac174f2Safresh1 2590eac174f2Safresh1Specify the desired base length and whether to enable "use integer" in the 2591eac174f2Safresh1computations. 2592eac174f2Safresh1 2593eac174f2Safresh1 Math::BigInt::Calc -> _base_len($base_len, $use_int); 2594eac174f2Safresh1 2595eac174f2Safresh1Note that it is better to specify the base length and whether to use integers as 2596eac174f2Safresh1options when the module is loaded, for example like this 2597eac174f2Safresh1 2598eac174f2Safresh1 use Math::BigInt::Calc base_len => 6, use_int => 1; 2599eac174f2Safresh1 2600eac174f2Safresh1=back 2601b8851fccSafresh1 2602b8851fccSafresh1=head1 SEE ALSO 2603b8851fccSafresh1 26049f11ffb7Safresh1L<Math::BigInt::Lib> for a description of the API. 26059f11ffb7Safresh1 2606eac174f2Safresh1Alternative libraries L<Math::BigInt::FastCalc>, L<Math::BigInt::GMP>, 2607eac174f2Safresh1L<Math::BigInt::Pari>, L<Math::BigInt::GMPz>, and L<Math::BigInt::BitVect>. 26089f11ffb7Safresh1 26099f11ffb7Safresh1Some of the modules that use these libraries L<Math::BigInt>, 26109f11ffb7Safresh1L<Math::BigFloat>, and L<Math::BigRat>. 2611b8851fccSafresh1 2612b8851fccSafresh1=cut 2613