1 package Math::BigInt::CalcEmu;
5 # use warnings; # dont use warnings for older Perls
18 $CALC_EMU = Math::BigInt->config()->{'lib'};
23 my ($self,$x,$base,@r) = @_;
25 return $x->bnan() if $x->is_zero() || $base->is_zero() || $base->is_one();
27 my $acmp = $x->bacmp($base);
28 return $x->bone('+',@r) if $acmp == 0;
29 return $x->bzero(@r) if $acmp < 0 || $x->is_one();
31 # blog($x,$base) ** $base + $y = $x
33 # this trial multiplication is very fast, even for large counts (like for
34 # 2 ** 1024, since this still requires only 1024 very fast steps
35 # (multiplication of a large number by a very small number is very fast))
36 # See Calc for an even faster algorightmn
37 my $x_org = $x->copy(); # preserve orgx
38 $x->bzero(); # keep ref to $x
39 my $trial = $base->copy();
40 while ($trial->bacmp($x_org) <= 0)
42 $trial->bmul($base); $x->binc();
49 my ($self,$x,$y,@r) = @_;
51 my ($u, $u1) = ($self->bzero(), $self->bone());
52 my ($a, $b) = ($y->copy(), $x->copy());
54 # first step need always be done since $num (and thus $b) is never 0
55 # Note that the loop is aligned so that the check occurs between #2 and #1
56 # thus saving us one step #2 at the loop end. Typical loop count is 1. Even
57 # a case with 28 loops still gains about 3% with this layout.
59 ($a, $q, $b) = ($b, $a->bdiv($b)); # step #1
60 # Euclid's Algorithm (calculate GCD of ($a,$b) in $a and also calculate
61 # two values in $u and $u1, we use only $u1 afterwards)
62 my $sign = 1; # flip-flop
63 while (!$b->is_zero()) # found GCD if $b == 0
65 # the original algorithm had:
66 # ($u, $u1) = ($u1, $u->bsub($u1->copy()->bmul($q))); # step #2
67 # The following creates exact the same sequence of numbers in $u1,
68 # except for the sign ($u1 is now always positive). Since formerly
69 # the sign of $u1 was alternating between '-' and '+', the $sign
70 # flip-flop will take care of that, so that at the end of the loop
71 # we have the real sign of $u1. Keeping numbers positive gains us
72 # speed since badd() is faster than bsub() and makes it possible
73 # to have the algorithmn in Calc for even more speed.
75 ($u, $u1) = ($u1, $u->badd($u1->copy()->bmul($q))); # step #2
76 $sign = - $sign; # flip sign
78 ($a, $q, $b) = ($b, $a->bdiv($b)); # step #1 again
81 # If the gcd is not 1, then return NaN! It would be pointless to have
82 # called bgcd to check this first, because we would then be performing
83 # the same Euclidean Algorithm *twice* in case the gcd is 1.
84 return $x->bnan() unless $a->is_one();
86 $u1->bneg() if $sign != 1; # need to flip?
88 $u1->bmod($y); # calc result
89 $x->{value} = $u1->{value}; # and copy over to $x
90 $x->{sign} = $u1->{sign}; # to modify in place
96 my ($self,$num,$exp,$mod,@r) = @_;
98 # in the trivial case,
99 return $num->bzero(@r) if $mod->is_one();
100 return $num->bone('+',@r) if $num->is_zero() or $num->is_one();
102 # $num->bmod($mod); # if $x is large, make it smaller first
103 my $acc = $num->copy(); # but this is not really faster...
105 $num->bone(); # keep ref to $num
107 my $expbin = $exp->as_bin(); $expbin =~ s/^[-]?0b//; # ignore sign and prefix
108 my $len = CORE::length($expbin);
111 $num->bmul($acc)->bmod($mod) if substr($expbin,$len,1) eq '1';
112 $acc->bmul($acc)->bmod($mod);
120 my ($self,$x,@r) = @_;
122 return $x->bone('+',@r) if $x->is_zero() || $x->is_one(); # 0 or 1 => 1
126 # seems we need not to temp. clear A/P of $x since the result is the same
127 my $f = $self->new(2);
128 while ($f->bacmp($n) < 0)
130 $x->bmul($f); $f->binc();
132 $x->bmul($f,@r); # last step and also round result
137 my ($self,$x,$y,@r) = @_;
139 return $x->bone('+',@r) if $y->is_zero();
140 return $x->round(@r) if $x->is_one() || $y->is_one();
141 return $x->round(@r) if $x->is_zero(); # 0**y => 0 (if not y <= 0)
143 my $pow2 = $self->bone();
144 my $y_bin = $y->as_bin(); $y_bin =~ s/^0b//;
145 my $len = CORE::length($y_bin);
148 $pow2->bmul($x) if substr($y_bin,$len,1) eq '1'; # is odd?
152 $x->round(@r) if !exists $x->{_f} || $x->{_f} & MB_NEVER_ROUND == 0;
158 my ($self,$x,$y,$sx,$sy,@r) = @_;
160 return $x->bzero(@r) if $y->is_zero() || $x->is_zero();
162 my $sign = 0; # sign of result
163 $sign = 1 if $sx == -1 && $sy == -1;
167 if ($sx == -1) # if x is negative
169 # two's complement: inc and flip all "bits" in $bx
170 $bx = $x->binc()->as_hex(); # -1 => 0, -2 => 1, -3 => 2 etc
172 $bx =~ tr/0123456789abcdef/\x0f\x0e\x0d\x0c\x0b\x0a\x09\x08\x07\x06\x05\x04\x03\x02\x01\x00/;
176 $bx = $x->as_hex(); # get binary representation
178 $bx =~ tr/fedcba9876543210/\x0f\x0e\x0d\x0c\x0b\x0a\x09\x08\x07\x06\x05\x04\x03\x02\x01\x00/;
180 if ($sy == -1) # if y is negative
182 # two's complement: inc and flip all "bits" in $by
183 $by = $y->copy()->binc()->as_hex(); # -1 => 0, -2 => 1, -3 => 2 etc
185 $by =~ tr/0123456789abcdef/\x0f\x0e\x0d\x0c\x0b\x0a\x09\x08\x07\x06\x05\x04\x03\x02\x01\x00/;
189 $by = $y->as_hex(); # get binary representation
191 $by =~ tr/fedcba9876543210/\x0f\x0e\x0d\x0c\x0b\x0a\x09\x08\x07\x06\x05\x04\x03\x02\x01\x00/;
193 # now we have bit-strings from X and Y, reverse them for padding
197 # cut the longer string to the length of the shorter one (the result would
198 # be 0 due to AND anyway)
199 my $diff = CORE::length($bx) - CORE::length($by);
202 $bx = substr($bx,0,CORE::length($by));
206 $by = substr($by,0,CORE::length($bx));
209 # and the strings together
212 # and reverse the result again
215 # one of $x or $y was negative, so need to flip bits in the result
216 # in both cases (one or two of them negative, or both positive) we need
217 # to get the characters back.
220 $bx =~ tr/\x0f\x0e\x0d\x0c\x0b\x0a\x09\x08\x07\x06\x05\x04\x03\x02\x01\x00/0123456789abcdef/;
224 $bx =~ tr/\x0f\x0e\x0d\x0c\x0b\x0a\x09\x08\x07\x06\x05\x04\x03\x02\x01\x00/fedcba9876543210/;
228 if ($CALC_EMU->can('_from_hex'))
230 $x->{value} = $CALC_EMU->_from_hex( \$bx );
234 $r = $self->new($bx);
235 $x->{value} = $r->{value};
238 # calculate sign of result
240 #$x->{sign} = '-' if $sx == $sy && $sx == -1 && !$x->is_zero();
241 $x->{sign} = '-' if $sign == 1 && !$x->is_zero();
243 $x->bdec() if $sign == 1;
250 my ($self,$x,$y,$sx,$sy,@r) = @_;
252 return $x->round(@r) if $y->is_zero();
254 my $sign = 0; # sign of result
255 $sign = 1 if ($sx == -1) || ($sy == -1);
259 if ($sx == -1) # if x is negative
261 # two's complement: inc and flip all "bits" in $bx
262 $bx = $x->binc()->as_hex(); # -1 => 0, -2 => 1, -3 => 2 etc
264 $bx =~ tr/0123456789abcdef/\x0f\x0e\x0d\x0c\x0b\x0a\x09\x08\x07\x06\x05\x04\x03\x02\x01\x00/;
268 $bx = $x->as_hex(); # get binary representation
270 $bx =~ tr/fedcba9876543210/\x0f\x0e\x0d\x0c\x0b\x0a\x09\x08\x07\x06\x05\x04\x03\x02\x01\x00/;
272 if ($sy == -1) # if y is negative
274 # two's complement: inc and flip all "bits" in $by
275 $by = $y->copy()->binc()->as_hex(); # -1 => 0, -2 => 1, -3 => 2 etc
277 $by =~ tr/0123456789abcdef/\x0f\x0e\x0d\x0c\x0b\x0a\x09\x08\x07\x06\x05\x04\x03\x02\x01\x00/;
281 $by = $y->as_hex(); # get binary representation
283 $by =~ tr/fedcba9876543210/\x0f\x0e\x0d\x0c\x0b\x0a\x09\x08\x07\x06\x05\x04\x03\x02\x01\x00/;
285 # now we have bit-strings from X and Y, reverse them for padding
289 # padd the shorter string
290 my $xx = "\x00"; $xx = "\x0f" if $sx == -1;
291 my $yy = "\x00"; $yy = "\x0f" if $sy == -1;
292 my $diff = CORE::length($bx) - CORE::length($by);
299 $bx .= $xx x abs($diff);
302 # or the strings together
305 # and reverse the result again
308 # one of $x or $y was negative, so need to flip bits in the result
309 # in both cases (one or two of them negative, or both positive) we need
310 # to get the characters back.
313 $bx =~ tr/\x0f\x0e\x0d\x0c\x0b\x0a\x09\x08\x07\x06\x05\x04\x03\x02\x01\x00/0123456789abcdef/;
317 $bx =~ tr/\x0f\x0e\x0d\x0c\x0b\x0a\x09\x08\x07\x06\x05\x04\x03\x02\x01\x00/fedcba9876543210/;
321 if ($CALC_EMU->can('_from_hex'))
323 $x->{value} = $CALC_EMU->_from_hex( \$bx );
327 $r = $self->new($bx);
328 $x->{value} = $r->{value};
331 # if one of X or Y was negative, we need to decrement result
332 $x->bdec() if $sign == 1;
339 my ($self,$x,$y,$sx,$sy,@r) = @_;
341 return $x->round(@r) if $y->is_zero();
343 my $sign = 0; # sign of result
344 $sign = 1 if $x->{sign} ne $y->{sign};
348 if ($sx == -1) # if x is negative
350 # two's complement: inc and flip all "bits" in $bx
351 $bx = $x->binc()->as_hex(); # -1 => 0, -2 => 1, -3 => 2 etc
353 $bx =~ tr/0123456789abcdef/\x0f\x0e\x0d\x0c\x0b\x0a\x09\x08\x07\x06\x05\x04\x03\x02\x01\x00/;
357 $bx = $x->as_hex(); # get binary representation
359 $bx =~ tr/fedcba9876543210/\x0f\x0e\x0d\x0c\x0b\x0a\x09\x08\x07\x06\x05\x04\x03\x02\x01\x00/;
361 if ($sy == -1) # if y is negative
363 # two's complement: inc and flip all "bits" in $by
364 $by = $y->copy()->binc()->as_hex(); # -1 => 0, -2 => 1, -3 => 2 etc
366 $by =~ tr/0123456789abcdef/\x0f\x0e\x0d\x0c\x0b\x0a\x09\x08\x07\x06\x05\x04\x03\x02\x01\x00/;
370 $by = $y->as_hex(); # get binary representation
372 $by =~ tr/fedcba9876543210/\x0f\x0e\x0d\x0c\x0b\x0a\x09\x08\x07\x06\x05\x04\x03\x02\x01\x00/;
374 # now we have bit-strings from X and Y, reverse them for padding
378 # padd the shorter string
379 my $xx = "\x00"; $xx = "\x0f" if $sx == -1;
380 my $yy = "\x00"; $yy = "\x0f" if $sy == -1;
381 my $diff = CORE::length($bx) - CORE::length($by);
388 $bx .= $xx x abs($diff);
391 # xor the strings together
394 # and reverse the result again
397 # one of $x or $y was negative, so need to flip bits in the result
398 # in both cases (one or two of them negative, or both positive) we need
399 # to get the characters back.
402 $bx =~ tr/\x0f\x0e\x0d\x0c\x0b\x0a\x09\x08\x07\x06\x05\x04\x03\x02\x01\x00/0123456789abcdef/;
406 $bx =~ tr/\x0f\x0e\x0d\x0c\x0b\x0a\x09\x08\x07\x06\x05\x04\x03\x02\x01\x00/fedcba9876543210/;
410 if ($CALC_EMU->can('_from_hex'))
412 $x->{value} = $CALC_EMU->_from_hex( \$bx );
416 $r = $self->new($bx);
417 $x->{value} = $r->{value};
420 # calculate sign of result
422 $x->{sign} = '-' if $sx != $sy && !$x->is_zero();
424 $x->bdec() if $sign == 1;
431 my ($self,$x,@r) = @_;
434 return $x->round(@r) if $x->is_zero(); # 0,1 => 0,1
436 return $x->bone('+',@r) if $x < 4; # 1,2,3 => 1
438 my $l = int($x->length()/2);
440 $x->bone(); # keep ref($x), but modify it
441 $x->blsft($l,10) if $l != 0; # first guess: 1.('0' x (l/2))
443 my $last = $self->bzero();
444 my $two = $self->new(2);
445 my $lastlast = $self->bzero();
446 #my $lastlast = $x+$two;
447 while ($last != $x && $lastlast != $x)
449 $lastlast = $last; $last = $x->copy();
453 $x->bdec() if $x * $x > $y; # overshot?
459 my ($self,$x,$y,@r) = @_;
461 return $x->bsqrt() if $y->bacmp(2) == 0; # 2 => square root
463 # since we take at least a cubic root, and only 8 ** 1/3 >= 2 (==2):
464 return $x->bone('+',@r) if $x < 8; # $x=2..7 => 1
466 my $num = $x->numify();
470 $x = $self->new( int ( sprintf ("%.8f", $num ** (1 / $y->numify() ))));
471 return $x->round(@r);
474 # if $n is a power of two, we can repeatedly take sqrt($X) and find the
475 # proper result, because sqrt(sqrt($x)) == root($x,4)
476 # See Calc.pm for more details
477 my $b = $y->as_bin();
478 if ($b =~ /0b1(0+)$/)
480 my $count = CORE::length($1); # 0b100 => len('00') => 2
481 my $cnt = $count; # counter for loop
482 my $shift = $self->new(6);
483 $x->blsft($shift); # add some zeros (even amount)
486 # 'inflate' $X by adding more zeros
488 # calculate sqrt($x), $x is now a bit too big, again. In the next
489 # round we make even bigger, again.
492 # $x is still to big, so truncate result
497 # trial computation by starting with 2,4,6,8,10 etc until we overstep
499 my $trial = $self->new(2);
500 my $two = $self->new(2);
501 my $s_128 = $self->new(128);
503 local undef $Math::BigInt::accuracy;
504 local undef $Math::BigInt::precision;
506 # while still to do more than X steps
509 $step = $self->new(2);
510 while ( $trial->copy->bpow($y)->bacmp($x) < 0)
517 if ( $trial->copy->bpow($y)->bacmp($x) == 0)
519 $x->{value} = $trial->{value}; # make copy while preserving ref to $x
520 return $x->round(@r);
522 # overstepped, so go back on step
524 } while ($step > $s_128);
526 $step = $two->copy();
527 while ( $trial->copy->bpow($y)->bacmp($x) < 0)
533 if ( $x->bacmp( $trial->copy()->bpow($y) ) < 0)
537 # copy result into $x (preserve ref)
538 $x->{value} = $trial->{value};
545 my ($self,$x,$s) = @_;
547 return '0x0' if $x->is_zero();
549 my $x1 = $x->copy()->babs(); my ($xr,$x10000,$h,$es);
552 $x10000 = $self->new (0x10000); $h = 'h4';
556 $x10000 = $self->new (0x1000); $h = 'h3';
558 while (!$x1->is_zero())
560 ($x1, $xr) = bdiv($x1,$x10000);
561 $es .= unpack($h,pack('v',$xr->numify()));
564 $es =~ s/^[0]+//; # strip leading zeros
570 my ($self,$x,$s) = @_;
572 return '0b0' if $x->is_zero();
574 my $x1 = $x->copy()->babs(); my ($xr,$x10000,$b,$es);
577 $x10000 = $self->new (0x10000); $b = 'b16';
581 $x10000 = $self->new (0x1000); $b = 'b12';
583 while (!$x1->is_zero())
585 ($x1, $xr) = bdiv($x1,$x10000);
586 $es .= unpack($b,pack('v',$xr->numify()));
589 $es =~ s/^[0]+//; # strip leading zeros
593 ##############################################################################
594 ##############################################################################
601 Math::BigInt::CalcEmu - Emulate low-level math with BigInt code
605 Contains routines that emulate low-level math functions in BigInt, e.g.
606 optional routines the low-level math package does not provide on it's own.
608 Will be loaded on demand and automatically by BigInt.
610 Stuff here is really low-priority to optimize,
611 since it is far better to implement the operation in the low-level math
612 libary directly, possible even using a call to the native lib.
620 This program is free software; you may redistribute it and/or modify it under
621 the same terms as Perl itself.
625 (c) Tels http://bloodgate.com 2003 - based on BigInt code by
630 L<Math::BigInt>, L<Math::BigFloat>, L<Math::BigInt::BitVect>,
631 L<Math::BigInt::GMP> and L<Math::BigInt::Pari>.