This is a live mirror of the Perl 5 development currently hosted at https://github.com/perl/perl5
pod punctuation nit in vmsish
[perl5.git] / lib / bigint.pl
1 warn "Legacy library @{[(caller(0))[6]]} will be removed from the Perl core distribution in the next major release. Please install it from the CPAN distribution Perl4::CoreLibs. It is being used at @{[(caller)[1]]}, line @{[(caller)[2]]}.\n";
2
3 package bigint;
4 #
5 # This library is no longer being maintained, and is included for backward
6 # compatibility with Perl 4 programs which may require it.
7 #
8 # In particular, this should not be used as an example of modern Perl
9 # programming techniques.
10 # This legacy library is deprecated and will be removed in a future
11 # release of perl.
12 #
13 # Suggested alternative:  Math::BigInt
14
15 # arbitrary size integer math package
16 #
17 # by Mark Biggar
18 #
19 # Canonical Big integer value are strings of the form
20 #       /^[+-]\d+$/ with leading zeros suppressed
21 # Input values to these routines may be strings of the form
22 #       /^\s*[+-]?[\d\s]+$/.
23 # Examples:
24 #   '+0'                            canonical zero value
25 #   '   -123 123 123'               canonical value '-123123123'
26 #   '1 23 456 7890'                 canonical value '+1234567890'
27 # Output values always in canonical form
28 #
29 # Actual math is done in an internal format consisting of an array
30 #   whose first element is the sign (/^[+-]$/) and whose remaining 
31 #   elements are base 100000 digits with the least significant digit first.
32 # The string 'NaN' is used to represent the result when input arguments 
33 #   are not numbers, as well as the result of dividing by zero
34 #
35 # routines provided are:
36 #
37 #   bneg(BINT) return BINT              negation
38 #   babs(BINT) return BINT              absolute value
39 #   bcmp(BINT,BINT) return CODE         compare numbers (undef,<0,=0,>0)
40 #   badd(BINT,BINT) return BINT         addition
41 #   bsub(BINT,BINT) return BINT         subtraction
42 #   bmul(BINT,BINT) return BINT         multiplication
43 #   bdiv(BINT,BINT) return (BINT,BINT)  division (quo,rem) just quo if scalar
44 #   bmod(BINT,BINT) return BINT         modulus
45 #   bgcd(BINT,BINT) return BINT         greatest common divisor
46 #   bnorm(BINT) return BINT             normalization
47 #
48
49 # overcome a floating point problem on certain osnames (posix-bc, os390)
50 BEGIN {
51     my $x = 100000.0;
52     my $use_mult = int($x*1e-5)*1e5 == $x ? 1 : 0;
53 }
54
55 $zero = 0;
56
57 \f
58 # normalize string form of number.   Strip leading zeros.  Strip any
59 #   white space and add a sign, if missing.
60 # Strings that are not numbers result the value 'NaN'.
61
62 sub main'bnorm { #(num_str) return num_str
63     local($_) = @_;
64     s/\s+//g;                           # strip white space
65     if (s/^([+-]?)0*(\d+)$/$1$2/) {     # test if number
66         substr($_,0,0) = '+' unless $1; # Add missing sign
67         s/^-0/+0/;
68         $_;
69     } else {
70         'NaN';
71     }
72 }
73
74 # Convert a number from string format to internal base 100000 format.
75 #   Assumes normalized value as input.
76 sub internal { #(num_str) return int_num_array
77     local($d) = @_;
78     ($is,$il) = (substr($d,0,1),length($d)-2);
79     substr($d,0,1) = '';
80     ($is, reverse(unpack("a" . ($il%5+1) . ("a5" x ($il/5)), $d)));
81 }
82
83 # Convert a number from internal base 100000 format to string format.
84 #   This routine scribbles all over input array.
85 sub external { #(int_num_array) return num_str
86     $es = shift;
87     grep($_ > 9999 || ($_ = substr('0000'.$_,-5)), @_);   # zero pad
88     &'bnorm(join('', $es, reverse(@_)));    # reverse concat and normalize
89 }
90
91 # Negate input value.
92 sub main'bneg { #(num_str) return num_str
93     local($_) = &'bnorm(@_);
94     vec($_,0,8) ^= ord('+') ^ ord('-') unless $_ eq '+0';
95     s/^./N/ unless /^[-+]/; # works both in ASCII and EBCDIC
96     $_;
97 }
98
99 # Returns the absolute value of the input.
100 sub main'babs { #(num_str) return num_str
101     &abs(&'bnorm(@_));
102 }
103
104 sub abs { # post-normalized abs for internal use
105     local($_) = @_;
106     s/^-/+/;
107     $_;
108 }
109 \f
110 # Compares 2 values.  Returns one of undef, <0, =0, >0. (suitable for sort)
111 sub main'bcmp { #(num_str, num_str) return cond_code
112     local($x,$y) = (&'bnorm($_[0]),&'bnorm($_[1]));
113     if ($x eq 'NaN') {
114         undef;
115     } elsif ($y eq 'NaN') {
116         undef;
117     } else {
118         &cmp($x,$y);
119     }
120 }
121
122 sub cmp { # post-normalized compare for internal use
123     local($cx, $cy) = @_;
124     return 0 if ($cx eq $cy);
125
126     local($sx, $sy) = (substr($cx, 0, 1), substr($cy, 0, 1));
127     local($ld);
128
129     if ($sx eq '+') {
130       return  1 if ($sy eq '-' || $cy eq '+0');
131       $ld = length($cx) - length($cy);
132       return $ld if ($ld);
133       return $cx cmp $cy;
134     } else { # $sx eq '-'
135       return -1 if ($sy eq '+');
136       $ld = length($cy) - length($cx);
137       return $ld if ($ld);
138       return $cy cmp $cx;
139     }
140
141 }
142
143 sub main'badd { #(num_str, num_str) return num_str
144     local(*x, *y); ($x, $y) = (&'bnorm($_[0]),&'bnorm($_[1]));
145     if ($x eq 'NaN') {
146         'NaN';
147     } elsif ($y eq 'NaN') {
148         'NaN';
149     } else {
150         @x = &internal($x);             # convert to internal form
151         @y = &internal($y);
152         local($sx, $sy) = (shift @x, shift @y); # get signs
153         if ($sx eq $sy) {
154             &external($sx, &add(*x, *y)); # if same sign add
155         } else {
156             ($x, $y) = (&abs($x),&abs($y)); # make abs
157             if (&cmp($y,$x) > 0) {
158                 &external($sy, &sub(*y, *x));
159             } else {
160                 &external($sx, &sub(*x, *y));
161             }
162         }
163     }
164 }
165
166 sub main'bsub { #(num_str, num_str) return num_str
167     &'badd($_[0],&'bneg($_[1]));    
168 }
169
170 # GCD -- Euclid's algorithm Knuth Vol 2 pg 296
171 sub main'bgcd { #(num_str, num_str) return num_str
172     local($x,$y) = (&'bnorm($_[0]),&'bnorm($_[1]));
173     if ($x eq 'NaN' || $y eq 'NaN') {
174         'NaN';
175     } else {
176         ($x, $y) = ($y,&'bmod($x,$y)) while $y ne '+0';
177         $x;
178     }
179 }
180 \f
181 # routine to add two base 1e5 numbers
182 #   stolen from Knuth Vol 2 Algorithm A pg 231
183 #   there are separate routines to add and sub as per Kunth pg 233
184 sub add { #(int_num_array, int_num_array) return int_num_array
185     local(*x, *y) = @_;
186     $car = 0;
187     for $x (@x) {
188         last unless @y || $car;
189         $x -= 1e5 if $car = (($x += shift(@y) + $car) >= 1e5) ? 1 : 0;
190     }
191     for $y (@y) {
192         last unless $car;
193         $y -= 1e5 if $car = (($y += $car) >= 1e5) ? 1 : 0;
194     }
195     (@x, @y, $car);
196 }
197
198 # subtract base 1e5 numbers -- stolen from Knuth Vol 2 pg 232, $x > $y
199 sub sub { #(int_num_array, int_num_array) return int_num_array
200     local(*sx, *sy) = @_;
201     $bar = 0;
202     for $sx (@sx) {
203         last unless @y || $bar;
204         $sx += 1e5 if $bar = (($sx -= shift(@sy) + $bar) < 0);
205     }
206     @sx;
207 }
208
209 # multiply two numbers -- stolen from Knuth Vol 2 pg 233
210 sub main'bmul { #(num_str, num_str) return num_str
211     local(*x, *y); ($x, $y) = (&'bnorm($_[0]), &'bnorm($_[1]));
212     if ($x eq 'NaN') {
213         'NaN';
214     } elsif ($y eq 'NaN') {
215         'NaN';
216     } else {
217         @x = &internal($x);
218         @y = &internal($y);
219         local($signr) = (shift @x ne shift @y) ? '-' : '+';
220         @prod = ();
221         for $x (@x) {
222             ($car, $cty) = (0, 0);
223             for $y (@y) {
224                 $prod = $x * $y + $prod[$cty] + $car;
225                 if ($use_mult) {
226                     $prod[$cty++] =
227                         $prod - ($car = int($prod * 1e-5)) * 1e5;
228                 }
229                 else {
230                     $prod[$cty++] =
231                         $prod - ($car = int($prod / 1e5)) * 1e5;
232                 }
233             }
234             $prod[$cty] += $car if $car;
235             $x = shift @prod;
236         }
237         &external($signr, @x, @prod);
238     }
239 }
240
241 # modulus
242 sub main'bmod { #(num_str, num_str) return num_str
243     (&'bdiv(@_))[1];
244 }
245 \f
246 sub main'bdiv { #(dividend: num_str, divisor: num_str) return num_str
247     local (*x, *y); ($x, $y) = (&'bnorm($_[0]), &'bnorm($_[1]));
248     return wantarray ? ('NaN','NaN') : 'NaN'
249         if ($x eq 'NaN' || $y eq 'NaN' || $y eq '+0');
250     return wantarray ? ('+0',$x) : '+0' if (&cmp(&abs($x),&abs($y)) < 0);
251     @x = &internal($x); @y = &internal($y);
252     $srem = $y[0];
253     $sr = (shift @x ne shift @y) ? '-' : '+';
254     $car = $bar = $prd = 0;
255     if (($dd = int(1e5/($y[$#y]+1))) != 1) {
256         for $x (@x) {
257             $x = $x * $dd + $car;
258             if ($use_mult) {
259             $x -= ($car = int($x * 1e-5)) * 1e5;
260             }
261             else {
262             $x -= ($car = int($x / 1e5)) * 1e5;
263             }
264         }
265         push(@x, $car); $car = 0;
266         for $y (@y) {
267             $y = $y * $dd + $car;
268             if ($use_mult) {
269             $y -= ($car = int($y * 1e-5)) * 1e5;
270             }
271             else {
272             $y -= ($car = int($y / 1e5)) * 1e5;
273             }
274         }
275     }
276     else {
277         push(@x, 0);
278     }
279     @q = (); ($v2,$v1) = @y[-2,-1];
280     while ($#x > $#y) {
281         ($u2,$u1,$u0) = @x[-3..-1];
282         $q = (($u0 == $v1) ? 99999 : int(($u0*1e5+$u1)/$v1));
283         --$q while ($v2*$q > ($u0*1e5+$u1-$q*$v1)*1e5+$u2);
284         if ($q) {
285             ($car, $bar) = (0,0);
286             for ($y = 0, $x = $#x-$#y-1; $y <= $#y; ++$y,++$x) {
287                 $prd = $q * $y[$y] + $car;
288                 if ($use_mult) {
289                 $prd -= ($car = int($prd * 1e-5)) * 1e5;
290                 }
291                 else {
292                 $prd -= ($car = int($prd / 1e5)) * 1e5;
293                 }
294                 $x[$x] += 1e5 if ($bar = (($x[$x] -= $prd + $bar) < 0));
295             }
296             if ($x[$#x] < $car + $bar) {
297                 $car = 0; --$q;
298                 for ($y = 0, $x = $#x-$#y-1; $y <= $#y; ++$y,++$x) {
299                     $x[$x] -= 1e5
300                         if ($car = (($x[$x] += $y[$y] + $car) > 1e5));
301                 }
302             }   
303         }
304         pop(@x); unshift(@q, $q);
305     }
306     if (wantarray) {
307         @d = ();
308         if ($dd != 1) {
309             $car = 0;
310             for $x (reverse @x) {
311                 $prd = $car * 1e5 + $x;
312                 $car = $prd - ($tmp = int($prd / $dd)) * $dd;
313                 unshift(@d, $tmp);
314             }
315         }
316         else {
317             @d = @x;
318         }
319         (&external($sr, @q), &external($srem, @d, $zero));
320     } else {
321         &external($sr, @q);
322     }
323 }
324 1;