File Coverage

blib/lib/Math/BigApprox.pm
Criterion Covered Total %
statement 130 133 97.7
branch 83 88 94.3
condition 14 15 93.3
subroutine 30 31 96.7
pod 6 6 100.0
total 263 273 96.3


line stmt bran cond sub pod time code
1             package Math::BigApprox;
2 1     1   2315 use strict;
  1         3  
  1         44  
3              
4             # use POSIX qw( floor ); # Maybe, maybe not.
5              
6 1     1   5 use vars qw( $VERSION @EXPORT_OK );
  1         3  
  1         223  
7             BEGIN {
8 1     1   11 $VERSION= 0.001_005;
9              
10 1         6 require Exporter;
11 1         3 *import= \&Exporter::import;
12 1         3 @EXPORT_OK= qw( c Fact Prod $SigDigs );
13              
14 1         1 my $useFloor= 0;
15 1 50       3 if( eval { require POSIX; 1 } ) {
  1         909  
  1         8547  
16             # On "long double" Perls, POSIX::floor converts to "double"
17 1         29 $useFloor= "4e+102" eq POSIX::floor(4e102);
18             }
19 1 50       4 if( $useFloor ) {
20 1         195 *_floor= \&POSIX::floor;
21             } else {
22             *_floor= sub {
23 0 0       0 return $_[0] < 0 ? -int(-$_[0]) : int($_[0]);
24 0         0 };
25             }
26             }
27              
28             use overload(
29 1         23 '+' => \&_add,
30             '-' => \&_sub,
31             neg => \&_neg,
32             '*' => \&_mul,
33             '/' => \&_div,
34             '**' => \&_pow,
35             '^' => \&_prd, # $x^$y = factorial($y)/factorial($x)
36             '<<' => \&_shl,
37             '>>' => \&_shr,
38             '<=>'=> \&_cmp,
39             '!' => \&_fct, # !$x = factorial($x)
40             '0+' => \&_num,
41             '""' => \&_str,
42             '=' => \&_clone,
43             abs => \&_abs,
44             log => \&_log,
45             sqrt => \&_half,
46             bool => \&_chastise,
47             fallback => 1, # autogenerate if possible
48 1     1   9 );
  1         1  
49              
50             # Don't use these as a methods
51             sub c
52             {
53 16     16 1 45 return __PACKAGE__->new( @_ );
54             }
55              
56             sub Prod
57             {
58 8     8 1 56 my( $x, $y )= @_;
59 8         13 my $p= c( 1 );
60 8         22 while( $x <= $y ) {
61 1247         2366 $p *= $x++;
62             }
63 8         39 return $p;
64             }
65              
66             sub Fact
67             {
68 6     6 1 96 return Prod( 1, @_ );
69             }
70              
71             # Public methods:
72             sub new
73             {
74 2669     2669 1 5805 my $us= shift @_;
75 2669 100       5059 if( ! @_ ) {
76 11 100       42 die "new() needs an object or a value"
77             if ! ref $us;
78 10         38 return bless [ @$us ];
79             }
80 2658         3222 my $val= shift @_;
81 2658 100       5043 return bless [ 0, 0 ]
82             if 0 == $val;
83 2642 100       4556 return bless [ log( -$val ), -1 ]
84             if $val < 0;
85 2629         7193 return bless [ log( $val ), 1 ]
86             }
87              
88             sub Get
89             {
90 10     10 1 25 my( $x )= @_;
91 10 100       36 return wantarray ? (0,0) : 0
    100          
92             if ! $x->[1];
93 8 100       42 return wantarray ? @$x : $x->[0];
94             }
95              
96             sub Sign
97             {
98 10     10 1 21 my $x= shift @_;
99 10         23 my $old= $x->[1];
100 10 100       26 if( @_ ) {
101 3         4 my $new= shift @_;
102 3 100       13 $x->[1]= 0<$new ? 1 : 0==$new ? 0 : -1;
    100          
103             }
104 10         41 return $old;
105             }
106              
107             # Private methods (please don't call these):
108              
109             # This just ignores extra arguments so overload.pm can use it
110             sub _clone
111             {
112 0     0   0 return $_[0]->new();
113             }
114              
115             sub _chastise
116             {
117 1     1   6 die "You can't use a Math::BigApprox as a Boolean.\n";
118             }
119              
120             sub _mul
121             {
122 2554     2554   3558 my( $x, $y )= @_;
123 2554 100       6148 $y= $x->new( $y )
124             if ! ref $y;
125 2554         10321 return bless [ $x->[0] + $y->[0], $x->[1] * $y->[1] ];
126             }
127              
128             sub __args
129             {
130 62     62   97 my( $x, $y, $rev )= @_;
131 62 100       172 $y= $x->new( $y )
132             if ! ref $y;
133 62 100       133 ( $x, $y )= ( $y, $x )
134             if $rev;
135 62         123 return( $x, $y );
136             }
137              
138             sub _div
139             {
140 16     16   49 my( $x, $y )= __args( @_ );
141 16 100       48 die "Division by 0"
142             if ! $y->[1];
143 15         88 return bless [ $x->[0] - $y->[0], $x->[1] * $y->[1] ];
144             }
145              
146             sub _pow
147             {
148 24     24   71 my( $x, $y )= __args( @_ );
149 24 100       63 return bless [ 0, 1 ]
150             if ! $y->[1];
151 21 100       44 return bless [ 0, 0 ]
152             if ! $x->[1];
153             # We rather "punt" on the sign.
154 19         20 my $sign= 1;
155 19 100 100     108 if( $x->[1] < 0
      100        
      100        
156             && 0 < $y->[1]
157             && $y->[0] < log(1e9)
158             && 1 == int( 0.5 + exp($y->[0]) ) % 2
159             ) {
160 4         5 $sign= -1;
161             }
162 19         40 return bless [ $x->[0] * $y->_num(), $sign ];
163             }
164              
165             sub _add
166             {
167 1314     1314   1940 my( $x, $y )= @_;
168 1314 100       4028 $y= $x->new( $y )
169             if ! ref $y;
170 1314 100       3004 return $y->new()
171             if ! $x->[1];
172 1311 100       2815 return $x->new()
173             if ! $y->[1];
174 1310 100       3820 ( $x, $y )= ( $y, $x )
175             if $y->[0] < $x->[0];
176 1310 100       3151 if( $x->[1] == $y->[1] ) {
177 1304         7798 return bless [
178             $y->[0] + log( 1 + exp( $x->[0] - $y->[0] ) ),
179             $x->[1],
180             ];
181             }
182 6 100       23 return bless [ 0, 0 ]
183             if $x->[0] == $y->[0];
184 4         50 return bless [
185             $y->[0] + log( 1 - exp( $x->[0] - $y->[0] ) ),
186             $y->[1],
187             ];
188             }
189              
190             sub _sub
191             {
192 4     4   10 my( $x, $y )= __args( @_ );
193 4         10 return $x->_add( $y->_neg() );
194             }
195              
196             sub _neg
197             {
198 14     14   24 my( $x )= @_;
199 14         51 return bless [ $x->[0], -$x->[1] ];
200             }
201              
202             sub _shl
203             {
204 5     5   15 my( $x, $y )= __args( @_ );
205 5         17 return bless [ $x->[0] + $y->_num()*log(2), $x->[1] ];
206             }
207              
208             sub _shr
209             {
210 4     4   12 my( $x, $y )= __args( @_ );
211 4         13 return bless [ $x->[0] - $y->_num()*log(2), $x->[1] ];
212             }
213              
214             sub _log
215             {
216 4     4   8 my( $x )= @_;
217 4 100       20 die "Can't take log(" . $x . ")\n"
218             if 1 != $x->[1];
219 2         10 return $x->[0];
220             }
221              
222             sub _abs
223             {
224 4     4   23 my( $x )= @_;
225 4         19 return bless [ $x->[0], abs( $x->[1] ) ];
226             }
227              
228             sub _half
229             {
230 4     4   8 my( $x )= @_;
231 4 100       12 die "Can't take sqrt(" . $x . ")\n"
232             if $x->[1] < 0;
233 3         16 return bless [ $x->[0]/2, $x->[1] ];
234             }
235              
236             sub _cmp
237             {
238 1340     1340   2190 my( $x, $y, $rev )= @_;
239 1340 100       2828 $y= $x->new( $y )
240             if ! ref $y;
241 1340 100       2780 return 0
242             if $x eq $y;
243 1320 100       2866 ( $x, $y )= ( $y, $x )
244             if $rev;
245 1320   66     9303 return $x->[1] <=> $y->[1] || $x->[1]*$x->[0] <=> $y->[1]*$y->[0];
246             }
247              
248             sub _prd
249             {
250 9     9   17 my( $x, $y )= __args( @_ );
251 9 100       23 return bless [ 0, 1 ]
252             if $y < $x;
253 6         15 my $p= $x->new();
254 6         14 my $m= $x + 1;
255 6         13 while( $m <= $y ) {
256 1293         2733 $p= $p * $m;
257 1293         3224 $m= $m + 1;
258             }
259 6         38 return $p;
260             }
261              
262             sub _fct
263             {
264 7     7   12 my( $x )= @_;
265 7         13 return 1^$x;
266             }
267              
268             sub _num
269             {
270 45     45   62 my( $x )= @_;
271 45         323 return $x->[1] * exp( $x->[0] );
272             }
273              
274 1     1   1915 use vars qw( $SigDigs $FloorMag $LenMag );
  1         1  
  1         185  
275             # Figure out how many significant digits are in an NV on this platform:
276             BEGIN {
277              
278             # Cheap trick to get at how many sig digs Perl figured out it has:
279 1     1   2 $SigDigs= length( 10 / 7 );
280              
281             # Don't call floor() on numbers larger than this, since they can't
282             # have a factional part [and floor(4e102) can substract 8e80!]
283 1         3 $FloorMag= "1e" . $SigDigs;
284              
285             # Minus 1 for decmical point, minus another two just
286             # because our calculations lose some precision:
287 1         3 $SigDigs -= 3;
288              
289             # Long doubles leave $SigDigs set too high (probably
290             # not all C RTL calls are fully long-double-using):
291 1 50       9 $SigDigs -= 3
292             if 14 < $SigDigs;
293              
294             # Don't us length() to measure magnitude on numbers larger than this:
295 1         317 $LenMag= "1e" . $SigDigs;
296             }
297              
298             sub _str
299             {
300 2828     2828   6921 my( $x )= @_;
301 2828 100       6178 return 0
302             if ! $x->[1];
303 2790         3941 my $exp= $x->[0] / log(10);
304 2790 100 100     11742 if( $exp && 2*$exp == $exp ) {
305 9 100       31 return 0
306             if $exp < 0;
307 3         15 return $x->[1] * $exp;
308             }
309 2781         15426 $exp= sprintf "%.*g", $SigDigs-1, $exp;
310 2781 100       10535 $exp= _floor( $exp )
311             if abs($exp) < $FloorMag;
312 2781         5264 my $mant= exp( $x->[0] - log(10)*$exp );
313 2781 100       5746 $mant= 1
314             if 2*$mant == $mant;
315 2781 100       7676 $exp= "+$exp"
316             if $exp !~ /^-/;
317 2781 100       7113 my $digs= $LenMag <= abs($exp) ? 1 : $SigDigs - length($exp);
318 2781 100       5044 $digs= 1
319             if $digs < 1;
320 2781 100       12974 $mant= sprintf "%s%.*f", $x->[1] < 0 ? '-' : '', $digs-1, $mant;
321 2781         11751 $mant =~ s/[.]?0+$//;
322 2781 100       5755 $mant .= 0==$exp ? "" : "e" . $exp;
323 2781 100       5987 return $mant
324             if $digs < abs($exp);
325 2741         10402 return 0+$mant;
326             }
327              
328             __PACKAGE__;
329             __END__