Upgrade to Locale::Maketext 1.07.
[p5sagit/p5-mst-13.2.git] / lib / Math / BigInt / CalcEmu.pm
CommitLineData
b282a552 1package Math::BigInt;
2
3use 5.005;
4use strict;
5# use warnings; # dont use warnings for older Perls
6
7use vars qw/$VERSION/;
8
3a427a11 9$VERSION = '0.02';
b282a552 10
11# See SYNOPSIS below.
12
13my $CALC_EMU;
14
15BEGIN
16 {
17 $CALC_EMU = Math::BigInt->config()->{'lib'};
18 }
19
20sub __emu_blog
21 {
22 my ($self,$x,$base,@r) = @_;
23
24 return $x->bnan() if $x->is_zero() || $base->is_zero() || $base->is_one();
25
26 my $acmp = $x->bacmp($base);
27 return $x->bone('+',@r) if $acmp == 0;
28 return $x->bzero(@r) if $acmp < 0 || $x->is_one();
29
30 # blog($x,$base) ** $base + $y = $x
31
32 # this trial multiplication is very fast, even for large counts (like for
33 # 2 ** 1024, since this still requires only 1024 very fast steps
34 # (multiplication of a large number by a very small number is very fast))
35 # See Calc for an even faster algorightmn
36 my $x_org = $x->copy(); # preserve orgx
37 $x->bzero(); # keep ref to $x
38 my $trial = $base->copy();
39 while ($trial->bacmp($x_org) <= 0)
40 {
41 $trial->bmul($base); $x->binc();
42 }
43 $x->round(@r);
44 }
45
46sub __emu_bmodinv
47 {
48 my ($self,$x,$y,@r) = @_;
49
50 my ($u, $u1) = ($self->bzero(), $self->bone());
51 my ($a, $b) = ($y->copy(), $x->copy());
52
53 # first step need always be done since $num (and thus $b) is never 0
54 # Note that the loop is aligned so that the check occurs between #2 and #1
55 # thus saving us one step #2 at the loop end. Typical loop count is 1. Even
56 # a case with 28 loops still gains about 3% with this layout.
57 my $q;
58 ($a, $q, $b) = ($b, $a->bdiv($b)); # step #1
59 # Euclid's Algorithm (calculate GCD of ($a,$b) in $a and also calculate
60 # two values in $u and $u1, we use only $u1 afterwards)
61 my $sign = 1; # flip-flop
62 while (!$b->is_zero()) # found GCD if $b == 0
63 {
64 # the original algorithm had:
65 # ($u, $u1) = ($u1, $u->bsub($u1->copy()->bmul($q))); # step #2
66 # The following creates exact the same sequence of numbers in $u1,
67 # except for the sign ($u1 is now always positive). Since formerly
68 # the sign of $u1 was alternating between '-' and '+', the $sign
69 # flip-flop will take care of that, so that at the end of the loop
70 # we have the real sign of $u1. Keeping numbers positive gains us
71 # speed since badd() is faster than bsub() and makes it possible
72 # to have the algorithmn in Calc for even more speed.
73
74 ($u, $u1) = ($u1, $u->badd($u1->copy()->bmul($q))); # step #2
75 $sign = - $sign; # flip sign
76
77 ($a, $q, $b) = ($b, $a->bdiv($b)); # step #1 again
78 }
79
80 # If the gcd is not 1, then return NaN! It would be pointless to have
81 # called bgcd to check this first, because we would then be performing
82 # the same Euclidean Algorithm *twice* in case the gcd is 1.
83 return $x->bnan() unless $a->is_one();
84
85 $u1->bneg() if $sign != 1; # need to flip?
86
87 $u1->bmod($y); # calc result
88 $x->{value} = $u1->{value}; # and copy over to $x
89 $x->{sign} = $u1->{sign}; # to modify in place
90 $x->round(@r);
91 }
92
93sub __emu_bmodpow
94 {
95 my ($self,$num,$exp,$mod,@r) = @_;
96
97 # in the trivial case,
98 return $num->bzero(@r) if $mod->is_one();
99 return $num->bone('+',@r) if $num->is_zero() or $num->is_one();
100
101 # $num->bmod($mod); # if $x is large, make it smaller first
102 my $acc = $num->copy(); # but this is not really faster...
103
104 $num->bone(); # keep ref to $num
105
106 my $expbin = $exp->as_bin(); $expbin =~ s/^[-]?0b//; # ignore sign and prefix
107 my $len = CORE::length($expbin);
108 while (--$len >= 0)
109 {
110 $num->bmul($acc)->bmod($mod) if substr($expbin,$len,1) eq '1';
111 $acc->bmul($acc)->bmod($mod);
112 }
113
114 $num->round(@r);
115 }
116
117sub __emu_bfac
118 {
119 my ($self,$x,@r) = @_;
120
121 return $x->bone('+',@r) if $x->is_zero() || $x->is_one(); # 0 or 1 => 1
122
123 my $n = $x->copy();
124 $x->bone();
125 # seems we need not to temp. clear A/P of $x since the result is the same
126 my $f = $self->new(2);
127 while ($f->bacmp($n) < 0)
128 {
129 $x->bmul($f); $f->binc();
130 }
131 $x->bmul($f,@r); # last step and also round result
132 }
133
134sub __emu_bpow
135 {
136 my ($self,$x,$y,@r) = @_;
137
138 return $x->bone('+',@r) if $y->is_zero();
139 return $x->round(@r) if $x->is_one() || $y->is_one();
140 return $x->round(@r) if $x->is_zero(); # 0**y => 0 (if not y <= 0)
141
142 my $pow2 = $self->bone();
143 my $y_bin = $y->as_bin(); $y_bin =~ s/^0b//;
144 my $len = CORE::length($y_bin);
145 while (--$len > 0)
146 {
147 $pow2->bmul($x) if substr($y_bin,$len,1) eq '1'; # is odd?
148 $x->bmul($x);
149 }
150 $x->bmul($pow2);
151 $x->round(@r) if !exists $x->{_f} || $x->{_f} & MB_NEVER_ROUND == 0;
152 $x;
153 }
154
155sub __emu_band
156 {
157 my ($self,$x,$y,$sx,$sy,@r) = @_;
158
159 return $x->bzero(@r) if $y->is_zero() || $x->is_zero();
160
161 my $sign = 0; # sign of result
162 $sign = 1 if $sx == -1 && $sy == -1;
163
164 my ($bx,$by);
165
166 if ($sx == -1) # if x is negative
167 {
168 # two's complement: inc and flip all "bits" in $bx
169 $bx = $x->binc()->as_hex(); # -1 => 0, -2 => 1, -3 => 2 etc
170 $bx =~ s/-?0x//;
171 $bx =~ tr/0123456789abcdef/\x0f\x0e\x0d\x0c\x0b\x0a\x09\x08\x07\x06\x05\x04\x03\x02\x01\x00/;
172 }
173 else
174 {
175 $bx = $x->as_hex(); # get binary representation
176 $bx =~ s/-?0x//;
177 $bx =~ tr/fedcba9876543210/\x0f\x0e\x0d\x0c\x0b\x0a\x09\x08\x07\x06\x05\x04\x03\x02\x01\x00/;
178 }
179 if ($sy == -1) # if y is negative
180 {
181 # two's complement: inc and flip all "bits" in $by
182 $by = $y->copy()->binc()->as_hex(); # -1 => 0, -2 => 1, -3 => 2 etc
183 $by =~ s/-?0x//;
184 $by =~ tr/0123456789abcdef/\x0f\x0e\x0d\x0c\x0b\x0a\x09\x08\x07\x06\x05\x04\x03\x02\x01\x00/;
185 }
186 else
187 {
188 $by = $y->as_hex(); # get binary representation
189 $by =~ s/-?0x//;
190 $by =~ tr/fedcba9876543210/\x0f\x0e\x0d\x0c\x0b\x0a\x09\x08\x07\x06\x05\x04\x03\x02\x01\x00/;
191 }
192 # now we have bit-strings from X and Y, reverse them for padding
193 $bx = reverse $bx;
194 $by = reverse $by;
195
196 # cut the longer string to the length of the shorter one (the result would
197 # be 0 due to AND anyway)
198 my $diff = CORE::length($bx) - CORE::length($by);
199 if ($diff > 0)
200 {
201 $bx = substr($bx,0,CORE::length($by));
202 }
203 elsif ($diff < 0)
204 {
205 $by = substr($by,0,CORE::length($bx));
206 }
207
208 # and the strings together
209 my $r = $bx & $by;
210
211 # and reverse the result again
212 $bx = reverse $r;
213
214 # one of $x or $y was negative, so need to flip bits in the result
215 # in both cases (one or two of them negative, or both positive) we need
216 # to get the characters back.
217 if ($sign == 1)
218 {
219 $bx =~ tr/\x0f\x0e\x0d\x0c\x0b\x0a\x09\x08\x07\x06\x05\x04\x03\x02\x01\x00/0123456789abcdef/;
220 }
221 else
222 {
223 $bx =~ tr/\x0f\x0e\x0d\x0c\x0b\x0a\x09\x08\x07\x06\x05\x04\x03\x02\x01\x00/fedcba9876543210/;
224 }
225
226 $bx = '0x' . $bx;
227 if ($CALC_EMU->can('_from_hex'))
228 {
229 $x->{value} = $CALC_EMU->_from_hex( \$bx );
230 }
231 else
232 {
233 $r = $self->new($bx);
234 $x->{value} = $r->{value};
235 }
236
237 # calculate sign of result
238 $x->{sign} = '+';
239 #$x->{sign} = '-' if $sx == $sy && $sx == -1 && !$x->is_zero();
240 $x->{sign} = '-' if $sign == 1 && !$x->is_zero();
241
242 $x->bdec() if $sign == 1;
243
244 $x->round(@r);
245 }
246
247sub __emu_bior
248 {
249 my ($self,$x,$y,$sx,$sy,@r) = @_;
250
251 return $x->round(@r) if $y->is_zero();
252
253 my $sign = 0; # sign of result
254 $sign = 1 if ($sx == -1) || ($sy == -1);
255
256 my ($bx,$by);
257
258 if ($sx == -1) # if x is negative
259 {
260 # two's complement: inc and flip all "bits" in $bx
261 $bx = $x->binc()->as_hex(); # -1 => 0, -2 => 1, -3 => 2 etc
262 $bx =~ s/-?0x//;
263 $bx =~ tr/0123456789abcdef/\x0f\x0e\x0d\x0c\x0b\x0a\x09\x08\x07\x06\x05\x04\x03\x02\x01\x00/;
264 }
265 else
266 {
267 $bx = $x->as_hex(); # get binary representation
268 $bx =~ s/-?0x//;
269 $bx =~ tr/fedcba9876543210/\x0f\x0e\x0d\x0c\x0b\x0a\x09\x08\x07\x06\x05\x04\x03\x02\x01\x00/;
270 }
271 if ($sy == -1) # if y is negative
272 {
273 # two's complement: inc and flip all "bits" in $by
274 $by = $y->copy()->binc()->as_hex(); # -1 => 0, -2 => 1, -3 => 2 etc
275 $by =~ s/-?0x//;
276 $by =~ tr/0123456789abcdef/\x0f\x0e\x0d\x0c\x0b\x0a\x09\x08\x07\x06\x05\x04\x03\x02\x01\x00/;
277 }
278 else
279 {
280 $by = $y->as_hex(); # get binary representation
281 $by =~ s/-?0x//;
282 $by =~ tr/fedcba9876543210/\x0f\x0e\x0d\x0c\x0b\x0a\x09\x08\x07\x06\x05\x04\x03\x02\x01\x00/;
283 }
284 # now we have bit-strings from X and Y, reverse them for padding
285 $bx = reverse $bx;
286 $by = reverse $by;
287
288 # padd the shorter string
289 my $xx = "\x00"; $xx = "\x0f" if $sx == -1;
290 my $yy = "\x00"; $yy = "\x0f" if $sy == -1;
291 my $diff = CORE::length($bx) - CORE::length($by);
292 if ($diff > 0)
293 {
294 $by .= $yy x $diff;
295 }
296 elsif ($diff < 0)
297 {
298 $bx .= $xx x abs($diff);
299 }
300
301 # or the strings together
302 my $r = $bx | $by;
303
304 # and reverse the result again
305 $bx = reverse $r;
306
307 # one of $x or $y was negative, so need to flip bits in the result
308 # in both cases (one or two of them negative, or both positive) we need
309 # to get the characters back.
310 if ($sign == 1)
311 {
312 $bx =~ tr/\x0f\x0e\x0d\x0c\x0b\x0a\x09\x08\x07\x06\x05\x04\x03\x02\x01\x00/0123456789abcdef/;
313 }
314 else
315 {
316 $bx =~ tr/\x0f\x0e\x0d\x0c\x0b\x0a\x09\x08\x07\x06\x05\x04\x03\x02\x01\x00/fedcba9876543210/;
317 }
318
319 $bx = '0x' . $bx;
320 if ($CALC_EMU->can('_from_hex'))
321 {
322 $x->{value} = $CALC_EMU->_from_hex( \$bx );
323 }
324 else
325 {
326 $r = $self->new($bx);
327 $x->{value} = $r->{value};
328 }
329
330 # if one of X or Y was negative, we need to decrement result
331 $x->bdec() if $sign == 1;
332
333 $x->round(@r);
334 }
335
336sub __emu_bxor
337 {
338 my ($self,$x,$y,$sx,$sy,@r) = @_;
339
340 return $x->round(@r) if $y->is_zero();
341
342 my $sign = 0; # sign of result
343 $sign = 1 if $x->{sign} ne $y->{sign};
344
345 my ($bx,$by);
346
347 if ($sx == -1) # if x is negative
348 {
349 # two's complement: inc and flip all "bits" in $bx
350 $bx = $x->binc()->as_hex(); # -1 => 0, -2 => 1, -3 => 2 etc
351 $bx =~ s/-?0x//;
352 $bx =~ tr/0123456789abcdef/\x0f\x0e\x0d\x0c\x0b\x0a\x09\x08\x07\x06\x05\x04\x03\x02\x01\x00/;
353 }
354 else
355 {
356 $bx = $x->as_hex(); # get binary representation
357 $bx =~ s/-?0x//;
358 $bx =~ tr/fedcba9876543210/\x0f\x0e\x0d\x0c\x0b\x0a\x09\x08\x07\x06\x05\x04\x03\x02\x01\x00/;
359 }
360 if ($sy == -1) # if y is negative
361 {
362 # two's complement: inc and flip all "bits" in $by
363 $by = $y->copy()->binc()->as_hex(); # -1 => 0, -2 => 1, -3 => 2 etc
364 $by =~ s/-?0x//;
365 $by =~ tr/0123456789abcdef/\x0f\x0e\x0d\x0c\x0b\x0a\x09\x08\x07\x06\x05\x04\x03\x02\x01\x00/;
366 }
367 else
368 {
369 $by = $y->as_hex(); # get binary representation
370 $by =~ s/-?0x//;
371 $by =~ tr/fedcba9876543210/\x0f\x0e\x0d\x0c\x0b\x0a\x09\x08\x07\x06\x05\x04\x03\x02\x01\x00/;
372 }
373 # now we have bit-strings from X and Y, reverse them for padding
374 $bx = reverse $bx;
375 $by = reverse $by;
376
377 # padd the shorter string
378 my $xx = "\x00"; $xx = "\x0f" if $sx == -1;
379 my $yy = "\x00"; $yy = "\x0f" if $sy == -1;
380 my $diff = CORE::length($bx) - CORE::length($by);
381 if ($diff > 0)
382 {
383 $by .= $yy x $diff;
384 }
385 elsif ($diff < 0)
386 {
387 $bx .= $xx x abs($diff);
388 }
389
390 # xor the strings together
391 my $r = $bx ^ $by;
392
393 # and reverse the result again
394 $bx = reverse $r;
395
396 # one of $x or $y was negative, so need to flip bits in the result
397 # in both cases (one or two of them negative, or both positive) we need
398 # to get the characters back.
399 if ($sign == 1)
400 {
401 $bx =~ tr/\x0f\x0e\x0d\x0c\x0b\x0a\x09\x08\x07\x06\x05\x04\x03\x02\x01\x00/0123456789abcdef/;
402 }
403 else
404 {
405 $bx =~ tr/\x0f\x0e\x0d\x0c\x0b\x0a\x09\x08\x07\x06\x05\x04\x03\x02\x01\x00/fedcba9876543210/;
406 }
407
408 $bx = '0x' . $bx;
409 if ($CALC_EMU->can('_from_hex'))
410 {
411 $x->{value} = $CALC_EMU->_from_hex( \$bx );
412 }
413 else
414 {
415 $r = $self->new($bx);
416 $x->{value} = $r->{value};
417 }
418
419 # calculate sign of result
420 $x->{sign} = '+';
421 $x->{sign} = '-' if $sx != $sy && !$x->is_zero();
422
423 $x->bdec() if $sign == 1;
424
425 $x->round(@r);
426 }
427
428sub __emu_bsqrt
429 {
430 my ($self,$x,@r) = @_;
431
432 # this is slow:
433 return $x->round(@r) if $x->is_zero(); # 0,1 => 0,1
434
435 return $x->bone('+',@r) if $x < 4; # 1,2,3 => 1
436 my $y = $x->copy();
437 my $l = int($x->length()/2);
438
439 $x->bone(); # keep ref($x), but modify it
440 $x->blsft($l,10) if $l != 0; # first guess: 1.('0' x (l/2))
441
442 my $last = $self->bzero();
443 my $two = $self->new(2);
444 my $lastlast = $self->bzero();
445 #my $lastlast = $x+$two;
446 while ($last != $x && $lastlast != $x)
447 {
448 $lastlast = $last; $last = $x->copy();
449 $x->badd($y / $x);
450 $x->bdiv($two);
451 }
452 $x->bdec() if $x * $x > $y; # overshot?
453 $x->round(@r);
454 }
455
456sub __emu_broot
457 {
458 my ($self,$x,$y,@r) = @_;
459
460 return $x->bsqrt() if $y->bacmp(2) == 0; # 2 => square root
461
462 # since we take at least a cubic root, and only 8 ** 1/3 >= 2 (==2):
463 return $x->bone('+',@r) if $x < 8; # $x=2..7 => 1
464
465 my $num = $x->numify();
466
467 if ($num <= 1000000)
468 {
469 $x = $self->new( int ( sprintf ("%.8f", $num ** (1 / $y->numify() ))));
470 return $x->round(@r);
471 }
472
473 # if $n is a power of two, we can repeatedly take sqrt($X) and find the
474 # proper result, because sqrt(sqrt($x)) == root($x,4)
475 # See Calc.pm for more details
476 my $b = $y->as_bin();
3a427a11 477 if ($b =~ /0b1(0+)$/)
b282a552 478 {
479 my $count = CORE::length($1); # 0b100 => len('00') => 2
480 my $cnt = $count; # counter for loop
481 my $shift = $self->new(6);
482 $x->blsft($shift); # add some zeros (even amount)
483 while ($cnt-- > 0)
484 {
485 # 'inflate' $X by adding more zeros
486 $x->blsft($shift);
487 # calculate sqrt($x), $x is now a bit too big, again. In the next
488 # round we make even bigger, again.
489 $x->bsqrt($x);
490 }
491 # $x is still to big, so truncate result
492 $x->brsft($shift);
493 }
494 else
495 {
3a427a11 496 # trial computation by starting with 2,4,6,8,10 etc until we overstep
497 my $step;
498 my $trial = $self->new(2);
499 my $two = $self->new(2);
500 my $s_128 = $self->new(128);
501
502 local undef $Math::BigInt::accuracy;
503 local undef $Math::BigInt::precision;
504
505 # while still to do more than X steps
506 do
507 {
508 $step = $self->new(2);
509 while ( $trial->copy->bpow($y)->bacmp($x) < 0)
510 {
511 $step->bmul($two);
512 $trial->badd($step);
513 }
514
515 # hit exactly?
516 if ( $trial->copy->bpow($y)->bacmp($x) == 0)
517 {
518 $x->{value} = $trial->{value}; # make copy while preserving ref to $x
519 return $x->round(@r);
520 }
521 # overstepped, so go back on step
522 $trial->bsub($step);
523 } while ($step > $s_128);
524
525 $step = $two->copy();
526 while ( $trial->copy->bpow($y)->bacmp($x) < 0)
527 {
528 $trial->badd($step);
529 }
530
531 # not hit exactly?
532 if ( $x->bacmp( $trial->copy()->bpow($y) ) < 0)
533 {
534 $trial->bdec();
535 }
536 # copy result into $x (preserve ref)
537 $x->{value} = $trial->{value};
b282a552 538 }
539 $x->round(@r);
540 }
541
542sub __emu_as_hex
543 {
544 my ($self,$x,$s) = @_;
545
546 return '0x0' if $x->is_zero();
547
548 my $x1 = $x->copy()->babs(); my ($xr,$x10000,$h,$es);
549 if ($] >= 5.006)
550 {
551 $x10000 = $self->new (0x10000); $h = 'h4';
552 }
553 else
554 {
555 $x10000 = $self->new (0x1000); $h = 'h3';
556 }
557 while (!$x1->is_zero())
558 {
559 ($x1, $xr) = bdiv($x1,$x10000);
560 $es .= unpack($h,pack('v',$xr->numify()));
561 }
562 $es = reverse $es;
563 $es =~ s/^[0]+//; # strip leading zeros
564 $s . '0x' . $es;
565 }
566
567sub __emu_as_bin
568 {
569 my ($self,$x,$s) = @_;
570
571 return '0b0' if $x->is_zero();
572
573 my $x1 = $x->copy()->babs(); my ($xr,$x10000,$b,$es);
574 if ($] >= 5.006)
575 {
576 $x10000 = $self->new (0x10000); $b = 'b16';
577 }
578 else
579 {
580 $x10000 = $self->new (0x1000); $b = 'b12';
581 }
582 while (!$x1->is_zero())
583 {
584 ($x1, $xr) = bdiv($x1,$x10000);
585 $es .= unpack($b,pack('v',$xr->numify()));
586 }
587 $es = reverse $es;
588 $es =~ s/^[0]+//; # strip leading zeros
589 $s . '0b' . $es;
590 }
591
592##############################################################################
593##############################################################################
594
5951;
596__END__
597
598=head1 NAME
599
600Math::BigInt::CalcEmu - Emulate low-level math with BigInt code
601
602=head1 SYNOPSIS
603
604Contains routines that emulate low-level math functions in BigInt, e.g.
605optional routines the low-level math package does not provide on it's own.
606
607Will be loaded on demand and automatically by BigInt.
608
609Stuff here is really low-priority to optimize,
610since it is far better to implement the operation in the low-level math
611libary directly, possible even using a call to the native lib.
612
613=head1 DESCRIPTION
614
615=head1 METHODS
616
617=head1 LICENSE
618
619This program is free software; you may redistribute it and/or modify it under
620the same terms as Perl itself.
621
622=head1 AUTHORS
623
624(c) Tels http://bloodgate.com 2003 - based on BigInt code by
625Tels from 2001-2003.
626
627=head1 SEE ALSO
628
629L<Math::BigInt>, L<Math::BigFloat>, L<Math::BigInt::BitVect>,
630L<Math::BigInt::GMP> and L<Math::BigInt::Pari>.
631
632=cut