allow distinct prefix for versioned executables
[p5sagit/p5-mst-13.2.git] / lib / Math / BigFloat.pm
1 package Math::BigFloat;
2
3 use Math::BigInt;
4
5 use Exporter;  # just for use to be happy
6 @ISA = (Exporter);
7
8 use overload
9 '+'     =>      sub {new Math::BigFloat &fadd},
10 '-'     =>      sub {new Math::BigFloat
11                        $_[2]? fsub($_[1],${$_[0]}) : fsub(${$_[0]},$_[1])},
12 '<=>'   =>      sub {$_[2]? fcmp($_[1],${$_[0]}) : fcmp(${$_[0]},$_[1])},
13 'cmp'   =>      sub {$_[2]? ($_[1] cmp ${$_[0]}) : (${$_[0]} cmp $_[1])},
14 '*'     =>      sub {new Math::BigFloat &fmul},
15 '/'     =>      sub {new Math::BigFloat 
16                        $_[2]? scalar fdiv($_[1],${$_[0]}) :
17                          scalar fdiv(${$_[0]},$_[1])},
18 'neg'   =>      sub {new Math::BigFloat &fneg},
19 'abs'   =>      sub {new Math::BigFloat &fabs},
20
21 qw(
22 ""      stringify
23 0+      numify)                 # Order of arguments unsignificant
24 ;
25
26 sub new {
27   my ($class) = shift;
28   my ($foo) = fnorm(shift);
29   bless \$foo, $class;
30 }
31
32 sub numify { 0 + "${$_[0]}" }   # Not needed, additional overhead
33                                 # comparing to direct compilation based on
34                                 # stringify
35 sub stringify {
36     my $n = ${$_[0]};
37
38     my $minus = ($n =~ s/^([+-])// && $1 eq '-');
39     $n =~ s/E//;
40
41     $n =~ s/([-+]\d+)$//;
42
43     my $e = $1;
44     my $ln = length($n);
45
46     if ($e > 0) {
47         $n .= "0" x $e . '.';
48     } elsif (abs($e) < $ln) {
49         substr($n, $ln + $e, 0) = '.';
50     } else {
51         $n = '.' . ("0" x (abs($e) - $ln)) . $n;
52     }
53     $n = "-$n" if $minus;
54
55     # 1 while $n =~ s/(.*\d)(\d\d\d)/$1,$2/;
56
57     return $n;
58 }
59
60 $div_scale = 40;
61
62 # Rounding modes one of 'even', 'odd', '+inf', '-inf', 'zero' or 'trunc'.
63
64 $rnd_mode = 'even';
65
66 sub fadd; sub fsub; sub fmul; sub fdiv;
67 sub fneg; sub fabs; sub fcmp;
68 sub fround; sub ffround;
69 sub fnorm; sub fsqrt;
70
71 # Convert a number to canonical string form.
72 #   Takes something that looks like a number and converts it to
73 #   the form /^[+-]\d+E[+-]\d+$/.
74 sub fnorm { #(string) return fnum_str
75     local($_) = @_;
76     s/\s+//g;                               # strip white space
77     if (/^([+-]?)(\d*)(\.(\d*))?([Ee]([+-]?\d+))?$/ && "$2$4" ne '') {
78         &norm(($1 ? "$1$2$4" : "+$2$4"),(($4 ne '') ? $6-length($4) : $6));
79     } else {
80         'NaN';
81     }
82 }
83
84 # normalize number -- for internal use
85 sub norm { #(mantissa, exponent) return fnum_str
86     local($_, $exp) = @_;
87     if ($_ eq 'NaN') {
88         'NaN';
89     } else {
90         s/^([+-])0+/$1/;                        # strip leading zeros
91         if (length($_) == 1) {
92             '+0E+0';
93         } else {
94             $exp += length($1) if (s/(0+)$//);  # strip trailing zeros
95             sprintf("%sE%+ld", $_, $exp);
96         }
97     }
98 }
99
100 # negation
101 sub fneg { #(fnum_str) return fnum_str
102     local($_) = fnorm($_[$[]);
103     vec($_,0,8) ^= ord('+') ^ ord('-') unless $_ eq '+0E+0'; # flip sign
104     s/^H/N/;
105     $_;
106 }
107
108 # absolute value
109 sub fabs { #(fnum_str) return fnum_str
110     local($_) = fnorm($_[$[]);
111     s/^-/+/;                                   # mash sign
112     $_;
113 }
114
115 # multiplication
116 sub fmul { #(fnum_str, fnum_str) return fnum_str
117     local($x,$y) = (fnorm($_[$[]),fnorm($_[$[+1]));
118     if ($x eq 'NaN' || $y eq 'NaN') {
119         'NaN';
120     } else {
121         local($xm,$xe) = split('E',$x);
122         local($ym,$ye) = split('E',$y);
123         &norm(Math::BigInt::bmul($xm,$ym),$xe+$ye);
124     }
125 }
126
127 # addition
128 sub fadd { #(fnum_str, fnum_str) return fnum_str
129     local($x,$y) = (fnorm($_[$[]),fnorm($_[$[+1]));
130     if ($x eq 'NaN' || $y eq 'NaN') {
131         'NaN';
132     } else {
133         local($xm,$xe) = split('E',$x);
134         local($ym,$ye) = split('E',$y);
135         ($xm,$xe,$ym,$ye) = ($ym,$ye,$xm,$xe) if ($xe < $ye);
136         &norm(Math::BigInt::badd($ym,$xm.('0' x ($xe-$ye))),$ye);
137     }
138 }
139
140 # subtraction
141 sub fsub { #(fnum_str, fnum_str) return fnum_str
142     fadd($_[$[],fneg($_[$[+1]));    
143 }
144
145 # division
146 #   args are dividend, divisor, scale (optional)
147 #   result has at most max(scale, length(dividend), length(divisor)) digits
148 sub fdiv #(fnum_str, fnum_str[,scale]) return fnum_str
149 {
150     local($x,$y,$scale) = (fnorm($_[$[]),fnorm($_[$[+1]),$_[$[+2]);
151     if ($x eq 'NaN' || $y eq 'NaN' || $y eq '+0E+0') {
152         'NaN';
153     } else {
154         local($xm,$xe) = split('E',$x);
155         local($ym,$ye) = split('E',$y);
156         $scale = $div_scale if (!$scale);
157         $scale = length($xm)-1 if (length($xm)-1 > $scale);
158         $scale = length($ym)-1 if (length($ym)-1 > $scale);
159         $scale = $scale + length($ym) - length($xm);
160         &norm(&round(Math::BigInt::bdiv($xm.('0' x $scale),$ym),
161                     Math::BigInt::babs($ym)),
162             $xe-$ye-$scale);
163     }
164 }
165
166 # round int $q based on fraction $r/$base using $rnd_mode
167 sub round { #(int_str, int_str, int_str) return int_str
168     local($q,$r,$base) = @_;
169     if ($q eq 'NaN' || $r eq 'NaN') {
170         'NaN';
171     } elsif ($rnd_mode eq 'trunc') {
172         $q;                         # just truncate
173     } else {
174         local($cmp) = Math::BigInt::bcmp(Math::BigInt::bmul($r,'+2'),$base);
175         if ( $cmp < 0 ||
176                  ($cmp == 0 &&
177                   ( $rnd_mode eq 'zero'                             ||
178                    ($rnd_mode eq '-inf' && (substr($q,$[,1) eq '+')) ||
179                    ($rnd_mode eq '+inf' && (substr($q,$[,1) eq '-')) ||
180                    ($rnd_mode eq 'even' && $q =~ /[24680]$/)        ||
181                    ($rnd_mode eq 'odd'  && $q =~ /[13579]$/)        )) ) {
182             $q;                     # round down
183         } else {
184             Math::BigInt::badd($q, ((substr($q,$[,1) eq '-') ? '-1' : '+1'));
185                                     # round up
186         }
187     }
188 }
189
190 # round the mantissa of $x to $scale digits
191 sub fround { #(fnum_str, scale) return fnum_str
192     local($x,$scale) = (fnorm($_[$[]),$_[$[+1]);
193     if ($x eq 'NaN' || $scale <= 0) {
194         $x;
195     } else {
196         local($xm,$xe) = split('E',$x);
197         if (length($xm)-1 <= $scale) {
198             $x;
199         } else {
200             &norm(&round(substr($xm,$[,$scale+1),
201                          "+0".substr($xm,$[+$scale+1,1),"+10"),
202                   $xe+length($xm)-$scale-1);
203         }
204     }
205 }
206
207 # round $x at the 10 to the $scale digit place
208 sub ffround { #(fnum_str, scale) return fnum_str
209     local($x,$scale) = (fnorm($_[$[]),$_[$[+1]);
210     if ($x eq 'NaN') {
211         'NaN';
212     } else {
213         local($xm,$xe) = split('E',$x);
214         if ($xe >= $scale) {
215             $x;
216         } else {
217             $xe = length($xm)+$xe-$scale;
218             if ($xe < 1) {
219                 '+0E+0';
220             } elsif ($xe == 1) {
221                 # The first substr preserves the sign, passing a non-
222                 # normalized "-0" to &round when rounding -0.006 (for
223                 # example), purely so &round won't lose the sign.
224                 &norm(&round(substr($xm,$[,1).'0',
225                       "+0".substr($xm,$[+1,1),"+10"), $scale);
226             } else {
227                 &norm(&round(substr($xm,$[,$xe),
228                       "+0".substr($xm,$[+$xe,1),"+10"), $scale);
229             }
230         }
231     }
232 }
233     
234 # compare 2 values returns one of undef, <0, =0, >0
235 #   returns undef if either or both input value are not numbers
236 sub fcmp #(fnum_str, fnum_str) return cond_code
237 {
238     local($x, $y) = (fnorm($_[$[]),fnorm($_[$[+1]));
239     if ($x eq "NaN" || $y eq "NaN") {
240         undef;
241     } else {
242         ord($y) <=> ord($x)
243         ||
244         (  local($xm,$xe,$ym,$ye) = split('E', $x."E$y"),
245              (($xe <=> $ye) * (substr($x,$[,1).'1')
246              || Math::BigInt::cmp($xm,$ym))
247         );
248     }
249 }
250
251 # square root by Newtons method.
252 sub fsqrt { #(fnum_str[, scale]) return fnum_str
253     local($x, $scale) = (fnorm($_[$[]), $_[$[+1]);
254     if ($x eq 'NaN' || $x =~ /^-/) {
255         'NaN';
256     } elsif ($x eq '+0E+0') {
257         '+0E+0';
258     } else {
259         local($xm, $xe) = split('E',$x);
260         $scale = $div_scale if (!$scale);
261         $scale = length($xm)-1 if ($scale < length($xm)-1);
262         local($gs, $guess) = (1, sprintf("1E%+d", (length($xm)+$xe-1)/2));
263         while ($gs < 2*$scale) {
264             $guess = fmul(fadd($guess,fdiv($x,$guess,$gs*2)),".5");
265             $gs *= 2;
266         }
267         new Math::BigFloat &fround($guess, $scale);
268     }
269 }
270
271 1;
272 __END__
273
274 =head1 NAME
275
276 Math::BigFloat - Arbitrary length float math package
277
278 =head1 SYNOPSIS
279
280   use Math::BigFloat;
281   $f = Math::BigFloat->new($string);
282
283   $f->fadd(NSTR) return NSTR            addition
284   $f->fsub(NSTR) return NSTR            subtraction
285   $f->fmul(NSTR) return NSTR            multiplication
286   $f->fdiv(NSTR[,SCALE]) returns NSTR   division to SCALE places
287   $f->fneg() return NSTR                negation
288   $f->fabs() return NSTR                absolute value
289   $f->fcmp(NSTR) return CODE            compare undef,<0,=0,>0
290   $f->fround(SCALE) return NSTR         round to SCALE digits
291   $f->ffround(SCALE) return NSTR        round at SCALEth place
292   $f->fnorm() return (NSTR)             normalize
293   $f->fsqrt([SCALE]) return NSTR        sqrt to SCALE places
294
295 =head1 DESCRIPTION
296
297 All basic math operations are overloaded if you declare your big
298 floats as
299
300     $float = new Math::BigFloat "2.123123123123123123123123123123123";
301
302 =over 2
303
304 =item number format
305
306 canonical strings have the form /[+-]\d+E[+-]\d+/ .  Input values can
307 have embedded whitespace.
308
309 =item Error returns 'NaN'
310
311 An input parameter was "Not a Number" or divide by zero or sqrt of
312 negative number.
313
314 =item Division is computed to 
315
316 C<max($Math::BigFloat::div_scale,length(dividend)+length(divisor))>
317 digits by default.
318 Also used for default sqrt scale.
319
320 =item Rounding is performed
321
322 according to the value of
323 C<$Math::BigFloat::rnd_mode>:
324
325   trunc     truncate the value
326   zero      round towards 0
327   +inf      round towards +infinity (round up)
328   -inf      round towards -infinity (round down)
329   even      round to the nearest, .5 to the even digit
330   odd       round to the nearest, .5 to the odd digit
331
332 The default is C<even> rounding.
333
334 =back
335
336 =head1 BUGS
337
338 The current version of this module is a preliminary version of the
339 real thing that is currently (as of perl5.002) under development.
340
341 The printf subroutine does not use the value of
342 C<$Math::BigFloat::rnd_mode> when rounding values for printing.
343 Consequently, the way to print rounded values is
344 to specify the number of digits both as an
345 argument to C<ffround> and in the C<%f> printf string,
346 as follows:
347
348   printf "%.3f\n", $bigfloat->ffround(-3);
349
350 =head1 AUTHOR
351
352 Mark Biggar
353
354 =cut