source: vendor/perl/5.8.8/lib/bigfloat.pl

Last change on this file was 3181, checked in by bird, 18 years ago

perl 5.8.8

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