File Coverage

blib/lib/Math/SigFigs.pm
Criterion Covered Total %
statement 166 166 100.0
branch 110 110 100.0
condition 39 39 100.0
subroutine 13 13 100.0
pod 6 6 100.0
total 334 334 100.0


line stmt bran cond sub pod time code
1             package Math::SigFigs;
2              
3             # Copyright (c) 1995-2015 Sullivan Beck. All rights reserved.
4             # This program is free software; you can redistribute it and/or modify it
5             # under the same terms as Perl itself.
6              
7             ########################################################################
8              
9             require 5.004;
10             require Exporter;
11 8     8   227265 use Carp;
  8         20  
  8         1133  
12 8     8   59 use strict;
  8         13  
  8         378  
13 8     8   50 use warnings;
  8         20  
  8         742  
14              
15             our (@ISA,@EXPORT,@EXPORT_OK,%EXPORT_TAGS);
16 8     8   55 use base qw(Exporter);
  8         11  
  8         1590  
17             @EXPORT = qw(FormatSigFigs
18             CountSigFigs
19             );
20             @EXPORT_OK = qw(FormatSigFigs
21             CountSigFigs
22             addSF subSF multSF divSF
23             VERSION);
24              
25             %EXPORT_TAGS = ('all' => \@EXPORT_OK);
26              
27             our($VERSION);
28             $VERSION = 1.10;
29              
30 8     8   57 use strict;
  8         16  
  8         31483  
31              
32             sub addSF {
33 60     60 1 28932 my($n1,$n2)=@_;
34 60 100 100     181 return () if (! (defined $n1 || defined $n2));
35 59 100       108 $n1 = 0 if (! defined($n1));
36 59 100       98 $n2 = 0 if (! defined($n2));
37 59         104 $n1 = _Simplify($n1);
38 59         116 $n2 = _Simplify($n2);
39 59 100       159 return $n2 if ($n1==0);
40 55 100       127 return $n1 if ($n2==0);
41              
42 51         103 my $m1 = _LSP($n1);
43 51         91 my $m2 = _LSP($n2);
44 51 100       95 my $m = ($m1>$m2 ? $m1 : $m2);
45              
46 51         80 my($n) = $n1+$n2;
47 51 100       192 my($s) = ($n<0 ? q{-} : "");
48 51 100       99 $n = -1*$n if ($n<0); # n = 1234.44 5678.99
49 51         501 $n =~ /^(\d*)/;
50 51         125 my $i = ($1); # i = 1234 5678
51 51         58 my $l = length($i); # l = 4
52              
53 51 100       145 if ($m>0) { # m = 5,4,3,2,1
    100          
54 14 100       35 if ($l >= $m+1) { # m = 3,2,1; l-m = 1,2,3
    100          
55 10         28 $n = FormatSigFigs($n,$l-$m); # n = 1000,1200,1230 6000,5700,5680
56             } elsif ($l == $m) { # m = 4
57 2 100       7 if ($i =~ /^[5-9]/) {
58 1         3 $n = 1 . "0"x$m; # n = 10000
59             } else {
60 1         6 return 0; # n = 0
61             }
62             } else { # m = 5
63 2         8 return 0;
64             }
65              
66             } elsif ($i>0) { # n = 1234.44 5678.99
67 20         58 $n = FormatSigFigs($n,$l-$m); # m = 0,-1,-2,...
68              
69             } else { # n = 0.1234 0.00123 0.00567
70 17         96 $n =~ /\.(0*)(\d+)/;
71 17         42 my ($z,$d) = ($1,$2);
72 17         18 $m = -$m;
73              
74 17 100       47 if ($m > length($z)) { # m = -1,-2,.. -3,-4,.. -3,-4,..
    100          
75 13         35 $n = FormatSigFigs($n,$m-length($z));
76              
77             } elsif ($m == length($z)) { # m = -2 -2
78 2 100       8 if ($d =~ /^[5-9]/) {
79 1         6 $n = "0."."0"x($m-1)."1"; # n = 0.01
80             } else {
81 1         6 return 0; # n = 0
82             }
83              
84             } else { # m = -1 -1
85 2         10 return 0;
86             }
87             }
88              
89 45         439 return "$s$n";
90             }
91              
92             sub subSF {
93 19     19 1 21128 my($n1,$n2)=@_;
94 19 100 100     69 return () if (! (defined $n1 || defined $n2));
95 18 100       35 $n1 = 0 if (! defined($n1));
96 18 100       40 $n2 = 0 if (! defined($n2));
97              
98 18         32 $n2 = _Simplify($n2);
99 18 100       81 if ($n2<0) {
100 6         21 $n2 =~ s/\-//;
101             } else {
102 12         48 $n2 =~ s/^\+?/-/;
103             }
104 18         42 addSF($n1,$n2);
105             }
106              
107             sub multSF {
108 10     10 1 8630 my($n1,$n2)=@_;
109 10 100 100     29 return () if (! (defined $n1 || defined $n2));
110 9 100 100     99 return 0 if (! defined $n1 || ! defined $n2 ||
      100        
      100        
111             $n1==0 || $n2==0);
112 5         11 $n1 = _Simplify($n1);
113 5         9 $n2 = _Simplify($n2);
114 5         12 my($m1) = CountSigFigs($n1);
115 5         11 my($m2) = CountSigFigs($n2);
116 5 100       12 my($m) = ($m1<$m2 ? $m1 : $m2);
117 5         20 my($n) = $n1*$n2;
118 5         11 FormatSigFigs($n,$m);
119             }
120              
121             sub divSF {
122 10     10 1 6316 my($n1,$n2)=@_;
123 10 100 100     33 return () if (! (defined $n1 || defined $n2));
124 9 100 100     48 return 0 if (! defined $n1 || $n1==0);
125 7 100 100     28 return () if (! defined $n2 || $n2==0);
126 5         14 $n1 = _Simplify($n1);
127 5         10 $n2 = _Simplify($n2);
128              
129 5         13 my($m1) = CountSigFigs($n1);
130 5         11 my($m2) = CountSigFigs($n2);
131 5 100       13 my($m) = ($m1<$m2 ? $m1 : $m2);
132 5         12 my($n) = $n1/$n2;
133 5         14 FormatSigFigs($n,$m);
134             }
135              
136             sub FormatSigFigs {
137 211     211 1 137712 my($N,$n) = @_;
138 211         236 my($ret);
139 211         372 $N = _Simplify($N);
140 211 100 100     1759 return "" if (! (defined($N) && $n =~ /^\d+$/ && $n>0));
      100        
141              
142 207         642 $N =~ s/^([+-]?)//; # Remove sign
143 207         445 my $s = $1;
144 207 100       502 return "${s}0" if ($N==0);
145              
146 204 100       641 $N =~ s/0+$// if ($N=~/\./); # Remove all trailing zeros after decimal
147 204 100       1043 $N = "0$N" if ($N=~ /^\./); # Turn .2 into 0.2
148              
149 204         269 my($l,$l1,$l2,$m)=();
150              
151 204         386 $m = CountSigFigs($N);
152              
153             # If the number has the right number of sigfigs already, we'll return
154             # it with one minor modification:
155             # turn 24 (2) into 24.
156             # but
157             # don't turn 2400 (2) into 2400.
158              
159 204 100       490 if ($m==$n) {
160 35 100       84 $N = "$N." if (length($N)==$n);
161 35         240 return "$s$N";
162             }
163              
164             # If the number has too few sigfigs, we need to pad it with some zeroes.
165              
166 169 100       408 if ($m<$n) {
167 48 100       146 if ($N=~ /\./) {
168             # 0.012 (4) => 0.01200
169             # 1.12 (4) => 1.120
170 33         161 return "$s$N" . "0"x($n-$m);
171             }
172              
173             # 120 (4) => 120.0
174             # 1200 (4) => 1200.
175             # 12000 (4) => 12000
176              
177 15         15 $l = length($N);
178 15 100       88 return "$s$N" if ($l>$n);
179 11         59 return "$s$N." . "0"x($n-$l);
180             }
181              
182             # Anything else has too many sigfigs.
183             #
184             # Handle:
185             # 0.0123 (2) => 0.012
186              
187 121 100       428 $N = "$N." if ($N !~ /\./); # 123.
188 121 100       289 if ($N=~ /^0\.(0*)(\d*)$/) { # 0.0001234 (2)
189 30         67 ($l1,$l2) = (length($1),length($2)); # (l1,l2) = (3,4)
190 30         64 $N =~ s/5$/6/;
191 30         41 $l = $l1+$n; # 5
192 30         295 $ret = sprintf("%.${l}f",$N); # 0.00012
193 30         60 $m = CountSigFigs($ret);
194 30 100       145 return "$s$ret" if ($n==$m);
195              
196             # special cases 0.099 (1) -> 0.1
197             # 0.99 (1) -> 1.
198              
199 7         10 $l--;
200 7         34 $ret = sprintf("%.${l}f",$N);
201 7         14 $m = CountSigFigs($ret);
202 7 100       18 $ret = "$ret." if ($l==0);
203 7         30 return "$s$ret";
204             }
205              
206             # Handle:
207             # 123.4567 (3) => 123.
208             # 123.4567 (4) => 123.5
209             # Also handle part of:
210             # 1234.567 (3) => 1235 (3)
211              
212 91         294 $N=~ /^(\d+)\.(\d*)/; # 123.4567
213 91         275 my($n1,$n2) = ($1,$2);
214 91         127 ($l1,$l2)=(length($n1),length($n2)); # (l1,l2) = (3,4)
215              
216             # Keep some decimal points (or exactly 0)
217              
218 91 100       197 if ($n>=$l1) {
219 49         60 $l = $n-$l1; # l = number of decimal points to keep
220 49         142 $N =~ s/5$/6/; # 4.95 rounds down... make it go up
221 49         401 $ret = sprintf("%.${l}f",$N);
222 49         113 $m = CountSigFigs($ret);
223 49 100       119 if ($m==$n) {
224 34 100 100     142 $ret="$ret." if ($l==0 && $m==length($ret));
225 34         127 return "$s$ret";
226             }
227              
228             # special case 9.99 (2) -> 10.
229             # 9.99 (1) -> 10
230              
231 15         19 $l--;
232 15 100       27 if ($l>=0) {
233 11         36 $ret = sprintf("%.${l}f",$N);
234 11 100       26 $ret = "$ret." if ($l==0);
235 11         41 return "$s$ret";
236             }
237 4         19 return "$s$ret";
238             }
239              
240             # Otherwise, we're removing all decimal points (and it needs to be
241             # truncated even further). Truncate (not
242             # round) to an integer and pass through.
243              
244 42         61 $N = $n1;
245              
246             # Handle integers (the only case here is that we want fewer sigfigs
247             # than the lenght of the number.
248             # 123 (2) => 120
249              
250             # 123 9900 (3) 9900 (2) 9900 (1)
251 42         68 $l = length($N); # 3 4 4 4
252 42         182 $N =~ s/0*$//; # 123 99 99 99
253 42         73 $N =~ s/5$/6/;
254 42         461 $m = sprintf("%.${n}f",".$N"); # .123 .990 .99 1.0
255 42 100       697 if ($m>=1) {
256 10         20 $n--;
257 10         15 $l++;
258 10         36 $m = sprintf("%.${n}f",".$N"); # .123 .990 .99 1.
259             }
260 42         137 $m =~ s/^0//;
261 42         97 $m =~ s/\.//;
262 42         94 $N = $m . "0"x($l-length($m));
263 42         177 return "$s$N";
264             }
265              
266             sub CountSigFigs {
267 342     342 1 7655 my($N) = @_;
268 342         662 $N = _Simplify($N);
269 342 100       685 return () if (! defined($N));
270 341 100       781 return 0 if ($N==0);
271              
272 339         636 $N =~ s/^[+-]//;
273 339 100       1306 if ($N=~ /^\d+$/) {
    100          
274 84         298 $N =~ s/0*$//;
275 84         189 return length($N);
276             } elsif ($N=~ /^\.0*(\d+)$/) {
277 94         225 return length($1);
278             } else {
279 161         339 return length($N)-1;
280             }
281             }
282              
283             ########################################################################
284             # NOT FOR EXPORT
285             #
286             # These are exported above only for debug purposes. They are not
287             # for general use. They are not guaranteed to remain backward
288             # compatible (or even to exist at all) in future versions.
289             ########################################################################
290              
291             # This returns the power of the least sigificant digit.
292             #
293             sub _LSP {
294 108     108   4815 my($n) = @_;
295 108         189 $n =~ s/\-//;
296 108 100       391 if ($n =~ /(.*)\.(.+)/) {
    100          
297 84         210 return -length($2);
298             } elsif ($n =~ /\.$/) {
299 2         10 return 0;
300             } else {
301 22         57 return length($n) - CountSigFigs($n);
302             }
303             }
304              
305             # This prepares a number by converting it to it's simplest correct
306             # form.
307             #
308             # Strip out spaces and leading zeroes before a decimal point.
309             #
310             sub _Simplify {
311 724     724   12388 my($n) = @_;
312 724 100       1477 return undef if (! defined $n);
313 722 100 100     5738 if ($n =~ /^\s*([+-]?)\s*0*(\.\d+)\s*$/ ||
314             $n =~ /^\s*([+-]?)\s*0*(\d+\.?\d*)\s*$/) {
315 721         1638 my($s,$num)=($1,$2);
316 721 100       1787 $num = 0 if ($num==0);
317 721         2044 return "$s$num";
318             }
319 1         6 return undef;
320             }
321              
322             1;
323             # Local Variables:
324             # mode: cperl
325             # indent-tabs-mode: nil
326             # cperl-indent-level: 3
327             # cperl-continued-statement-offset: 2
328             # cperl-continued-brace-offset: 0
329             # cperl-brace-offset: 0
330             # cperl-brace-imaginary-offset: 0
331             # cperl-label-offset: 0
332             # End: