📄 calc.pm
字号:
} else { @d = @$x; } @$x = @q; my $d = \@d; __strip_zeros($x); __strip_zeros($d); return ($x,$d); } @$x = @q; __strip_zeros($x); $x; }sub _div_use_div { # ref to array, ref to array, modify first array and return remainder if # in list context my ($c,$x,$yorg) = @_; # the general div algorithmn here is about O(N*N) and thus quite slow, so # we first check for some special cases and use shortcuts to handle them. # This works, because we store the numbers in a chunked format where each # element contains 5..7 digits (depending on system). # if both numbers have only one element: if (@$x == 1 && @$yorg == 1) { # shortcut, $yorg and $x are two small numbers if (wantarray) { my $r = [ $x->[0] % $yorg->[0] ]; $x->[0] = int($x->[0] / $yorg->[0]); return ($x,$r); } else { $x->[0] = int($x->[0] / $yorg->[0]); return $x; } } # if x has more than one, but y has only one element: if (@$yorg == 1) { my $rem; $rem = _mod($c,[ @$x ],$yorg) if wantarray; # shortcut, $y is < $BASE my $j = scalar @$x; my $r = 0; my $y = $yorg->[0]; my $b; while ($j-- > 0) { $b = $r * $BASE + $x->[$j]; $x->[$j] = int($b/$y); $r = $b % $y; } pop @$x if @$x > 1 && $x->[-1] == 0; # splice up a leading zero return ($x,$rem) if wantarray; return $x; } # now x and y have more than one element # check whether y has more elements than x, if yet, the result will be 0 if (@$yorg > @$x) { my $rem; $rem = [@$x] if wantarray; # make copy splice (@$x,1); # keep ref to original array $x->[0] = 0; # set to 0 return ($x,$rem) if wantarray; # including remainder? return $x; # only x, which is [0] now } # check whether the numbers have the same number of elements, in that case # the result will fit into one element and can be computed efficiently if (@$yorg == @$x) { my $rem; # if $yorg has more digits than $x (it's leading element is longer than # the one from $x), the result will also be 0: if (length(int($yorg->[-1])) > length(int($x->[-1]))) { $rem = [@$x] if wantarray; # make copy splice (@$x,1); # keep ref to org array $x->[0] = 0; # set to 0 return ($x,$rem) if wantarray; # including remainder? return $x; } # now calculate $x / $yorg if (length(int($yorg->[-1])) == length(int($x->[-1]))) { # same length, so make full compare my $a = 0; my $j = scalar @$x - 1; # manual way (abort if unequal, good for early ne) while ($j >= 0) { last if ($a = $x->[$j] - $yorg->[$j]); $j--; } # $a contains the result of the compare between X and Y # a < 0: x < y, a == 0: x == y, a > 0: x > y if ($a <= 0) { $rem = [ 0 ]; # a = 0 => x == y => rem 0 $rem = [@$x] if $a != 0; # a < 0 => x < y => rem = x splice(@$x,1); # keep single element $x->[0] = 0; # if $a < 0 $x->[0] = 1 if $a == 0; # $x == $y return ($x,$rem) if wantarray; # including remainder? return $x; } # $x >= $y, so proceed normally } } # all other cases: my $y = [ @$yorg ]; # always make copy to preserve my ($car,$bar,$prd,$dd,$xi,$yi,@q,$v2,$v1,@d,$tmp,$q,$u2,$u1,$u0); $car = $bar = $prd = 0; if (($dd = int($BASE/($y->[-1]+1))) != 1) { for $xi (@$x) { $xi = $xi * $dd + $car; $xi -= ($car = int($xi / $BASE)) * $BASE; } push(@$x, $car); $car = 0; for $yi (@$y) { $yi = $yi * $dd + $car; $yi -= ($car = int($yi / $BASE)) * $BASE; } } else { push(@$x, 0); } # @q will accumulate the final result, $q contains the current computed # part of the final result @q = (); ($v2,$v1) = @$y[-2,-1]; $v2 = 0 unless $v2; while ($#$x > $#$y) { ($u2,$u1,$u0) = @$x[-3..-1]; $u2 = 0 unless $u2; #warn "oups v1 is 0, u0: $u0 $y->[-2] $y->[-1] l ",scalar @$y,"\n" # if $v1 == 0; $q = (($u0 == $v1) ? $MAX_VAL : int(($u0*$BASE+$u1)/$v1)); --$q while ($v2*$q > ($u0*$BASE+$u1-$q*$v1)*$BASE+$u2); if ($q) { ($car, $bar) = (0,0); for ($yi = 0, $xi = $#$x-$#$y-1; $yi <= $#$y; ++$yi,++$xi) { $prd = $q * $y->[$yi] + $car; $prd -= ($car = int($prd / $BASE)) * $BASE; $x->[$xi] += $BASE if ($bar = (($x->[$xi] -= $prd + $bar) < 0)); } if ($x->[-1] < $car + $bar) { $car = 0; --$q; for ($yi = 0, $xi = $#$x-$#$y-1; $yi <= $#$y; ++$yi,++$xi) { $x->[$xi] -= $BASE if ($car = (($x->[$xi] += $y->[$yi] + $car) >= $BASE)); } } } pop(@$x); unshift(@q, $q); } if (wantarray) { @d = (); if ($dd != 1) { $car = 0; for $xi (reverse @$x) { $prd = $car * $BASE + $xi; $car = $prd - ($tmp = int($prd / $dd)) * $dd; unshift(@d, $tmp); } } else { @d = @$x; } @$x = @q; my $d = \@d; __strip_zeros($x); __strip_zeros($d); return ($x,$d); } @$x = @q; __strip_zeros($x); $x; }############################################################################### testingsub _acmp { # internal absolute post-normalized compare (ignore signs) # ref to array, ref to array, return <0, 0, >0 # arrays must have at least one entry; this is not checked for my ($c,$cx,$cy) = @_; # shortcut for short numbers return (($cx->[0] <=> $cy->[0]) <=> 0) if scalar @$cx == scalar @$cy && scalar @$cx == 1; # fast comp based on number of array elements (aka pseudo-length) my $lxy = (scalar @$cx - scalar @$cy) # or length of first element if same number of elements (aka difference 0) || # need int() here because sometimes the last element is '00018' vs '18' (length(int($cx->[-1])) - length(int($cy->[-1]))); return -1 if $lxy < 0; # already differs, ret return 1 if $lxy > 0; # ditto # manual way (abort if unequal, good for early ne) my $a; my $j = scalar @$cx; while (--$j >= 0) { last if ($a = $cx->[$j] - $cy->[$j]); } $a <=> 0; }sub _len { # compute number of digits in base 10 # int() because add/sub sometimes leaves strings (like '00005') instead of # '5' in this place, thus causing length() to report wrong length my $cx = $_[1]; (@$cx-1)*$BASE_LEN+length(int($cx->[-1])); }sub _digit { # return the nth digit, 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 = '0' x $BASE_LEN . @$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]; return 0 if scalar @$x == 1 && $x->[0] == 0; 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 is zero (((scalar @{$_[1]} == 1) && ($_[1]->[0] == 0))) <=> 0; }sub _is_even { # return true if arg is even (!($_[1]->[0] & 1)) <=> 0; }sub _is_odd { # return true if arg is even (($_[1]->[0] & 1)) <=> 0; }sub _is_one { # return true if arg is one (scalar @{$_[1]} == 1) && ($_[1]->[0] == 1) <=> 0; }sub _is_two { # return true if arg is two (scalar @{$_[1]} == 1) && ($_[1]->[0] == 2) <=> 0; }sub _is_ten { # return true if arg is ten (scalar @{$_[1]} == 1) && ($_[1]->[0] == 10) <=> 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 for 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; 0; }###############################################################################sub _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 a single element, but @x has more than one element my $b = $BASE % $y; if ($b == 0) { # when BASE % Y == 0 then (B * BASE) % Y == 0 # (B * BASE) % $y + A % Y => A % Y # so need to consider only last element: O(1) $x->[0] %= $y; } elsif ($b == 1) { # else need to go through all elements: O(N), but loop is a bit simplified my $r = 0; foreach (@$x) { $r = ($r + $_) % $y; # not much faster, but heh... #$r += $_ % $y; $r %= $y; } $r = 0 if $r == $y; $x->[0] = $r; } else { # else need to go through all elements: 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); # keep one element of $x $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 or ($src == $xlen and ! defined $x->[1])) { # 12345 67890 shifted right by more than 10 digits => 0 splice (@$x,1); # leave only one element $x->[0] = 0; # set to zero return $x;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -