File Coverage

blib/lib/DateTime/Math/bigint.pl
Criterion Covered Total %
statement 120 171 70.1
branch 54 106 50.9
condition 12 26 46.1
subroutine 14 20 70.0
pod 0 7 0.0
total 200 330 60.6


line stmt bran cond sub pod time code
1             package bigint;
2              
3 1     1   7 use strict;
  1         1  
  1         38  
4 1     1   14 use vars qw($zero);
  1         2  
  1         2194  
5              
6             # arbitrary size integer math package
7             #
8             # by Mark Biggar
9             #
10             # Canonical Big integer value are strings of the form
11             # /^[+-]\d+$/ with leading zeros suppressed
12             # Input values to these routines may be strings of the form
13             # /^\s*[+-]?[\d\s]+$/.
14             # Examples:
15             # '+0' canonical zero value
16             # ' -123 123 123' canonical value '-123123123'
17             # '1 23 456 7890' canonical value '+1234567890'
18             # Output values always always in canonical form
19             #
20             # Actual math is done in an internal format consisting of an array
21             # whose first element is the sign (/^[+-]$/) and whose remaining
22             # elements are base 100000 digits with the least significant digit first.
23             # The string 'NaN' is used to represent the result when input arguments
24             # are not numbers, as well as the result of dividing by zero
25             #
26             # routines provided are:
27             #
28             # bneg(BINT) return BINT negation
29             # babs(BINT) return BINT absolute value
30             # bcmp(BINT,BINT) return CODE compare numbers (undef,<0,=0,>0)
31             # badd(BINT,BINT) return BINT addition
32             # bsub(BINT,BINT) return BINT subtraction
33             # bmul(BINT,BINT) return BINT multiplication
34             # bdiv(BINT,BINT) return (BINT,BINT) division (quo,rem) just quo if scalar
35             # bmod(BINT,BINT) return BINT modulus
36             # bgcd(BINT,BINT) return BINT greatest common divisor
37             # bnorm(BINT) return BINT normalization
38             #
39              
40             $zero = 0;
41              
42              
43             # normalize string form of number. Strip leading zeros. Strip any
44             # white space and add a sign, if missing.
45             # Strings that are not numbers result the value 'NaN'.
46              
47             sub DateTime::Math::bnorm { #(num_str) return num_str
48 5345     5345   10972 local($_) = @_;
49 5345 50       12842 defined($_) or return 'NaN';
50 5345         9789 s/\s+//g; # strip white space
51 5345 50       39320 if (s/^([+-]?)0*(\d+)$/$1$2/) { # test if number
52 5345 50       28150 substr($_,0,0) = '+' unless $1; # Add missing sign
53 5345         7588 s/^-0/+0/;
54 5345         32632 $_;
55             } else {
56 0         0 'NaN';
57             }
58             }
59              
60             # Convert a number from string format to internal base 100000 format.
61             # Assumes normalized value as input.
62             sub internal { #(num_str) return int_num_array
63 3054     3054 0 4067 my $d = shift;
64 3054         7137 my ($is,$il) = (substr($d,0,1),length($d)-2);
65 3054         4662 substr($d,0,1) = '';
66 3054         20376 ($is, reverse(unpack("a" . ($il%5+1) . ("a5" x ($il/5)), $d)));
67             }
68              
69             # Convert a number from internal base 100000 format to string format.
70             # This routine scribbles all over input array.
71             sub external { #(int_num_array) return num_str
72 1613     1613 0 2361 my $es = shift;
73 1613   66     20833 grep($_ > 9999 || ($_ = substr('0000'.$_,-5)), @_); # zero pad
74 1613         6905 &DateTime::Math::bnorm(join('', $es, reverse(@_))); # reverse concat and normalize
75             }
76              
77             # Negate input value.
78             sub DateTime::Math::bneg { #(num_str) return num_str
79 0     0   0 my $num = &DateTime::Math::bnorm(@_);
80 0 0       0 vec($num,0,8) ^= ord('+') ^ ord('-') unless $num eq '+0';
81 0         0 $num =~ s/^H/N/;
82 0         0 $num;
83             }
84              
85             # Returns the absolute value of the input.
86             sub DateTime::Math::babs { #(num_str) return num_str
87 0     0   0 &abs(&DateTime::Math::bnorm(@_));
88             }
89              
90             sub abs { # post-normalized abs for internal use
91 1026     1026 0 1492 my $num = shift;
92 1026         2082 $num =~ s/^-/+/;
93 1026         7615 $num;
94             }
95              
96             # Compares 2 values. Returns one of undef, <0, =0, >0. (suitable for sort)
97             sub DateTime::Math::bcmp { #(num_str, num_str) return cond_code
98 246     246   759 my ($x,$y) = (&DateTime::Math::bnorm($_[0]),&DateTime::Math::bnorm($_[1]));
99 246 50       769 if ($x eq 'NaN') {
    50          
100 0         0 return;
101             } elsif ($y eq 'NaN') {
102 0         0 return;
103             } else {
104 246         415 &cmp($x,$y);
105             }
106             }
107              
108             sub cmp { # post-normalized compare for internal use
109 759     759 0 1235 my ($cx, $cy) = @_;
110              
111 759 100       1684 return 0 if ($cx eq $cy);
112              
113 737         1553 my ($sx, $sy) = (substr($cx, 0, 1), substr($cy, 0, 1));
114 737         1565 my $ld;
115              
116 737 50       1522 if ($sx eq '+') {
117 737 50 33     3393 return 1 if ($sy eq '-' || $cy eq '+0');
118 737         1196 $ld = length($cx) - length($cy);
119 737 100       3254 return $ld if ($ld);
120 394         2997 return $cx cmp $cy;
121             } else { # $sx eq '-'
122 0 0       0 return -1 if ($sy eq '+');
123 0         0 $ld = length($cy) - length($cx);
124 0 0       0 return $ld if ($ld);
125 0         0 return $cy cmp $cx;
126             }
127              
128             }
129              
130             sub DateTime::Math::badd { #(num_str, num_str) return num_str
131 1046     1046   2176 my ($x, $y) = (&DateTime::Math::bnorm($_[0]),&DateTime::Math::bnorm($_[1]));
132 1046 50       3149 if ($x eq 'NaN') {
    50          
133 0         0 'NaN';
134             } elsif ($y eq 'NaN') {
135 0         0 'NaN';
136             } else {
137 1046         2209 my @x = &internal($x); # convert to internal form
138 1046         2580 my @y = &internal($y);
139 1046         2600 my ($sx, $sy) = (shift @x, shift @y); # get signs
140 1046 100       2458 if ($sx eq $sy) {
141 712         1640 &external($sx, &add(\@x, \@y)); # if same sign add
142             } else {
143 334         613 ($x, $y) = (&abs($x),&abs($y)); # make abs
144 334 100       6123 if (&cmp($y,$x) > 0) {
145 93         231 &external($sy, &sub(\@y, \@x));
146             } else {
147 241         938 &external($sx, &sub(\@x, \@y));
148             }
149             }
150             }
151             }
152              
153             sub DateTime::Math::bsub { #(num_str, num_str) return num_str
154 0     0   0 &DateTime::Math::badd($_[0],&DateTime::Math::bneg($_[1]));
155             }
156              
157             # GCD -- Euclids algorithm Knuth Vol 2 pg 296
158             sub DateTime::Math::bgcd { #(num_str, num_str) return num_str
159 0     0   0 my ($x,$y) = (&DateTime::Math::bnorm($_[0]),&DateTime::Math::bnorm($_[1]));
160 0 0 0     0 if ($x eq 'NaN' || $y eq 'NaN') {
161 0         0 'NaN';
162             } else {
163 0         0 ($x, $y) = ($y,&DateTime::Math::bmod($x,$y)) while $y ne '+0';
164 0         0 $x;
165             }
166             }
167              
168             # routine to add two base 1e5 numbers
169             # stolen from Knuth Vol 2 Algorithm A pg 231
170             # there are separate routines to add and sub as per Kunth pg 233
171             sub add { #(int_num_array, int_num_array) return int_num_array
172 712     712 0 970 my ($x_array, $y_array) = @_;
173 712         892 my $car = 0;
174 712         1304 for my $x (@$x_array) {
175 1344 100 66     3642 last unless @$y_array || $car;
176 1307 50       6959 $x -= 1e5 if $car = (($x += (@$y_array ? shift(@$y_array) : 0) + $car) >= 1e5) ? 1 : 0;
    100          
    100          
177             }
178 712         1416 for my $y (@$y_array) {
179 227 100       649 last unless $car;
180 16 50       67 $y -= 1e5 if $car = (($y += $car) >= 1e5) ? 1 : 0;
    50          
181             }
182 712         2513 (@$x_array, @$y_array, $car);
183             }
184              
185             # subtract base 1e5 numbers -- stolen from Knuth Vol 2 pg 232, $x > $y
186             sub sub { #(int_num_array, int_num_array) return int_num_array
187 334     334 0 451 my ($sx_array, $sy_array) = @_;
188 334         409 my $bar = 0;
189 334         588 for my $sx (@$sx_array) {
190 2768 100 100     9735 last unless @$sy_array || $bar;
191 2748 100       11577 $sx += 1e5 if $bar = (($sx -= (@$sy_array ? shift(@$sy_array) : 0) + $bar) < 0);
    100          
192             }
193 334         1249 @$sx_array;
194             }
195              
196             # multiply two numbers -- stolen from Knuth Vol 2 pg 233
197             sub DateTime::Math::bmul { #(num_str, num_str) return num_str
198 395     395   1311 my ($x, $y) = (&DateTime::Math::bnorm($_[0]), &DateTime::Math::bnorm($_[1]));
199 395 50       1279 if ($x eq 'NaN') {
    50          
200 0         0 'NaN';
201             } elsif ($y eq 'NaN') {
202 0         0 'NaN';
203             } else {
204 395         875 my @x = &internal($x);
205 395         1334 my @y = &internal($y);
206 395         993 &external(&mul(\@x, \@y));
207             }
208             }
209              
210             # multiply two numbers in internal representation
211             # destroys the arguments, supposes that two arguments are different
212             sub mul { #(*int_num_array, *int_num_array) return int_num_array
213 395     395 0 558 my ($x_array, $y_array) = @_;
214 395 100       1130 my $signr = (shift @$x_array ne shift @$y_array) ? '-' : '+';
215 395         821 my @prod = ();
216 395         718 for my $x (@$x_array) {
217 1698         2147 my ($car, $cty) = (0, 0);
218 1698         2240 for my $y (@$y_array) {
219 1698   100     6391 my $prod = $x * $y + ($prod[$cty] || 0) + $car;
220 1698         24551 $prod[$cty++] =
221             $prod - ($car = int($prod * 1e-5)) * 1e5;
222             }
223 1698 100       3953 $prod[$cty] += $car if $car;
224 1698         3185 $x = shift @prod;
225             }
226 395         1696 ($signr, @$x_array, @prod);
227             }
228              
229              
230             # modulus
231             sub DateTime::Math::bmod { #(num_str, num_str) return num_str
232 0     0   0 (&DateTime::Math::bdiv(@_))[1];
233             }
234              
235             sub DateTime::Math::bdiv { #(dividend: num_str, divisor: num_str) return num_str
236 179     179   422 my ($x, $y) = (&DateTime::Math::bnorm($_[0]), &DateTime::Math::bnorm($_[1]));
237 179 0 33     1587 return wantarray ? ('NaN','NaN') : 'NaN'
    50 33        
238             if ($x eq 'NaN' || $y eq 'NaN' || $y eq '+0');
239 179 50       453 return wantarray ? ('+0',$x) : '+0' if (&cmp(&abs($x),&abs($y)) < 0);
    100          
240 86         209 my @x = &internal($x);
241 86         364 my @y = &internal($y);
242 86         176 my $srem = $y[0];
243 86 50       252 my $sr = (shift @x ne shift @y) ? '-' : '+';
244 86         232 my ($car, $bar, $prd, $dd) = (0, 0, 0, 0);
245 86 50       313 if (($dd = int(1e5/($y[$#y]+1))) != 1) {
246 86         169 for $x (@x) {
247 774         1163 $x = $x * $dd + $car;
248 774         1334 $x -= ($car = int($x * 1e-5)) * 1e5;
249             }
250 86         259 push(@x, $car); $car = 0;
  86         141  
251 86         149 for $y (@y) {
252 86         109 $y = $y * $dd + $car;
253 86         244 $y -= ($car = int($y * 1e-5)) * 1e5;
254             }
255             }
256             else {
257 0         0 push(@x, 0);
258             }
259 86         216 my @q = ();
260 86         192 my ($v2,$v1) = @y[-2,-1];
261 86         264 while ($#x > $#y) {
262 774         1536 my ($u2,$u1,$u0) = @x[-3..-1];
263 774 50       1969 my $q = (($u0 == $v1) ? 99999 : int(($u0*1e5+$u1)/$v1));
264             {
265 774         945 local $^W = 0;
  774         1919  
266 774         3179 --$q while ($v2*$q > ($u0*1e5+$u1-$q*$v1)*1e5+$u2);
267             }
268 774 100       1670 if ($q) {
269 675         1107 ($car, $bar) = (0,0);
270 675         2061 for ($y = 0, $x = $#x-$#y-1; $y <= $#y; ++$y,++$x) {
271 675         1100 $prd = $q * $y[$y] + $car;
272 675         1047 $prd -= ($car = int($prd * 1e-5)) * 1e5;
273 675 100       3065 $x[$x] += 1e5 if ($bar = (($x[$x] -= $prd + $bar) < 0));
274             }
275 675 50       1576 if ($x[$#x] < $car + $bar) {
276 0         0 $car = 0; --$q;
  0         0  
277 0         0 for ($y = 0, $x = $#x-$#y-1; $y <= $#y; ++$y,++$x) {
278 0 0       0 $x[$x] -= 1e5
279             if ($car = (($x[$x] += $y[$y] + $car) > 1e5));
280             }
281             }
282             }
283 774         1046 pop(@x); unshift(@q, $q);
  774         2538  
284             }
285 86 50       198 if (wantarray) {
286 86         142 my @d = ();
287 86 50       206 if ($dd != 1) {
288 86         121 $car = 0;
289 86         165 for $x (reverse @x) {
290 86         142 $prd = $car * 1e5 + $x;
291 86         195 $car = $prd - (my $tmp = int($prd / $dd)) * $dd;
292 86         222 unshift(@d, $tmp);
293             }
294             }
295             else {
296 0         0 @d = @x;
297             }
298 86         253 (&external($sr, @q), &external($srem, @d, $zero));
299             } else {
300 0           &external($sr, @q);
301             }
302             }
303              
304             # compute power of two numbers -- stolen from Knuth Vol 2 pg 233
305             sub DateTime::Math::bpow { #(num_str, num_str) return num_str
306 0     0     my ($x, $y) = (&DateTime::Math::bnorm($_[0]), &DateTime::Math::bnorm($_[1]));
307 0 0 0       if ($x eq 'NaN') {
    0          
    0          
    0          
    0          
    0          
308 0           'NaN';
309             } elsif ($y eq 'NaN') {
310 0           'NaN';
311             } elsif ($x eq '+1') {
312 0           '+1';
313             } elsif ($x eq '-1') {
314 0 0         &bmod($x,2) ? '-1': '+1';
315             } elsif ($y =~ /^-/) {
316 0           'NaN';
317             } elsif ($x eq '+0' && $y eq '+0') {
318 0           'NaN';
319             } else {
320 0           my @x = &internal($x);
321 0           my @pow2 = @x;
322 0           my @pow = &internal("+1");
323 0           my ($y1, $res, @tmp1, @tmp2) = (1); # need tmp to send to mul
324 0           while ($y ne '+0') {
325 0           ($y,$res)=&DateTime::Math::bdiv($y,2);
326 0 0         if ($res ne '+0') {my @tmp=@pow2; @pow =&mul(\@pow, \@tmp);}
  0            
  0            
327 0 0         if ($y ne '+0') {my @tmp=@pow2; @pow2=&mul(\@pow2, \@tmp);}
  0            
  0            
328             }
329 0           &external(@pow);
330             }
331             }
332              
333             1;