📄 calc.pm
字号:
# compute number of digits # int() because add/sub sometimes leaves strings (like '00005') instead of # '5' in this place, thus causing length() to report wrong length my $cx = $_[1]; (@$cx-1)*$BASE_LEN+length(int($cx->[-1])); }sub _digit { # return the nth digit, negative values count backward # zero is rightmost, so _digit(123,0) will give 3 my ($c,$x,$n) = @_; my $len = _len('',$x); $n = $len+$n if $n < 0; # -1 last, -2 second-to-last $n = abs($n); # if negative was too big $len--; $n = $len if $n > $len; # n to big? my $elem = int($n / $BASE_LEN); # which array element my $digit = $n % $BASE_LEN; # which digit in this element $elem = '0000'.@$x[$elem]; # get element padded with 0's substr($elem,-$digit-1,1); }sub _zeros { # return amount of trailing zeros in decimal # check each array elem in _m for having 0 at end as long as elem == 0 # Upon finding a elem != 0, stop my $x = $_[1]; my $zeros = 0; my $elem; foreach my $e (@$x) { if ($e != 0) { $elem = "$e"; # preserve x $elem =~ s/.*?(0*$)/$1/; # strip anything not zero $zeros *= $BASE_LEN; # elems * 5 $zeros += length($elem); # count trailing zeros last; # early out } $zeros ++; # real else branch: 50% slower! } $zeros; }############################################################################### _is_* routinessub _is_zero { # return true if arg (BINT or num_str) is zero (array '+', '0') my $x = $_[1]; (((scalar @$x == 1) && ($x->[0] == 0))) <=> 0; }sub _is_even { # return true if arg (BINT or num_str) is even my $x = $_[1]; (!($x->[0] & 1)) <=> 0; }sub _is_odd { # return true if arg (BINT or num_str) is even my $x = $_[1]; (($x->[0] & 1)) <=> 0; }sub _is_one { # return true if arg (BINT or num_str) is one (array '+', '1') my $x = $_[1]; (scalar @$x == 1) && ($x->[0] == 1) <=> 0; }sub __strip_zeros { # internal normalization function that strips leading zeros from the array # args: ref to array my $s = shift; my $cnt = scalar @$s; # get count of parts my $i = $cnt-1; push @$s,0 if $i < 0; # div might return empty results, so fix it return $s if @$s == 1; # early out #print "strip: cnt $cnt i $i\n"; # '0', '3', '4', '0', '0', # 0 1 2 3 4 # cnt = 5, i = 4 # i = 4 # i = 3 # => fcnt = cnt - i (5-2 => 3, cnt => 5-1 = 4, throw away from 4th pos) # >= 1: skip first part (this can be zero) while ($i > 0) { last if $s->[$i] != 0; $i--; } $i++; splice @$s,$i if ($i < $cnt); # $i cant be 0 $s; } ################################################################################ check routine to test internal state of corruptionssub _check { # used by the test suite my $x = $_[1]; return "$x is not a reference" if !ref($x); # are all parts are valid? my $i = 0; my $j = scalar @$x; my ($e,$try); while ($i < $j) { $e = $x->[$i]; $e = 'undef' unless defined $e; $try = '=~ /^[\+]?[0-9]+\$/; '."($x, $e)"; last if $e !~ /^[+]?[0-9]+$/; $try = '=~ /^[\+]?[0-9]+\$/; '."($x, $e) (stringify)"; last if "$e" !~ /^[+]?[0-9]+$/; $try = '=~ /^[\+]?[0-9]+\$/; '."($x, $e) (cat-stringify)"; last if '' . "$e" !~ /^[+]?[0-9]+$/; $try = ' < 0 || >= $BASE; '."($x, $e)"; last if $e <0 || $e >= $BASE; # this test is disabled, since new/bnorm and certain ops (like early out # in add/sub) are allowed/expected to leave '00000' in some elements #$try = '=~ /^00+/; '."($x, $e)"; #last if $e =~ /^00+/; $i++; } return "Illegal part '$e' at pos $i (tested: $try)" if $i < $j; return 0; }############################################################################################################################################################### some optional routines to make BigInt fastersub _mod { # if possible, use mod shortcut my ($c,$x,$yo) = @_; # slow way since $y to big if (scalar @$yo > 1) { my ($xo,$rem) = _div($c,$x,$yo); return $rem; } my $y = $yo->[0]; # both are single element arrays if (scalar @$x == 1) { $x->[0] %= $y; return $x; } # @y is single element, but @x has more than one my $b = $BASE % $y; if ($b == 0) { # when BASE % Y == 0 then (B * BASE) % Y == 0 # (B * BASE) % $y + A % Y => A % Y # so need to consider only last element: O(1) $x->[0] %= $y; } elsif ($b == 1) { # else need to go trough all elements: O(N), but loop is a bit simplified my $r = 0; foreach (@$x) { $r = ($r + $_) % $y; # not much faster, but heh... #$r += $_ % $y; $r %= $y; } $r = 0 if $r == $y; $x->[0] = $r; } else { # else need to go trough all elements: O(N) my $r = 0; my $bm = 1; foreach (@$x) { $r = ($_ * $bm + $r) % $y; $bm = ($bm * $b) % $y; #$r += ($_ % $y) * $bm; #$bm *= $b; #$bm %= $y; #$r %= $y; } $r = 0 if $r == $y; $x->[0] = $r; } splice (@$x,1); $x; }############################################################################### shiftssub _rsft { my ($c,$x,$y,$n) = @_; if ($n != 10) { $n = _new($c,\$n); return _div($c,$x, _pow($c,$n,$y)); } # shortcut (faster) for shifting by 10) # multiples of $BASE_LEN my $dst = 0; # destination my $src = _num($c,$y); # as normal int my $xlen = (@$x-1)*$BASE_LEN+length(int($x->[-1])); # len of x in digits if ($src > $xlen) { # 12345 67890 shifted right by more than 10 digits => 0 splice (@$x,1); # leave only one element $x->[0] = 0; # set to zero return $x; } my $rem = $src % $BASE_LEN; # remainder to shift $src = int($src / $BASE_LEN); # source if ($rem == 0) { splice (@$x,0,$src); # even faster, 38.4 => 39.3 } else { my $len = scalar @$x - $src; # elems to go my $vd; my $z = '0'x $BASE_LEN; $x->[scalar @$x] = 0; # avoid || 0 test inside loop while ($dst < $len) { $vd = $z.$x->[$src]; $vd = substr($vd,-$BASE_LEN,$BASE_LEN-$rem); $src++; $vd = substr($z.$x->[$src],-$rem,$rem) . $vd; $vd = substr($vd,-$BASE_LEN,$BASE_LEN) if length($vd) > $BASE_LEN; $x->[$dst] = int($vd); $dst++; } splice (@$x,$dst) if $dst > 0; # kill left-over array elems pop @$x if $x->[-1] == 0 && @$x > 1; # kill last element if 0 } # else rem == 0 $x; }sub _lsft { my ($c,$x,$y,$n) = @_; if ($n != 10) { $n = _new($c,\$n); return _mul($c,$x, _pow($c,$n,$y)); } # shortcut (faster) for shifting by 10) since we are in base 10eX # multiples of $BASE_LEN: my $src = scalar @$x; # source my $len = _num($c,$y); # shift-len as normal int my $rem = $len % $BASE_LEN; # remainder to shift my $dst = $src + int($len/$BASE_LEN); # destination my $vd; # further speedup $x->[$src] = 0; # avoid first ||0 for speed my $z = '0' x $BASE_LEN; while ($src >= 0) { $vd = $x->[$src]; $vd = $z.$vd; $vd = substr($vd,-$BASE_LEN+$rem,$BASE_LEN-$rem); $vd .= $src > 0 ? substr($z.$x->[$src-1],-$BASE_LEN,$rem) : '0' x $rem; $vd = substr($vd,-$BASE_LEN,$BASE_LEN) if length($vd) > $BASE_LEN; $x->[$dst] = int($vd); $dst--; $src--; } # set lowest parts to 0 while ($dst >= 0) { $x->[$dst--] = 0; } # fix spurios last zero element splice @$x,-1 if $x->[-1] == 0; $x; }sub _pow { # power of $x to $y # ref to array, ref to array, return ref to array my ($c,$cx,$cy) = @_; my $pow2 = _one(); my $y_bin = ${_as_bin($c,$cy)}; $y_bin =~ s/^0b//; my $len = length($y_bin); while (--$len > 0) { _mul($c,$pow2,$cx) if substr($y_bin,$len,1) eq '1'; # is odd? _mul($c,$cx,$cx); } _mul($c,$cx,$pow2); $cx; }sub _fac { # factorial of $x # ref to array, return ref to array my ($c,$cx) = @_; if ((@$cx == 1) && ($cx->[0] <= 2)) { $cx->[0] = 1 * ($cx->[0]||1); # 0,1 => 1, 2 => 2 return $cx; } # go forward until $base is exceeded # limit is either $x or $base (x == 100 means as result too high) my $steps = 100; $steps = $cx->[0] if @$cx == 1; my $r = 2; my $cf = 3; my $step = 1; my $last = $r; while ($r < $BASE && $step < $steps) { $last = $r; $r *= $cf++; $step++; } if ((@$cx == 1) && ($step == $cx->[0])) { # completely done $cx = [$last]; return $cx; } my $n = _copy($c,$cx); $cx = [$last]; while (!(@$n == 1 && $n->[0] == $step)) { _mul($c,$cx,$n); _dec($c,$n); } $cx; }# for debugging: use constant DEBUG => 0; my $steps = 0; sub steps { $steps };sub _sqrt { # square-root of $x in place # Compute a guess of the result (rule of thumb), then improve it via # Newton's method. my ($c,$x) = @_; if (scalar @$x == 1) { # fit's into one Perl scalar, so result can be computed directly $x->[0] = int(sqrt($x->[0])); return $x; } my $y = _copy($c,$x); # hopefully _len/2 is < $BASE, the -1 is to always undershot the guess # since our guess will "grow" my $l = int((_len($c,$x)-1) / 2); my $lastelem = $x->[-1]; # for guess my $elems = scalar @$x - 1; # not enough digits, but could have more? if ((length($lastelem) <= 3) && ($elems > 1)) { # right-align with zero pad my $len = length($lastelem) & 1; print "$lastelem => " if DEBUG; $lastelem .= substr($x->[-2] . '0' x $BASE_LEN,0,$BASE_LEN); # former odd => make odd again, or former even to even again $lastelem = $lastelem / 10 if (length($lastelem) & 1) != $len; print "$lastelem\n" if DEBUG; } # construct $x (instead of _lsft($c,$x,$l,10) my $r = $l % $BASE_LEN; # 10000 00000 00000 00000 ($BASE_LEN=5) $l = int($l / $BASE_LEN); print "l = $l " if DEBUG; splice @$x,$l; # keep ref($x), but modify it # we make the first part of the guess not '1000...0' but int(sqrt($lastelem)) # that gives us: # 14400 00000 => sqrt(14400) => guess first digits to be 120 # 144000 000000 => sqrt(144000) => guess 379 print "$lastelem (elems $elems) => " if DEBUG; $lastelem = $lastelem / 10 if ($elems & 1 == 1); # odd or even? my $g = sqrt($lastelem); $g =~ s/\.//; # 2.345 => 2345 $r -= 1 if $elems & 1 == 0; # 70 => 7 # padd with zeros if result is too short $x->[$l--] = int(substr($g . '0' x $r,0,$r+1)); print "now ",$x->[-1] if DEBUG; print " would have been ", int('1' . '0' x $r),"\n" if DEBUG; # If @$x > 1, we could compute the second elem of the guess, too, to create # an even better guess. Not implemented yet. Does it improve performance? $x->[$l--] = 0 while ($l >= 0); # all other digits of guess are zero print "start x= ",${_str($c,$x)},"\n" if DEBUG; my $two = _two(); my $last = _zero(); my $lastlast = _zero(); $steps = 0 if DEBUG; while (_acmp($c,$last,$x) != 0 && _acmp($c,$lastlast,$x) != 0) { $steps++ if DEBUG; $lastlast = _copy($c,$last); $last = _copy($c,$x); _add($c,$x, _div($c,_copy($c,$y),$x)); _div($c,$x, $two ); print " x= ",${_str($c,$x)},"\n" if DEBUG; } print "\nsteps in sqrt: $steps, " if DEBUG; _dec($c,$x) if _acmp($c,$y,_mul($c,_copy($c,$x),$x)) < 0; # overshot? print " final ",$x->[-1],"\n" if DEBUG; $x; }############################################################################### binary stuffsub _and { my ($c,$x,$y) = @_; # the shortcut makes equal, large numbers _really_ fast, and makes only a # very small performance drop for small numbers (e.g. something with less # than 32 bit) Since we optimize for large numbers, this is enabled. return $x if _acmp($c,$x,$y) == 0; # shortcut my $m = _one(); my ($xr,$yr);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -