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