This is a live mirror of the Perl 5 development currently hosted at https://github.com/perl/perl5
untodo the no-longer-failing todo test for rgs' patch
[perl5.git] / lib / bigfloat.pl
1 package bigfloat;
2 require "bigint.pl";
3 #
4 # This library is no longer being maintained, and is included for backward
5 # compatibility with Perl 4 programs which may require it.
6 # This legacy library is deprecated and will be removed in a future
7 # release of perl.
8 #
9 # In particular, this should not be used as an example of modern Perl
10 # programming techniques.
11 #
12 # Suggested alternative: Math::BigFloat
13
14 warn( "The 'bigfloat.pl' legacy library is deprecated and will be"
15       . " removed in the next major release of perl. Please use the"
16       . " Math::BigFloat module instead." );
17
18 # Arbitrary length float math package
19 #
20 # by Mark Biggar
21 #
22 # number format
23 #   canonical strings have the form /[+-]\d+E[+-]\d+/
24 #   Input values can have embedded whitespace
25 # Error returns
26 #   'NaN'           An input parameter was "Not a Number" or 
27 #                       divide by zero or sqrt of negative number
28 # Division is computed to 
29 #   max($div_scale,length(dividend)+length(divisor)) 
30 #   digits by default.
31 # Also used for default sqrt scale
32
33 $div_scale = 40;
34
35 # Rounding modes one of 'even', 'odd', '+inf', '-inf', 'zero' or 'trunc'.
36
37 $rnd_mode = 'even';
38
39 #   bigfloat routines
40 #
41 #   fadd(NSTR, NSTR) return NSTR            addition
42 #   fsub(NSTR, NSTR) return NSTR            subtraction
43 #   fmul(NSTR, NSTR) return NSTR            multiplication
44 #   fdiv(NSTR, NSTR[,SCALE]) returns NSTR   division to SCALE places
45 #   fneg(NSTR) return NSTR                  negation
46 #   fabs(NSTR) return NSTR                  absolute value
47 #   fcmp(NSTR,NSTR) return CODE             compare undef,<0,=0,>0
48 #   fround(NSTR, SCALE) return NSTR         round to SCALE digits
49 #   ffround(NSTR, SCALE) return NSTR        round at SCALEth place
50 #   fnorm(NSTR) return (NSTR)               normalize
51 #   fsqrt(NSTR[, SCALE]) return NSTR        sqrt to SCALE places
52 \f
53 # Convert a number to canonical string form.
54 #   Takes something that looks like a number and converts it to
55 #   the form /^[+-]\d+E[+-]\d+$/.
56 sub main'fnorm { #(string) return fnum_str
57     local($_) = @_;
58     s/\s+//g;                               # strip white space
59     if (/^([+-]?)(\d*)(\.(\d*))?([Ee]([+-]?\d+))?$/
60           && ($2 ne '' || defined($4))) {
61         my $x = defined($4) ? $4 : '';
62         &norm(($1 ? "$1$2$x" : "+$2$x"), (($x ne '') ? $6-length($x) : $6));
63     } else {
64         'NaN';
65     }
66 }
67
68 # normalize number -- for internal use
69 sub norm { #(mantissa, exponent) return fnum_str
70     local($_, $exp) = @_;
71     if ($_ eq 'NaN') {
72         'NaN';
73     } else {
74         s/^([+-])0+/$1/;                        # strip leading zeros
75         if (length($_) == 1) {
76             '+0E+0';
77         } else {
78             $exp += length($1) if (s/(0+)$//);  # strip trailing zeros
79             sprintf("%sE%+ld", $_, $exp);
80         }
81     }
82 }
83
84 # negation
85 sub main'fneg { #(fnum_str) return fnum_str
86     local($_) = &'fnorm($_[$[]);
87     vec($_,0,8) ^= ord('+') ^ ord('-') unless $_ eq '+0E+0'; # flip sign
88     if ( ord("\t") == 9 ) { # ascii
89         s/^H/N/;
90     }
91     else { # ebcdic character set
92         s/\373/N/;
93     }
94     $_;
95 }
96
97 # absolute value
98 sub main'fabs { #(fnum_str) return fnum_str
99     local($_) = &'fnorm($_[$[]);
100     s/^-/+/;                                   # mash sign
101     $_;
102 }
103
104 # multiplication
105 sub main'fmul { #(fnum_str, fnum_str) return fnum_str
106     local($x,$y) = (&'fnorm($_[$[]),&'fnorm($_[$[+1]));
107     if ($x eq 'NaN' || $y eq 'NaN') {
108         'NaN';
109     } else {
110         local($xm,$xe) = split('E',$x);
111         local($ym,$ye) = split('E',$y);
112         &norm(&'bmul($xm,$ym),$xe+$ye);
113     }
114 }
115 \f
116 # addition
117 sub main'fadd { #(fnum_str, fnum_str) return fnum_str
118     local($x,$y) = (&'fnorm($_[$[]),&'fnorm($_[$[+1]));
119     if ($x eq 'NaN' || $y eq 'NaN') {
120         'NaN';
121     } else {
122         local($xm,$xe) = split('E',$x);
123         local($ym,$ye) = split('E',$y);
124         ($xm,$xe,$ym,$ye) = ($ym,$ye,$xm,$xe) if ($xe < $ye);
125         &norm(&'badd($ym,$xm.('0' x ($xe-$ye))),$ye);
126     }
127 }
128
129 # subtraction
130 sub main'fsub { #(fnum_str, fnum_str) return fnum_str
131     &'fadd($_[$[],&'fneg($_[$[+1]));    
132 }
133
134 # division
135 #   args are dividend, divisor, scale (optional)
136 #   result has at most max(scale, length(dividend), length(divisor)) digits
137 sub main'fdiv #(fnum_str, fnum_str[,scale]) return fnum_str
138 {
139     local($x,$y,$scale) = (&'fnorm($_[$[]),&'fnorm($_[$[+1]),$_[$[+2]);
140     if ($x eq 'NaN' || $y eq 'NaN' || $y eq '+0E+0') {
141         'NaN';
142     } else {
143         local($xm,$xe) = split('E',$x);
144         local($ym,$ye) = split('E',$y);
145         $scale = $div_scale if (!$scale);
146         $scale = length($xm)-1 if (length($xm)-1 > $scale);
147         $scale = length($ym)-1 if (length($ym)-1 > $scale);
148         $scale = $scale + length($ym) - length($xm);
149         &norm(&round(&'bdiv($xm.('0' x $scale),$ym),&'babs($ym)),
150             $xe-$ye-$scale);
151     }
152 }
153 \f
154 # round int $q based on fraction $r/$base using $rnd_mode
155 sub round { #(int_str, int_str, int_str) return int_str
156     local($q,$r,$base) = @_;
157     if ($q eq 'NaN' || $r eq 'NaN') {
158         'NaN';
159     } elsif ($rnd_mode eq 'trunc') {
160         $q;                         # just truncate
161     } else {
162         local($cmp) = &'bcmp(&'bmul($r,'+2'),$base);
163         if ( $cmp < 0 ||
164                  ($cmp == 0 &&
165                   ( $rnd_mode eq 'zero'                             ||
166                    ($rnd_mode eq '-inf' && (substr($q,$[,1) eq '+')) ||
167                    ($rnd_mode eq '+inf' && (substr($q,$[,1) eq '-')) ||
168                    ($rnd_mode eq 'even' && $q =~ /[24680]$/)        ||
169                    ($rnd_mode eq 'odd'  && $q =~ /[13579]$/)        )) ) {
170             $q;                     # round down
171         } else {
172             &'badd($q, ((substr($q,$[,1) eq '-') ? '-1' : '+1'));
173                                     # round up
174         }
175     }
176 }
177
178 # round the mantissa of $x to $scale digits
179 sub main'fround { #(fnum_str, scale) return fnum_str
180     local($x,$scale) = (&'fnorm($_[$[]),$_[$[+1]);
181     if ($x eq 'NaN' || $scale <= 0) {
182         $x;
183     } else {
184         local($xm,$xe) = split('E',$x);
185         if (length($xm)-1 <= $scale) {
186             $x;
187         } else {
188             &norm(&round(substr($xm,$[,$scale+1),
189                          "+0".substr($xm,$[+$scale+1,1),"+10"),
190                   $xe+length($xm)-$scale-1);
191         }
192     }
193 }
194 \f
195 # round $x at the 10 to the $scale digit place
196 sub main'ffround { #(fnum_str, scale) return fnum_str
197     local($x,$scale) = (&'fnorm($_[$[]),$_[$[+1]);
198     if ($x eq 'NaN') {
199         'NaN';
200     } else {
201         local($xm,$xe) = split('E',$x);
202         if ($xe >= $scale) {
203             $x;
204         } else {
205             $xe = length($xm)+$xe-$scale;
206             if ($xe < 1) {
207                 '+0E+0';
208             } elsif ($xe == 1) {
209                 # The first substr preserves the sign, which means that
210                 # we'll pass a non-normalized "-0" to &round when rounding
211                 # -0.006 (for example), purely so that &round won't lose
212                 # the sign.
213                 &norm(&round(substr($xm,$[,1).'0',
214                       "+0".substr($xm,$[+1,1),"+10"), $scale);
215             } else {
216                 &norm(&round(substr($xm,$[,$xe),
217                       "+0".substr($xm,$[+$xe,1),"+10"), $scale);
218             }
219         }
220     }
221 }
222     
223 # compare 2 values returns one of undef, <0, =0, >0
224 #   returns undef if either or both input value are not numbers
225 sub main'fcmp #(fnum_str, fnum_str) return cond_code
226 {
227     local($x, $y) = (&'fnorm($_[$[]),&'fnorm($_[$[+1]));
228     if ($x eq "NaN" || $y eq "NaN") {
229         undef;
230     } else {
231         ord($y) <=> ord($x)
232         ||
233         (  local($xm,$xe,$ym,$ye) = split('E', $x."E$y"),
234              (($xe <=> $ye) * (substr($x,$[,1).'1')
235              || &bigint'cmp($xm,$ym))
236         );
237     }
238 }
239 \f
240 # square root by Newtons method.
241 sub main'fsqrt { #(fnum_str[, scale]) return fnum_str
242     local($x, $scale) = (&'fnorm($_[$[]), $_[$[+1]);
243     if ($x eq 'NaN' || $x =~ /^-/) {
244         'NaN';
245     } elsif ($x eq '+0E+0') {
246         '+0E+0';
247     } else {
248         local($xm, $xe) = split('E',$x);
249         $scale = $div_scale if (!$scale);
250         $scale = length($xm)-1 if ($scale < length($xm)-1);
251         local($gs, $guess) = (1, sprintf("1E%+d", (length($xm)+$xe-1)/2));
252         while ($gs < 2*$scale) {
253             $guess = &'fmul(&'fadd($guess,&'fdiv($x,$guess,$gs*2)),".5");
254             $gs *= 2;
255         }
256         &'fround($guess, $scale);
257     }
258 }
259
260 1;