File Coverage

blib/lib/DateTime/Math/bigfloat.pl
Criterion Covered Total %
statement 60 101 59.4
branch 35 70 50.0
condition 11 39 28.2
subroutine 11 15 73.3
pod n/a
total 117 225 52.0


line stmt bran cond sub pod time code
1             package bigfloat;
2              
3 1     1   6 use strict;
  1         4  
  1         53  
4 1     1   6 use vars qw($div_scale $rnd_mode);
  1         3  
  1         2234  
5             require 'DateTime/Math/bigint.pl';
6              
7             # Arbitrary length float math package
8             #
9             # by Mark Biggar
10             #
11             # number format
12             # canonical strings have the form /[+-]\d+E[+-]\d+/
13             # Input values can have inbedded whitespace
14             # Error returns
15             # 'NaN' An input parameter was "Not a Number" or
16             # divide by zero or sqrt of negative number
17             # Division is computed to
18             # max($div_scale,length(dividend)+length(divisor))
19             # digits by default.
20             # Also used for default sqrt scale
21              
22             $div_scale = 40;
23              
24             # Rounding modes one of 'even', 'odd', '+inf', '-inf', 'zero' or 'trunc'.
25              
26             $rnd_mode = 'even';
27              
28             # bigfloat routines
29             #
30             # fadd(NSTR, NSTR) return NSTR addition
31             # fsub(NSTR, NSTR) return NSTR subtraction
32             # fmul(NSTR, NSTR) return NSTR multiplication
33             # fdiv(NSTR, NSTR[,SCALE]) returns NSTR division to SCALE places
34             # fneg(NSTR) return NSTR negation
35             # fabs(NSTR) return NSTR absolute value
36             # fcmp(NSTR,NSTR) return CODE compare undef,<0,=0,>0
37             # fround(NSTR, SCALE) return NSTR round to SCALE digits
38             # ffround(NSTR, SCALE) return NSTR round at SCALEth place
39             # fnorm(NSTR) return (NSTR) normalize
40             # fsqrt(NSTR[, SCALE]) return NSTR sqrt to SCALE places
41              
42             # Convert a number to canonical string form.
43             # Takes something that looks like a number and converts it to
44             # the form /^[+-]\d+E[+-]\d+$/.
45             sub DateTime::Math::fnorm { #(string) return fnum_str
46 3392     3392   6375 local($_) = @_;
47 3392 50       7496 defined($_) or return 'NaN';
48 3392         10505 s/\s+//g; # strip white space
49 3392 50 33     29854 if (/^([+-]?)(\d*)(\.(\d*))?([Ee]([+-]?\d+))?$/ &&
      33        
50             ($2 ne '' || defined($4))) {
51 3392 100       7390 my $x = defined($4) ? $4 : '';
52 3392 100       9981 my $y = defined($6) ? $6 : 0;
53 3392 100       17930 &norm(($1 ? "$1$2$x" : "+$2$x"), (($x ne '') ? $y-length($x) : $y));
    100          
54             } else {
55 0         0 'NaN';
56             }
57             }
58              
59             # normalize number -- for internal use
60             sub norm { #(mantissa, exponent) return fnum_str
61 4797     4797   7529 my ($mantissa, $exp) = @_;
62 4797 50       10533 defined($exp) or $exp = 0;
63 4797 50       9482 if ($mantissa eq 'NaN') {
64 0         0 'NaN';
65             } else {
66             # strip leading zeros
67 4797         11653 $mantissa =~ s/^([+-])0+/$1/;
68 4797 100       15527 if (length($mantissa) == 1) {
69 1589         22384 '+0E+0';
70             } else {
71             # strip trailing zeros
72 3208 100       11224 $exp += length($1) if ($mantissa =~ s/(0+)$//);
73 3208         18516 sprintf("%sE%+ld", $mantissa, $exp);
74             }
75             }
76             }
77              
78             # negation
79             sub DateTime::Math::fneg { #(fnum_str) return fnum_str
80 448     448   835 local($_) = &DateTime::Math::fnorm($_[0]);
81 448 100       1827 vec($_,0,8) ^= ord('+') ^ ord('-') unless $_ eq '+0E+0'; # flip sign
82 448         690 s/^H/N/;
83 448         1129 $_;
84             }
85              
86             # absolute value
87             sub DateTime::Math::fabs { #(fnum_str) return fnum_str
88 0     0   0 local($_) = &DateTime::Math::fnorm($_[0]);
89 0         0 s/^-/+/; # mash sign
90 0         0 $_;
91             }
92              
93             # multiplication
94             sub DateTime::Math::fmul { #(fnum_str, fnum_str) return fnum_str
95 216     216   481 my ($x,$y) = (&DateTime::Math::fnorm($_[0]),&DateTime::Math::fnorm($_[1]));
96 216 50 33     1403 if ($x eq 'NaN' || $y eq 'NaN') {
97 0         0 'NaN';
98             } else {
99 216         606 my ($xm,$xe) = split('E',$x);
100 216         946 my ($ym,$ye) = split('E',$y);
101 216         1542 &norm(&DateTime::Math::bmul($xm,$ym),$xe+$ye);
102             }
103             }
104              
105             # addition
106             sub DateTime::Math::fadd { #(fnum_str, fnum_str) return fnum_str
107 1010     1010   1986 my ($x,$y) = (&DateTime::Math::fnorm($_[0]),&DateTime::Math::fnorm($_[1]));
108 1010 50 33     5330 if ($x eq 'NaN' || $y eq 'NaN') {
109 0         0 'NaN';
110             } else {
111 1010         2912 my ($xm,$xe) = split('E',$x);
112 1010         2250 my ($ym,$ye) = split('E',$y);
113 1010 100       3098 ($xm,$xe,$ym,$ye) = ($ym,$ye,$xm,$xe) if ($xe < $ye);
114 1010         4404 &norm(&DateTime::Math::badd($ym,$xm.('0' x ($xe-$ye))),$ye);
115             }
116             }
117              
118             # subtraction
119             sub DateTime::Math::fsub { #(fnum_str, fnum_str) return fnum_str
120 447     447   1374 &DateTime::Math::fadd($_[0],&DateTime::Math::fneg($_[1]));
121             }
122              
123             # division
124             # args are dividend, divisor, scale (optional)
125             # result has at most max(scale, length(dividend), length(divisor)) digits
126             sub DateTime::Math::fdiv #(fnum_str, fnum_str[,scale]) return fnum_str
127             {
128 179     179   655 my ($x,$y,$scale) = (&DateTime::Math::fnorm($_[0]),&DateTime::Math::fnorm($_[1]),$_[2]);
129 179 50 33     1182 if ($x eq 'NaN' || $y eq 'NaN' || $y eq '+0E+0') {
      33        
130 0         0 'NaN';
131             } else {
132 179         589 my ($xm,$xe) = split('E',$x);
133 179         433 my ($ym,$ye) = split('E',$y);
134 179 50       524 $scale = $div_scale if (!$scale);
135 179 50       491 $scale = length($xm)-1 if (length($xm)-1 > $scale);
136 179 50       378 $scale = length($ym)-1 if (length($ym)-1 > $scale);
137 179         406 $scale = $scale + length($ym) - length($xm);
138 179         821 &norm(&round(&DateTime::Math::bdiv($xm.('0' x $scale),$ym),$ym),
139             $xe-$ye-$scale);
140             }
141             }
142              
143             # round int $q based on fraction $r/$base using $rnd_mode
144             sub round { #(int_str, int_str, int_str) return int_str
145 179     179   365 my ($q,$r,$base) = @_;
146 179 50 33     4990 if ($q eq 'NaN' || $r eq 'NaN') {
    50          
147 0         0 'NaN';
148             } elsif ($rnd_mode eq 'trunc') {
149 0         0 $q; # just truncate
150             } else {
151 179         703 my $cmp = &DateTime::Math::bcmp(&DateTime::Math::bmul($r,'+2'),$base);
152 179 100 0     1331 if ( $cmp < 0 ||
      33        
      66        
153             ($cmp == 0 &&
154             ( $rnd_mode eq 'zero' ||
155             ($rnd_mode eq '-inf' && (substr($q,0,1) eq '+')) ||
156             ($rnd_mode eq '+inf' && (substr($q,0,1) eq '-')) ||
157             ($rnd_mode eq 'even' && $q =~ /[24680]$/) ||
158             ($rnd_mode eq 'odd' && $q =~ /[13579]$/) )) ) {
159 143         591 $q; # round down
160             } else {
161 36 50       183 &DateTime::Math::badd($q, ((substr($q,0,1) eq '-') ? '-1' : '+1'));
162             # round up
163             }
164             }
165             }
166              
167             # round the mantissa of $x to $scale digits
168             sub DateTime::Math::fround { #(fnum_str, scale) return fnum_str
169 0     0   0 my ($x,$scale) = (&DateTime::Math::fnorm($_[0]),$_[1]);
170 0 0 0     0 if ($x eq 'NaN' || $scale <= 0) {
171 0         0 $x;
172             } else {
173 0         0 my ($xm,$xe) = split('E',$x);
174 0 0       0 if (length($xm)-1 <= $scale) {
175 0         0 $x;
176             } else {
177 0         0 &norm(&round(substr($xm,0,$scale+1),
178             "+0".substr($xm,$scale+1,1),"+10"),
179             $xe+length($xm)-$scale-1);
180             }
181             }
182             }
183              
184             # round $x at the 10 to the $scale digit place
185             sub DateTime::Math::ffround { #(fnum_str, scale) return fnum_str
186 0     0   0 my ($x,$scale) = (&DateTime::Math::fnorm($_[0]),$_[1]);
187 0 0       0 if ($x eq 'NaN') {
188 0         0 'NaN';
189             } else {
190 0         0 my ($xm,$xe) = split('E',$x);
191 0 0       0 if ($xe >= $scale) {
192 0         0 $x;
193             } else {
194 0         0 $xe = length($xm)+$xe-$scale;
195 0 0       0 if ($xe < 1) {
    0          
196 0         0 '+0E+0';
197             } elsif ($xe == 1) {
198 0         0 &norm(&round('+0',"+0".substr($xm,1,1),"+10"), $scale);
199             } else {
200 0         0 &norm(&round(substr($xm,0,$xe),
201             "+0".substr($xm,$xe,1),"+10"), $scale);
202             }
203             }
204             }
205             }
206            
207             # compare 2 values returns one of undef, <0, =0, >0
208             # returns undef if either or both input value are not numbers
209             sub DateTime::Math::fcmp #(fnum_str, fnum_str) return cond_code
210             {
211 67     67   192 my ($x, $y) = (&DateTime::Math::fnorm($_[0]),&DateTime::Math::fnorm($_[1]));
212 67 50 33     379 if ($x eq "NaN" || $y eq "NaN") {
213 0         0 return;
214             } else {
215             # Compare signs between the two numbers.
216 67         115 my $ret = (ord($y) <=> ord($x));
217 67 50       133 $ret and return $ret;
218             # Compare the numbers by making both of them integer and using the
219             # integer compare routines. Make the numbers into integers by
220             # taking the number with the larger exponent and adding either
221             # abs($xe - $ye) to the end of it so that the two numbers have the
222             # same exponent.
223 67         289 my ($xm,$xe,$ym,$ye) = split('E', $x."E$y");
224 67         158 my $diff = abs($xe - $ye);
225 67 100       156 (($xe > $ye) ? $xm : $ym) .= '0' x $diff;
226 67         267 &DateTime::Math::bcmp($xm,$ym) <=> 0;
227             }
228             }
229              
230             # square root by Newtons method.
231             sub DateTime::Math::fsqrt { #(fnum_str[, scale]) return fnum_str
232 0     0     my ($x, $scale) = (&DateTime::Math::fnorm($_[0]), $_[1]);
233 0 0 0       if ($x eq 'NaN' || $x =~ /^-/) {
    0          
234 0           'NaN';
235             } elsif ($x eq '+0E+0') {
236 0           '+0E+0';
237             } else {
238 0           my ($xm, $xe) = split('E',$x);
239 0 0         $scale = $div_scale if (!$scale);
240 0 0         $scale = length($xm)-1 if ($scale < length($xm)-1);
241 0           my ($gs, $guess) = (1, sprintf("1E%+d", (length($xm)+$xe-1)/2));
242 0           while ($gs < 2*$scale) {
243 0           $guess = &DateTime::Math::fmul(&DateTime::Math::fadd($guess,&DateTime::Math::fdiv($x,$guess,$gs*2)),".5");
244 0           $gs *= 2;
245             }
246 0           &DateTime::Math::fround($guess, $scale);
247             }
248             }
249              
250             1;