Commit | Line | Data |
184f15d5 |
1 | #!/usr/bin/perl -w |
2 | |
3 | # The following hash values are used: |
4 | # sign : +,-,NaN,+inf,-inf |
5 | # _d : denominator |
6 | # _n : numeraotr (value = _n/_d) |
7 | # _a : accuracy |
8 | # _p : precision |
bce7c187 |
9 | # _f : flags, used by MBR to flag parts of a rational as untouchable |
184f15d5 |
10 | |
11 | package Math::BigRat; |
12 | |
13 | require 5.005_02; |
14 | use strict; |
15 | |
16 | use Exporter; |
17 | use Math::BigFloat; |
18 | use vars qw($VERSION @ISA $PACKAGE @EXPORT_OK $upgrade $downgrade |
19 | $accuracy $precision $round_mode $div_scale); |
20 | |
21 | @ISA = qw(Exporter Math::BigFloat); |
22 | @EXPORT_OK = qw(); |
23 | |
6de7f0cc |
24 | $VERSION = '0.06'; |
184f15d5 |
25 | |
26 | use overload; # inherit from Math::BigFloat |
27 | |
28 | ############################################################################## |
29 | # global constants, flags and accessory |
30 | |
31 | use constant MB_NEVER_ROUND => 0x0001; |
32 | |
33 | $accuracy = $precision = undef; |
34 | $round_mode = 'even'; |
35 | $div_scale = 40; |
36 | $upgrade = undef; |
37 | $downgrade = undef; |
38 | |
39 | my $nan = 'NaN'; |
40 | my $class = 'Math::BigRat'; |
6de7f0cc |
41 | my $MBI = 'Math::BigInt'; |
184f15d5 |
42 | |
8f675a64 |
43 | sub isa |
44 | { |
45 | return 0 if $_[1] =~ /^Math::Big(Int|Float)/; # we aren't |
46 | UNIVERSAL::isa(@_); |
47 | } |
48 | |
184f15d5 |
49 | sub _new_from_float |
50 | { |
bce7c187 |
51 | # turn a single float input into a rational (like '0.1') |
184f15d5 |
52 | my ($self,$f) = @_; |
53 | |
54 | return $self->bnan() if $f->is_nan(); |
55 | return $self->binf('-inf') if $f->{sign} eq '-inf'; |
56 | return $self->binf('+inf') if $f->{sign} eq '+inf'; |
57 | |
58 | #print "f $f caller", join(' ',caller()),"\n"; |
59 | $self->{_n} = $f->{_m}->copy(); # mantissa |
6de7f0cc |
60 | $self->{_d} = $MBI->bone(); |
184f15d5 |
61 | $self->{sign} = $f->{sign}; $self->{_n}->{sign} = '+'; |
62 | if ($f->{_e}->{sign} eq '-') |
63 | { |
64 | # something like Math::BigRat->new('0.1'); |
65 | $self->{_d}->blsft($f->{_e}->copy()->babs(),10); # 1 / 1 => 1/10 |
66 | } |
67 | else |
68 | { |
69 | # something like Math::BigRat->new('10'); |
70 | # 1 / 1 => 10/1 |
71 | $self->{_n}->blsft($f->{_e},10) unless $f->{_e}->is_zero(); |
72 | } |
73 | # print "float new $self->{_n} / $self->{_d}\n"; |
74 | $self; |
75 | } |
76 | |
77 | sub new |
78 | { |
79 | # create a Math::BigRat |
80 | my $class = shift; |
81 | |
82 | my ($n,$d) = shift; |
83 | |
84 | my $self = { }; bless $self,$class; |
85 | |
6de7f0cc |
86 | # print "ref ",ref($n),"\n"; |
87 | # if (ref($n)) |
184f15d5 |
88 | # { |
6de7f0cc |
89 | # print "isa float " if $n->isa('Math::BigFloat'); |
90 | # print "isa int " if $n->isa('Math::BigInt'); |
91 | # print "isa rat " if $n->isa('Math::BigRat'); |
92 | # print "isa lite " if $n->isa('Math::BigInt::Lite'); |
93 | # } |
94 | # else |
95 | # { |
96 | # print "scalar $n\n"; |
184f15d5 |
97 | # } |
184f15d5 |
98 | # input like (BigInt,BigInt) or (BigFloat,BigFloat) not handled yet |
99 | |
6de7f0cc |
100 | if ((!defined $d) && (ref $n) && (!$n->isa('Math::BigRat'))) |
184f15d5 |
101 | { |
102 | # print "is ref, but not rat\n"; |
103 | if ($n->isa('Math::BigFloat')) |
104 | { |
8f675a64 |
105 | # print "is ref, and float\n"; |
184f15d5 |
106 | return $self->_new_from_float($n)->bnorm(); |
107 | } |
108 | if ($n->isa('Math::BigInt')) |
109 | { |
8f675a64 |
110 | $self->{_n} = $n->copy(); # "mantissa" = $n |
6de7f0cc |
111 | $self->{_d} = $MBI->bone(); |
8f675a64 |
112 | $self->{sign} = $self->{_n}->{sign}; $self->{_n}->{sign} = '+'; |
113 | return $self->bnorm(); |
114 | } |
115 | if ($n->isa('Math::BigInt::Lite')) |
116 | { |
117 | # print "is ref, and lite\n"; |
6de7f0cc |
118 | $self->{_n} = $MBI->new($$n); # "mantissa" = $n |
119 | $self->{_d} = $MBI->bone(); |
184f15d5 |
120 | $self->{sign} = $self->{_n}->{sign}; $self->{_n}->{sign} = '+'; |
121 | return $self->bnorm(); |
122 | } |
123 | } |
124 | return $n->copy() if ref $n; |
125 | |
126 | # print "is string\n"; |
127 | |
128 | if (!defined $n) |
129 | { |
6de7f0cc |
130 | $self->{_n} = $MBI->bzero(); # undef => 0 |
131 | $self->{_d} = $MBI->bone(); |
184f15d5 |
132 | $self->{sign} = '+'; |
133 | return $self->bnorm(); |
134 | } |
135 | # string input with / delimiter |
136 | if ($n =~ /\s*\/\s*/) |
137 | { |
138 | return Math::BigRat->bnan() if $n =~ /\/.*\//; # 1/2/3 isn't valid |
139 | return Math::BigRat->bnan() if $n =~ /\/\s*$/; # 1/ isn't valid |
140 | ($n,$d) = split (/\//,$n); |
141 | # try as BigFloats first |
142 | if (($n =~ /[\.eE]/) || ($d =~ /[\.eE]/)) |
143 | { |
144 | # one of them looks like a float |
145 | $self->_new_from_float(Math::BigFloat->new($n)); |
146 | # now correct $self->{_n} due to $n |
147 | my $f = Math::BigFloat->new($d); |
148 | if ($f->{_e}->{sign} eq '-') |
149 | { |
150 | # 10 / 0.1 => 100/1 |
151 | $self->{_n}->blsft($f->{_e}->copy()->babs(),10); |
152 | } |
153 | else |
154 | { |
155 | $self->{_d}->blsft($f->{_e},10); # 1 / 1 => 10/1 |
156 | } |
157 | } |
158 | else |
159 | { |
6de7f0cc |
160 | $self->{_n} = $MBI->new($n); |
161 | $self->{_d} = $MBI->new($d); |
184f15d5 |
162 | return $self->bnan() if $self->{_n}->is_nan() || $self->{_d}->is_nan(); |
163 | # inf handling is missing here |
164 | |
165 | $self->{sign} = $self->{_n}->{sign}; $self->{_n}->{sign} = '+'; |
166 | # if $d is negative, flip sign |
167 | $self->{sign} =~ tr/+-/-+/ if $self->{_d}->{sign} eq '-'; |
168 | $self->{_d}->{sign} = '+'; # normalize |
169 | } |
170 | return $self->bnorm(); |
171 | } |
172 | |
173 | # simple string input |
174 | if (($n =~ /[\.eE]/)) |
175 | { |
176 | # looks like a float |
177 | # print "float-like string $d\n"; |
178 | $self->_new_from_float(Math::BigFloat->new($n)); |
179 | } |
180 | else |
181 | { |
6de7f0cc |
182 | $self->{_n} = $MBI->new($n); |
183 | $self->{_d} = $MBI->bone(); |
184f15d5 |
184 | $self->{sign} = $self->{_n}->{sign}; $self->{_n}->{sign} = '+'; |
185 | } |
186 | $self->bnorm(); |
187 | } |
188 | |
8f675a64 |
189 | ############################################################################### |
190 | |
184f15d5 |
191 | sub bstr |
192 | { |
193 | my ($self,$x) = ref($_[0]) ? (ref($_[0]),$_[0]) : objectify(1,@_); |
194 | |
195 | if ($x->{sign} !~ /^[+-]$/) # inf, NaN etc |
196 | { |
197 | my $s = $x->{sign}; $s =~ s/^\+//; # +inf => inf |
198 | return $s; |
199 | } |
200 | |
201 | # print "bstr $x->{sign} $x->{_n} $x->{_d}\n"; |
202 | my $s = ''; $s = $x->{sign} if $x->{sign} ne '+'; # +3 vs 3 |
203 | |
204 | return $s.$x->{_n}->bstr() if $x->{_d}->is_one(); |
205 | return $s.$x->{_n}->bstr() . '/' . $x->{_d}->bstr(); |
206 | } |
207 | |
208 | sub bsstr |
209 | { |
210 | my ($self,$x) = ref($_[0]) ? (ref($_[0]),$_[0]) : objectify(1,@_); |
211 | |
212 | if ($x->{sign} !~ /^[+-]$/) # inf, NaN etc |
213 | { |
214 | my $s = $x->{sign}; $s =~ s/^\+//; # +inf => inf |
215 | return $s; |
216 | } |
217 | |
218 | my $s = ''; $s = $x->{sign} if $x->{sign} ne '+'; # +3 vs 3 |
219 | return $x->{_n}->bstr() . '/' . $x->{_d}->bstr(); |
220 | } |
221 | |
222 | sub bnorm |
223 | { |
224 | # reduce the number to the shortest form and remember this (so that we |
225 | # don't reduce again) |
226 | my ($self,$x) = ref($_[0]) ? (ref($_[0]),$_[0]) : objectify(1,@_); |
227 | |
6de7f0cc |
228 | # both parts must be BigInt's |
229 | die ("n is not $MBI but (".ref($x->{_n}).')') |
230 | if ref($x->{_n}) ne $MBI; |
231 | die ("d is not $MBI but (".ref($x->{_d}).')') |
232 | if ref($x->{_d}) ne $MBI; |
233 | |
184f15d5 |
234 | # this is to prevent automatically rounding when MBI's globals are set |
235 | $x->{_d}->{_f} = MB_NEVER_ROUND; |
236 | $x->{_n}->{_f} = MB_NEVER_ROUND; |
237 | # 'forget' that parts were rounded via MBI::bround() in MBF's bfround() |
238 | $x->{_d}->{_a} = undef; $x->{_n}->{_a} = undef; |
239 | $x->{_d}->{_p} = undef; $x->{_n}->{_p} = undef; |
240 | |
6de7f0cc |
241 | # print "$x->{sign} $x->{_n} / $x->{_d} => "; |
242 | |
243 | # no normalize for NaN, inf etc. |
244 | return $x if $x->{sign} !~ /^[+-]$/; |
245 | |
184f15d5 |
246 | # normalize zeros to 0/1 |
247 | if (($x->{sign} =~ /^[+-]$/) && |
248 | ($x->{_n}->is_zero())) |
249 | { |
250 | $x->{sign} = '+'; # never -0 |
6de7f0cc |
251 | $x->{_d} = $MBI->bone() unless $x->{_d}->is_one(); |
184f15d5 |
252 | return $x; |
253 | } |
254 | |
6de7f0cc |
255 | return $x if $x->{_d}->is_one(); |
256 | |
184f15d5 |
257 | # reduce other numbers |
8f675a64 |
258 | # print "bgcd $x->{_n} (",ref($x->{_n}),") $x->{_d} (",ref($x->{_d}),")\n"; |
259 | # disable upgrade in BigInt, otherwise deep recursion |
260 | local $Math::BigInt::upgrade = undef; |
184f15d5 |
261 | my $gcd = $x->{_n}->bgcd($x->{_d}); |
262 | |
263 | if (!$gcd->is_one()) |
264 | { |
6de7f0cc |
265 | # print "normalize $x->{_d} / $x->{_n} => "; |
184f15d5 |
266 | $x->{_n}->bdiv($gcd); |
267 | $x->{_d}->bdiv($gcd); |
6de7f0cc |
268 | # print "$x->{_d} / $x->{_n}\n"; |
184f15d5 |
269 | } |
270 | # print "$x->{_n} / $x->{_d}\n"; |
271 | $x; |
272 | } |
273 | |
274 | ############################################################################## |
275 | # special values |
276 | |
277 | sub _bnan |
278 | { |
279 | # used by parent class bone() to initialize number to 1 |
280 | my $self = shift; |
281 | $self->{_n} = Math::BigInt->bzero(); |
282 | $self->{_d} = Math::BigInt->bzero(); |
283 | } |
284 | |
285 | sub _binf |
286 | { |
287 | # used by parent class bone() to initialize number to 1 |
288 | my $self = shift; |
289 | $self->{_n} = Math::BigInt->bzero(); |
290 | $self->{_d} = Math::BigInt->bzero(); |
291 | } |
292 | |
293 | sub _bone |
294 | { |
295 | # used by parent class bone() to initialize number to 1 |
296 | my $self = shift; |
297 | $self->{_n} = Math::BigInt->bone(); |
298 | $self->{_d} = Math::BigInt->bone(); |
299 | } |
300 | |
301 | sub _bzero |
302 | { |
303 | # used by parent class bone() to initialize number to 1 |
304 | my $self = shift; |
305 | $self->{_n} = Math::BigInt->bzero(); |
306 | $self->{_d} = Math::BigInt->bone(); |
307 | } |
308 | |
309 | ############################################################################## |
310 | # mul/add/div etc |
311 | |
312 | sub badd |
313 | { |
bce7c187 |
314 | # add two rationals |
184f15d5 |
315 | my ($self,$x,$y,$a,$p,$r) = objectify(2,@_); |
316 | |
6de7f0cc |
317 | # print "rat badd\n"; |
318 | # print "ref($x) = ",ref($x),"\n"; |
319 | # print "ref($y) = ",ref($y),"\n"; |
320 | $x = $self->new($x) unless $x->isa($self); |
321 | $y = $self->new($y) unless $y->isa($self); |
322 | # print "ref($x) = ",ref($x),"\n"; |
323 | # print "ref($y) = ",ref($y),"\n"; |
184f15d5 |
324 | |
8f675a64 |
325 | return $x->bnan() if ($x->{sign} eq 'NaN' || $y->{sign} eq 'NaN'); |
184f15d5 |
326 | |
327 | # 1 1 gcd(3,4) = 1 1*3 + 1*4 7 |
328 | # - + - = --------- = -- |
329 | # 4 3 4*3 12 |
330 | |
331 | my $gcd = $x->{_d}->bgcd($y->{_d}); |
332 | |
333 | my $aa = $x->{_d}->copy(); |
334 | my $bb = $y->{_d}->copy(); |
335 | if ($gcd->is_one()) |
336 | { |
337 | $bb->bdiv($gcd); $aa->bdiv($gcd); |
338 | } |
339 | $x->{_n}->bmul($bb); $x->{_n}->{sign} = $x->{sign}; |
340 | my $m = $y->{_n}->copy()->bmul($aa); |
341 | $m->{sign} = $y->{sign}; # 2/1 - 2/1 |
342 | $x->{_n}->badd($m); |
343 | |
344 | $x->{_d}->bmul($y->{_d}); |
345 | |
346 | # calculate new sign |
347 | $x->{sign} = $x->{_n}->{sign}; $x->{_n}->{sign} = '+'; |
348 | |
349 | $x->bnorm()->round($a,$p,$r); |
350 | } |
351 | |
352 | sub bsub |
353 | { |
bce7c187 |
354 | # subtract two rationals |
184f15d5 |
355 | my ($self,$x,$y,$a,$p,$r) = objectify(2,@_); |
356 | |
8f675a64 |
357 | $x = $class->new($x) unless $x->isa($class); |
358 | $y = $class->new($y) unless $y->isa($class); |
359 | |
184f15d5 |
360 | return $x->bnan() if ($x->{sign} eq 'NaN' || $y->{sign} eq 'NaN'); |
361 | # TODO: inf handling |
362 | |
184f15d5 |
363 | # 1 1 gcd(3,4) = 1 1*3 + 1*4 7 |
364 | # - + - = --------- = -- |
365 | # 4 3 4*3 12 |
366 | |
367 | my $gcd = $x->{_d}->bgcd($y->{_d}); |
368 | |
369 | my $aa = $x->{_d}->copy(); |
370 | my $bb = $y->{_d}->copy(); |
371 | if ($gcd->is_one()) |
372 | { |
373 | $bb->bdiv($gcd); $aa->bdiv($gcd); |
374 | } |
375 | $x->{_n}->bmul($bb); $x->{_n}->{sign} = $x->{sign}; |
376 | my $m = $y->{_n}->copy()->bmul($aa); |
377 | $m->{sign} = $y->{sign}; # 2/1 - 2/1 |
378 | $x->{_n}->bsub($m); |
379 | |
380 | $x->{_d}->bmul($y->{_d}); |
381 | |
382 | # calculate new sign |
383 | $x->{sign} = $x->{_n}->{sign}; $x->{_n}->{sign} = '+'; |
384 | |
385 | $x->bnorm()->round($a,$p,$r); |
386 | } |
387 | |
388 | sub bmul |
389 | { |
bce7c187 |
390 | # multiply two rationals |
184f15d5 |
391 | my ($self,$x,$y,$a,$p,$r) = objectify(2,@_); |
392 | |
8f675a64 |
393 | $x = $class->new($x) unless $x->isa($class); |
394 | $y = $class->new($y) unless $y->isa($class); |
395 | |
184f15d5 |
396 | return $x->bnan() if ($x->{sign} eq 'NaN' || $y->{sign} eq 'NaN'); |
397 | |
398 | # inf handling |
399 | if (($x->{sign} =~ /^[+-]inf$/) || ($y->{sign} =~ /^[+-]inf$/)) |
400 | { |
401 | return $x->bnan() if $x->is_zero() || $y->is_zero(); |
402 | # result will always be +-inf: |
403 | # +inf * +/+inf => +inf, -inf * -/-inf => +inf |
404 | # +inf * -/-inf => -inf, -inf * +/+inf => -inf |
405 | return $x->binf() if ($x->{sign} =~ /^\+/ && $y->{sign} =~ /^\+/); |
406 | return $x->binf() if ($x->{sign} =~ /^-/ && $y->{sign} =~ /^-/); |
407 | return $x->binf('-'); |
408 | } |
409 | |
410 | # x== 0 # also: or y == 1 or y == -1 |
411 | return wantarray ? ($x,$self->bzero()) : $x if $x->is_zero(); |
412 | |
184f15d5 |
413 | # According to Knuth, this can be optimized by doingtwice gcd (for d and n) |
414 | # and reducing in one step) |
415 | |
416 | # 1 1 2 1 |
417 | # - * - = - = - |
418 | # 4 3 12 6 |
419 | $x->{_n}->bmul($y->{_n}); |
420 | $x->{_d}->bmul($y->{_d}); |
421 | |
422 | # compute new sign |
423 | $x->{sign} = $x->{sign} eq $y->{sign} ? '+' : '-'; |
424 | |
425 | $x->bnorm()->round($a,$p,$r); |
426 | } |
427 | |
428 | sub bdiv |
429 | { |
430 | # (dividend: BRAT or num_str, divisor: BRAT or num_str) return |
431 | # (BRAT,BRAT) (quo,rem) or BRAT (only rem) |
432 | my ($self,$x,$y,$a,$p,$r) = objectify(2,@_); |
433 | |
8f675a64 |
434 | $x = $class->new($x) unless $x->isa($class); |
435 | $y = $class->new($y) unless $y->isa($class); |
436 | |
6de7f0cc |
437 | # print "rat bdiv $x $y ",ref($x)," ",ref($y),"\n"; |
184f15d5 |
438 | return $self->_div_inf($x,$y) |
439 | if (($x->{sign} !~ /^[+-]$/) || ($y->{sign} !~ /^[+-]$/) || $y->is_zero()); |
440 | |
441 | # x== 0 # also: or y == 1 or y == -1 |
442 | return wantarray ? ($x,$self->bzero()) : $x if $x->is_zero(); |
443 | |
444 | # TODO: list context, upgrade |
445 | |
184f15d5 |
446 | # 1 1 1 3 |
447 | # - / - == - * - |
448 | # 4 3 4 1 |
449 | $x->{_n}->bmul($y->{_d}); |
450 | $x->{_d}->bmul($y->{_n}); |
451 | |
6de7f0cc |
452 | # print "result $x->{_d} $x->{_n}\n"; |
184f15d5 |
453 | # compute new sign |
454 | $x->{sign} = $x->{sign} eq $y->{sign} ? '+' : '-'; |
455 | |
456 | $x->bnorm()->round($a,$p,$r); |
6de7f0cc |
457 | # print "result $x->{_d} $x->{_n}\n"; |
458 | $x; |
184f15d5 |
459 | } |
460 | |
461 | ############################################################################## |
462 | # is_foo methods (the rest is inherited) |
463 | |
464 | sub is_int |
465 | { |
466 | # return true if arg (BRAT or num_str) is an integer |
467 | my ($self,$x) = ref($_[0]) ? (ref($_[0]),$_[0]) : objectify(1,@_); |
468 | |
469 | return 1 if ($x->{sign} =~ /^[+-]$/) && # NaN and +-inf aren't |
470 | $x->{_d}->is_one(); # 1e-1 => no integer |
471 | 0; |
472 | } |
473 | |
474 | sub is_zero |
475 | { |
476 | # return true if arg (BRAT or num_str) is zero |
477 | my ($self,$x) = ref($_[0]) ? (ref($_[0]),$_[0]) : objectify(1,@_); |
478 | |
479 | return 1 if $x->{sign} eq '+' && $x->{_n}->is_zero(); |
480 | 0; |
481 | } |
482 | |
483 | sub is_one |
484 | { |
485 | # return true if arg (BRAT or num_str) is +1 or -1 if signis given |
486 | my ($self,$x) = ref($_[0]) ? (ref($_[0]),$_[0]) : objectify(1,@_); |
487 | |
488 | my $sign = shift || ''; $sign = '+' if $sign ne '-'; |
489 | return 1 |
490 | if ($x->{sign} eq $sign && $x->{_n}->is_one() && $x->{_d}->is_one()); |
491 | 0; |
492 | } |
493 | |
494 | sub is_odd |
495 | { |
496 | # return true if arg (BFLOAT or num_str) is odd or false if even |
497 | my ($self,$x) = ref($_[0]) ? (ref($_[0]),$_[0]) : objectify(1,@_); |
498 | |
499 | return 1 if ($x->{sign} =~ /^[+-]$/) && # NaN & +-inf aren't |
500 | ($x->{_d}->is_one() && $x->{_n}->is_odd()); # x/2 is not, but 3/1 |
501 | 0; |
502 | } |
503 | |
504 | sub is_even |
505 | { |
506 | # return true if arg (BINT or num_str) is even or false if odd |
507 | my ($self,$x) = ref($_[0]) ? (ref($_[0]),$_[0]) : objectify(1,@_); |
508 | |
509 | return 0 if $x->{sign} !~ /^[+-]$/; # NaN & +-inf aren't |
510 | return 1 if ($x->{_d}->is_one() # x/3 is never |
511 | && $x->{_n}->is_even()); # but 4/1 is |
512 | 0; |
513 | } |
514 | |
515 | BEGIN |
516 | { |
517 | *objectify = \&Math::BigInt::objectify; |
518 | } |
519 | |
520 | ############################################################################## |
521 | # parts() and friends |
522 | |
523 | sub numerator |
524 | { |
525 | my ($self,$x) = ref($_[0]) ? (ref($_[0]),$_[0]) : objectify(1,@_); |
526 | |
527 | my $n = $x->{_n}->copy(); $n->{sign} = $x->{sign}; |
528 | $n; |
529 | } |
530 | |
531 | sub denominator |
532 | { |
533 | my ($self,$x) = ref($_[0]) ? (ref($_[0]),$_[0]) : objectify(1,@_); |
534 | |
535 | $x->{_d}->copy(); |
536 | } |
537 | |
538 | sub parts |
539 | { |
540 | my ($self,$x) = ref($_[0]) ? (ref($_[0]),$_[0]) : objectify(1,@_); |
541 | |
542 | my $n = $x->{_n}->copy(); |
543 | $n->{sign} = $x->{sign}; |
544 | return ($x->{_n}->copy(),$x->{_d}->copy()); |
545 | } |
546 | |
547 | sub length |
548 | { |
549 | return 0; |
550 | } |
551 | |
552 | sub digit |
553 | { |
554 | return 0; |
555 | } |
556 | |
557 | ############################################################################## |
558 | # special calc routines |
559 | |
560 | sub bceil |
561 | { |
562 | my ($self,$x) = ref($_[0]) ? (ref($_[0]),$_[0]) : objectify(1,@_); |
563 | |
564 | return $x unless $x->{sign} =~ /^[+-]$/; |
565 | return $x if $x->{_d}->is_one(); # 22/1 => 22, 0/1 => 0 |
566 | |
567 | $x->{_n}->bdiv($x->{_d}); # 22/7 => 3/1 |
568 | $x->{_d}->bone(); |
569 | $x->{_n}->binc() if $x->{sign} eq '+'; # +22/7 => 4/1 |
570 | $x; |
571 | } |
572 | |
573 | sub bfloor |
574 | { |
575 | my ($self,$x) = ref($_[0]) ? (ref($_[0]),$_[0]) : objectify(1,@_); |
576 | |
577 | return $x unless $x->{sign} =~ /^[+-]$/; |
578 | return $x if $x->{_d}->is_one(); # 22/1 => 22, 0/1 => 0 |
579 | |
580 | $x->{_n}->bdiv($x->{_d}); # 22/7 => 3/1 |
581 | $x->{_d}->bone(); |
582 | $x->{_n}->binc() if $x->{sign} eq '-'; # -22/7 => -4/1 |
583 | $x; |
584 | } |
585 | |
586 | sub bfac |
587 | { |
588 | return Math::BigRat->bnan(); |
589 | } |
590 | |
591 | sub bpow |
592 | { |
593 | my ($self,$x,$y,@r) = objectify(2,@_); |
594 | |
595 | return $x if $x->{sign} =~ /^[+-]inf$/; # -inf/+inf ** x |
596 | return $x->bnan() if $x->{sign} eq $nan || $y->{sign} eq $nan; |
597 | return $x->bone(@r) if $y->is_zero(); |
598 | return $x->round(@r) if $x->is_one() || $y->is_one(); |
599 | if ($x->{sign} eq '-' && $x->{_n}->is_one() && $x->{_d}->is_one()) |
600 | { |
601 | # if $x == -1 and odd/even y => +1/-1 |
602 | return $y->is_odd() ? $x->round(@r) : $x->babs()->round(@r); |
603 | # my Casio FX-5500L has a bug here: -1 ** 2 is -1, but -1 * -1 is 1; |
604 | } |
605 | # 1 ** -y => 1 / (1 ** |y|) |
606 | # so do test for negative $y after above's clause |
607 | # return $x->bnan() if $y->{sign} eq '-'; |
608 | return $x->round(@r) if $x->is_zero(); # 0**y => 0 (if not y <= 0) |
609 | |
610 | my $pow2 = $self->__one(); |
611 | my $y1 = Math::BigInt->new($y->{_n}/$y->{_d})->babs(); |
612 | my $two = Math::BigInt->new(2); |
613 | while (!$y1->is_one()) |
614 | { |
615 | print "at $y1 (= $x)\n"; |
616 | $pow2->bmul($x) if $y1->is_odd(); |
617 | $y1->bdiv($two); |
618 | $x->bmul($x); |
619 | } |
620 | $x->bmul($pow2) unless $pow2->is_one(); |
621 | # n ** -x => 1/n ** x |
622 | ($x->{_d},$x->{_n}) = ($x->{_n},$x->{_d}) if $y->{sign} eq '-'; |
623 | $x; |
624 | #$x->round(@r); |
625 | } |
626 | |
627 | sub blog |
628 | { |
629 | return Math::BigRat->bnan(); |
630 | } |
631 | |
632 | sub bsqrt |
633 | { |
634 | my ($self,$x,$a,$p,$r) = ref($_[0]) ? (ref($_[0]),$_[0]) : objectify(1,@_); |
635 | |
636 | return $x->bnan() if $x->{sign} ne '+'; # inf, NaN, -1 etc |
637 | $x->{_d}->bsqrt($a,$p,$r); |
638 | $x->{_n}->bsqrt($a,$p,$r); |
639 | $x->bnorm(); |
640 | } |
641 | |
642 | sub blsft |
643 | { |
644 | my ($self,$x,$y,$b,$a,$p,$r) = objectify(3,@_); |
645 | |
646 | $x->bmul( $b->copy()->bpow($y), $a,$p,$r); |
647 | $x; |
648 | } |
649 | |
650 | sub brsft |
651 | { |
652 | my ($self,$x,$y,$b,$a,$p,$r) = objectify(2,@_); |
653 | |
654 | $x->bdiv( $b->copy()->bpow($y), $a,$p,$r); |
655 | $x; |
656 | } |
657 | |
658 | ############################################################################## |
659 | # round |
660 | |
661 | sub round |
662 | { |
663 | $_[0]; |
664 | } |
665 | |
666 | sub bround |
667 | { |
668 | $_[0]; |
669 | } |
670 | |
671 | sub bfround |
672 | { |
673 | $_[0]; |
674 | } |
675 | |
676 | ############################################################################## |
677 | # comparing |
678 | |
679 | sub bcmp |
680 | { |
681 | my ($self,$x,$y) = objectify(2,@_); |
682 | |
683 | if (($x->{sign} !~ /^[+-]$/) || ($y->{sign} !~ /^[+-]$/)) |
684 | { |
685 | # handle +-inf and NaN |
686 | return undef if (($x->{sign} eq $nan) || ($y->{sign} eq $nan)); |
687 | return 0 if $x->{sign} eq $y->{sign} && $x->{sign} =~ /^[+-]inf$/; |
688 | return +1 if $x->{sign} eq '+inf'; |
689 | return -1 if $x->{sign} eq '-inf'; |
690 | return -1 if $y->{sign} eq '+inf'; |
691 | return +1; |
692 | } |
693 | # check sign for speed first |
694 | return 1 if $x->{sign} eq '+' && $y->{sign} eq '-'; # does also 0 <=> -y |
695 | return -1 if $x->{sign} eq '-' && $y->{sign} eq '+'; # does also -x <=> 0 |
696 | |
697 | # shortcut |
698 | my $xz = $x->{_n}->is_zero(); |
699 | my $yz = $y->{_n}->is_zero(); |
700 | return 0 if $xz && $yz; # 0 <=> 0 |
701 | return -1 if $xz && $y->{sign} eq '+'; # 0 <=> +y |
702 | return 1 if $yz && $x->{sign} eq '+'; # +x <=> 0 |
703 | |
704 | my $t = $x->{_n} * $y->{_d}; $t->{sign} = $x->{sign}; |
705 | my $u = $y->{_n} * $x->{_d}; $u->{sign} = $y->{sign}; |
706 | $t->bcmp($u); |
707 | } |
708 | |
709 | sub bacmp |
710 | { |
711 | my ($self,$x,$y) = objectify(2,@_); |
712 | |
713 | if (($x->{sign} !~ /^[+-]$/) || ($y->{sign} !~ /^[+-]$/)) |
714 | { |
715 | # handle +-inf and NaN |
716 | return undef if (($x->{sign} eq $nan) || ($y->{sign} eq $nan)); |
717 | return 0 if $x->{sign} =~ /^[+-]inf$/ && $y->{sign} =~ /^[+-]inf$/; |
718 | return +1; # inf is always bigger |
719 | } |
720 | |
721 | my $t = $x->{_n} * $y->{_d}; |
722 | my $u = $y->{_n} * $x->{_d}; |
723 | $t->bacmp($u); |
724 | } |
725 | |
726 | ############################################################################## |
727 | # output conversation |
728 | |
729 | sub as_number |
730 | { |
731 | my ($self,$x) = ref($_[0]) ? (ref($_[0]),$_[0]) : objectify(1,@_); |
732 | |
733 | return $x if $x->{sign} !~ /^[+-]$/; # NaN, inf etc |
734 | my $t = $x->{_n}->copy()->bdiv($x->{_d}); # 22/7 => 3 |
735 | $t->{sign} = $x->{sign}; |
736 | $t; |
737 | } |
738 | |
6de7f0cc |
739 | sub import |
740 | { |
741 | my $self = shift; |
742 | my $l = scalar @_; |
743 | my $lib = ''; my @a; |
744 | for ( my $i = 0; $i < $l ; $i++) |
745 | { |
746 | # print "at $_[$i] (",$_[$i+1]||'undef',")\n"; |
747 | if ( $_[$i] eq ':constant' ) |
748 | { |
749 | # this rest causes overlord er load to step in |
750 | # print "overload @_\n"; |
751 | overload::constant float => sub { $self->new(shift); }; |
752 | } |
753 | # elsif ($_[$i] eq 'upgrade') |
754 | # { |
755 | # # this causes upgrading |
756 | # $upgrade = $_[$i+1]; # or undef to disable |
757 | # $i++; |
758 | # } |
759 | elsif ($_[$i] eq 'downgrade') |
760 | { |
761 | # this causes downgrading |
762 | $downgrade = $_[$i+1]; # or undef to disable |
763 | $i++; |
764 | } |
765 | elsif ($_[$i] eq 'lib') |
766 | { |
767 | $lib = $_[$i+1] || ''; # default Calc |
768 | $i++; |
769 | } |
770 | elsif ($_[$i] eq 'with') |
771 | { |
772 | $MBI = $_[$i+1] || 'Math::BigInt'; # default Math::BigInt |
773 | $i++; |
774 | } |
775 | else |
776 | { |
777 | push @a, $_[$i]; |
778 | } |
779 | } |
780 | # let use Math::BigInt lib => 'GMP'; use Math::BigFloat; still work |
781 | my $mbilib = eval { Math::BigInt->config()->{lib} }; |
782 | if ((defined $mbilib) && ($MBI eq 'Math::BigInt')) |
783 | { |
784 | # MBI already loaded |
785 | $MBI->import('lib',"$lib,$mbilib", 'objectify'); |
786 | } |
787 | else |
788 | { |
789 | # MBI not loaded, or with ne "Math::BigInt" |
790 | $lib .= ",$mbilib" if defined $mbilib; |
791 | |
792 | # my @parts = split /::/, $MBI; # Math::BigInt => Math BigInt |
793 | # my $file = pop @parts; $file .= '.pm'; # BigInt => BigInt.pm |
794 | # $file = File::Spec->catfile (@parts, $file); |
795 | |
796 | if ($] < 5.006) |
797 | { |
798 | # Perl < 5.6.0 dies with "out of memory!" when eval() and ':constant' is |
799 | # used in the same script, or eval inside import(). |
800 | my @parts = split /::/, $MBI; # Math::BigInt => Math BigInt |
801 | my $file = pop @parts; $file .= '.pm'; # BigInt => BigInt.pm |
802 | $file = File::Spec->catfile (@parts, $file); |
803 | eval { require $file; $MBI->import( lib => '$lib', 'objectify' ); } |
804 | } |
805 | else |
806 | { |
807 | my $rc = "use $MBI lib => '$lib', 'objectify';"; |
808 | eval $rc; |
809 | } |
810 | } |
811 | die ("Couldn't load $MBI: $! $@") if $@; |
812 | |
813 | # any non :constant stuff is handled by our parent, Exporter |
814 | # even if @_ is empty, to give it a chance |
815 | $self->SUPER::import(@a); # for subclasses |
816 | $self->export_to_level(1,$self,@a); # need this, too |
817 | } |
184f15d5 |
818 | |
819 | 1; |
820 | |
821 | __END__ |
822 | |
823 | =head1 NAME |
824 | |
bce7c187 |
825 | Math::BigRat - arbitrarily big rationals |
184f15d5 |
826 | |
827 | =head1 SYNOPSIS |
828 | |
829 | use Math::BigRat; |
830 | |
831 | $x = Math::BigRat->new('3/7'); |
832 | |
833 | print $x->bstr(),"\n"; |
834 | |
835 | =head1 DESCRIPTION |
836 | |
837 | This is just a placeholder until the real thing is up and running. Watch this |
838 | space... |
839 | |
840 | =head2 MATH LIBRARY |
841 | |
842 | Math with the numbers is done (by default) by a module called |
843 | Math::BigInt::Calc. This is equivalent to saying: |
844 | |
845 | use Math::BigRat lib => 'Calc'; |
846 | |
847 | You can change this by using: |
848 | |
849 | use Math::BigRat lib => 'BitVect'; |
850 | |
851 | The following would first try to find Math::BigInt::Foo, then |
852 | Math::BigInt::Bar, and when this also fails, revert to Math::BigInt::Calc: |
853 | |
854 | use Math::BigRat lib => 'Foo,Math::BigInt::Bar'; |
855 | |
856 | Calc.pm uses as internal format an array of elements of some decimal base |
857 | (usually 1e7, but this might be differen for some systems) with the least |
858 | significant digit first, while BitVect.pm uses a bit vector of base 2, most |
859 | significant bit first. Other modules might use even different means of |
860 | representing the numbers. See the respective module documentation for further |
861 | details. |
862 | |
863 | =head1 METHODS |
864 | |
6de7f0cc |
865 | Any method not listed here is dervied from Math::BigFloat (or |
866 | Math::BigInt), so make sure you check these two modules for further |
867 | information. |
868 | |
869 | =head2 new() |
184f15d5 |
870 | |
871 | $x = Math::BigRat->new('1/3'); |
872 | |
873 | Create a new Math::BigRat object. Input can come in various forms: |
874 | |
875 | $x = Math::BigRat->new('1/3'); # simple string |
876 | $x = Math::BigRat->new('1 / 3'); # spaced |
877 | $x = Math::BigRat->new('1 / 0.1'); # w/ floats |
878 | $x = Math::BigRat->new(Math::BigInt->new(3)); # BigInt |
879 | $x = Math::BigRat->new(Math::BigFloat->new('3.1')); # BigFloat |
6de7f0cc |
880 | $x = Math::BigRat->new(Math::BigInt::Lite->new('2')); # BigLite |
184f15d5 |
881 | |
6de7f0cc |
882 | =head2 numerator() |
184f15d5 |
883 | |
884 | $n = $x->numerator(); |
885 | |
886 | Returns a copy of the numerator (the part above the line) as signed BigInt. |
887 | |
6de7f0cc |
888 | =head2 denominator() |
184f15d5 |
889 | |
890 | $d = $x->denominator(); |
891 | |
892 | Returns a copy of the denominator (the part under the line) as positive BigInt. |
893 | |
6de7f0cc |
894 | =head2 parts() |
184f15d5 |
895 | |
896 | ($n,$d) = $x->parts(); |
897 | |
898 | Return a list consisting of (signed) numerator and (unsigned) denominator as |
899 | BigInts. |
900 | |
6de7f0cc |
901 | =head2 as_number() |
902 | |
903 | Returns a copy of the object as BigInt by truncating it to integer. |
904 | |
905 | =head2 bfac()/blog() |
906 | |
907 | Are not yet implemented. |
908 | |
909 | =head2 bround()/round()/bfround() |
910 | |
911 | Are not yet implemented. |
912 | |
913 | |
184f15d5 |
914 | =head1 BUGS |
915 | |
6de7f0cc |
916 | =over 2 |
917 | |
918 | =item perl -Mbigrat -le 'print 1 + 2/3' |
919 | |
920 | This produces wrongly NaN. It is unclear why. The following variants all work: |
921 | |
922 | perl -Mbigrat -le 'print 1/3 + 2/3' |
923 | perl -Mbigrat -le 'print 1/3 + 2' |
924 | |
925 | This also does not work: |
926 | |
927 | perl -Mbigrat -le 'print 1+3+1/2' |
928 | |
929 | =back |
930 | |
931 | Please see also L<Math::BigInt>. |
184f15d5 |
932 | |
933 | =head1 LICENSE |
934 | |
935 | This program is free software; you may redistribute it and/or modify it under |
936 | the same terms as Perl itself. |
937 | |
938 | =head1 SEE ALSO |
939 | |
940 | L<Math::BigFloat> and L<Math::Big> as well as L<Math::BigInt::BitVect>, |
941 | L<Math::BigInt::Pari> and L<Math::BigInt::GMP>. |
942 | |
943 | The package at |
944 | L<http://search.cpan.org/search?mode=module&query=Math%3A%3ABigRat> may |
945 | contain more documentation and examples as well as testcases. |
946 | |
947 | =head1 AUTHORS |
948 | |
949 | (C) by Tels L<http://bloodgate.com/> 2001-2002. |
950 | |
951 | =cut |