File Coverage

blib/lib/Math/SigFigs.pm
Criterion Covered Total %
statement 161 161 100.0
branch 98 98 100.0
condition 23 23 100.0
subroutine 14 14 100.0
pod 6 6 100.0
total 302 302 100.0


line stmt bran cond sub pod time code
1             package Math::SigFigs;
2              
3             # Copyright (c) 1995-2016 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   119953 use Carp;
  8         13  
  8         461  
12 8     8   34 use strict;
  8         10  
  8         152  
13 8     8   23 use warnings;
  8         17  
  8         419  
14              
15             our (@ISA,@EXPORT,@EXPORT_OK,%EXPORT_TAGS);
16 8     8   29 use base qw(Exporter);
  8         6  
  8         1109  
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.20';
29              
30 8     8   33 use strict;
  8         10  
  8         10603  
31              
32             sub addSF {
33 52     52 1 24001 my($n1,$n2)=@_;
34 52         61 _add($n1,$n2,0);
35             }
36              
37             sub subSF {
38 26     26 1 11030 my($n1,$n2)=@_;
39 26         30 _add($n1,$n2,1);
40             }
41              
42             sub _add {
43 78     78   78 my($n1in,$n2in,$sub) = @_;
44              
45 78         47 my($n1,$sig1,$lsp1,$s1,$int1,$dec1,$n2,$sig2,$lsp2,$s2,$int2,$dec2);
46              
47 78 100       117 if (defined($n1in)) {
48 74         82 ($n1,$sig1,$lsp1,$s1,$int1,$dec1) = _Simplify($n1in);
49             }
50 78 100       122 return if (! defined($n1));
51              
52 72 100       93 if (defined($n2in)) {
53 70         76 ($n2,$sig2,$lsp2,$s2,$int2,$dec2) = _Simplify($n2in);
54             }
55 72 100       124 return if (! defined($n2));
56              
57 68 100       76 if ($sub) {
58 21 100       38 if ($n2<0) {
    100          
59 8         15 $n2 =~ s/\-//;
60 8         8 $s2 = '';
61             } elsif ($n2 > 0) {
62 12         24 $n2 =~ s/^\+?/-/;
63 12         13 $s2 = '-';
64             }
65             }
66              
67 68 100       87 return $n2 if ($n1in eq '0');
68 67 100       74 return $n1 if ($n2in eq '0');
69              
70 66 100       93 my $lsp = ($lsp1 > $lsp2 ? $lsp1 : $lsp2);
71              
72 66         81 ($n1) = _ToExp($s1,$int1,$dec1,$lsp);
73 66         101 ($n2) = _ToExp($s2,$int2,$dec2,$lsp);
74              
75 66         149 my($n,$sig,$tmp,$s,$int,$dec) = _Simplify($n1+$n2);
76 66         155 $n = sprintf("%.0f",$n) . ".e$lsp";
77 66         97 ($n,$sig,$lsp,$tmp,$int,$dec) = _Simplify("${n}");
78 66         188 return $n;
79             }
80              
81             sub multSF {
82 12     12 1 5388 my($n1,$n2)=@_;
83 12         10 my($sig1,$sig2);
84              
85 12 100       20 if (defined($n1)) {
86 10         13 ($n1,$sig1) = _Simplify($n1);
87             }
88 12 100       23 return if (! defined($n1));
89              
90 10 100       13 if (defined($n2)) {
91 9         10 ($n2,$sig2) = _Simplify($n2);
92             }
93 10 100       17 return if (! defined($n2));
94              
95 9 100       13 my $sig = ($sig1 < $sig2 ? $sig1 : $sig2);
96 9         15 my($n) = $n1*$n2;
97 9         12 FormatSigFigs($n,$sig);
98             }
99              
100             sub divSF {
101 10     10 1 4277 my($n1,$n2)=@_;
102 10         7 my($sig1,$sig2);
103              
104 10 100       17 if (defined($n1)) {
105 8         10 ($n1,$sig1) = _Simplify($n1);
106             }
107 10 100       19 return if (! defined($n1));
108              
109 8 100       12 if (defined($n2)) {
110 7         10 ($n2,$sig2) = _Simplify($n2);
111             }
112 8 100 100     31 return if (! defined($n2) || $n2 == 0);
113              
114 6 100       10 my $sig = ($sig1 < $sig2 ? $sig1 : $sig2);
115 6         10 my($n) = $n1/$n2;
116 6         9 FormatSigFigs($n,$sig);
117             }
118              
119             sub FormatSigFigs {
120 174     174 1 70686 my($N,$n) = @_;
121 174 100 100     811 return '' if ($n !~ /^\d+$/ || $n == 0);
122              
123 171         106 my($ret,$sig,$lsp,$s,$int,$dec);
124 171         183 ($N,$sig,$lsp,$s,$int,$dec) = _Simplify($N);
125 171 100       265 return "" if (! defined($N));
126 170 100 100     307 return '0.0' if ($N==0 && $n==1);
127              
128 168 100       255 return $N if ($sig eq $n);
129              
130             # Convert $N to an exponential where the numeric part with the exponent
131             # ignored is 0.1 <= $num < 1.0. i.e. 0.#####e## where the first '#' is
132             # non-zero. Then we can format it using a simple sprintf command.
133              
134 143         93 my($num,$e);
135 143 100 100     264 if ($int > 0) {
    100          
136 98         91 $num = "0.$int$dec";
137 98         69 $e = length($int);
138             } elsif ($dec ne '' && $dec > 0) {
139 38         69 $dec =~ s/^(0*)//;
140 38         53 $num = "0.$dec";
141 38         47 $e = -length($1);
142             } else {
143 7         7 $e = 0;
144 7         8 $num = "$int.$dec";
145             }
146              
147             # sprintf doesn't round 5 up, so convert a 5 to 6 in the n+1'th position
148              
149 143 100 100     364 if ($n < $sig && substr($num,$n+2,1) eq '5') {
150 16         17 substr($num,$n+2,1) = '6';
151             }
152              
153             # We have to handle the one special case:
154             # 0.99 (1) => 1.0
155             # If sprintf rounds a number to 1.0 or higher, then we reduce the
156             # number of decimal points by 1.
157              
158 143         685 my $tmp = sprintf("%.${n}f",$num);
159 143 100       238 if ($tmp >= 1.0) {
160 38         37 $n--;
161 38         69 $tmp = sprintf("%.${n}f",$num);
162             }
163 143         257 ($N,$sig,$lsp,$s,$int,$dec) = _Simplify("$s${tmp}e$e");
164 143         339 return $N;
165             }
166              
167             sub CountSigFigs {
168 14     14 1 4587 my($N) = @_;
169 14         9 my($sig);
170 14         18 ($N,$sig) = _Simplify($N);
171 14 100       25 return () if (! defined($N));
172 13         21 return $sig;
173             }
174              
175             ########################################################################
176             # NOT FOR EXPORT
177             #
178             # These are for internal use only. They are not guaranteed to remain
179             # backward compatible (or even to exist at all) in future versions.
180             ########################################################################
181              
182             # This takes the parts of a number ($int and $dec) and turns it into
183             # an exponential with the LSP in the 1's place. The exponent is
184             # returned (rather than appended to the number).
185             #
186             sub _ToExp {
187 139     139   5179 my($s,$int,$dec,$lsp) = @_;
188              
189 139 100       212 if ($lsp == 0) {
190 15         31 return ("$s$int.${dec}",0);
191             }
192              
193 124 100       151 if ($lsp > 0) {
194 33         682 $int = "0"x($lsp-length($int)) . $int;
195 33         62 $dec = substr($int,-$lsp) . $dec;
196 33         36 $int = substr($int,0,length($int)-$lsp);
197 33         78 return ("$s$int.${dec}",-$lsp);
198             }
199              
200 91         813 $dec .= "0"x(-$lsp-length($dec));
201 91         101 $int .= substr($dec,0,-$lsp);
202 91         79 $dec = substr($dec,-$lsp);
203 91         161 return ("$s$int.${dec}",-$lsp);
204             }
205              
206             # This prepares a number by converting it to it's simplest correct
207             # form. All space is ignored. It handles numbers of the form:
208             # signed (+, -, or no sign)
209             # integers
210             # reals (###.###)
211             # exponential (###.###e###)
212             #
213             # It returns:
214             # the number in the simplest form
215             # the number of significant figures
216             # the power of the least significant digit
217             #
218             sub _Simplify {
219 724     724   65893 my($n) = @_;
220 724 100       970 return if (! defined($n));
221 723         1109 $n =~ s/\s+//g;
222 723         821 $n =~ s/^([+-])//;
223 723   100     1839 my $s = $1 || '';
224 723 100       921 $s = '' if ($s eq '+');
225 723 100       892 return if ($n eq '');
226 721         425 my $exp;
227 721 100       1451 if ($n =~ s/[eE]([+-]*\d+)$//) {
228 244         261 $exp = $1;
229             } else {
230 477         351 $exp = 0;
231             }
232              
233 721         465 my($int,$dec,$sig,$lsp);
234              
235 721 100       2399 if ($n =~ /^(\d+)$/) { # 00 0123 012300
    100          
    100          
236 132         91 $int = $n;
237 132         256 $int =~ s/^0*//; # '' 123 12300
238 132         126 $dec = '';
239 132         208 $int =~ /(0*)$/;
240 132         132 my $tmp = $1; # '' '' 00
241 132 100       187 $int = 0 if ($int eq ''); # 0
242 132         86 $lsp = length($tmp); # 0 0 2
243 132         120 $sig = length($int) - $lsp; # 1 3 3
244              
245             } elsif ($n =~ /^0*\.(\d+)$/) { # .000 .00123 .0012300
246 233         225 $dec = $1; # 000 00123 0012300
247 233         170 $int = '';
248 233         261 $dec =~ /^(0*)/;
249 233         189 my $tmp = $1; # 000 00 00
250 233         187 $lsp = -length($dec); # -3 -5 -7
251 233         166 $sig = length($dec)-length($tmp); # 0 3 5
252 233 100       332 $sig = length($dec)
253             if ($dec eq $tmp); # 3
254              
255             } elsif ($n =~ /^0*(\d+)\.(\d*)$/) { # 12. 12.3
256 348         456 ($int,$dec) = ($1,$2); # 12,'' 12,3
257 348         295 $lsp = -length($dec); # 0 -1
258 348         293 $sig = length($int) + length($dec);# 2 3
259              
260             } else {
261 8         16 return;
262             }
263              
264             # Handle the exponent, if any
265              
266 713 100       1188 if ($exp > 0) {
    100          
267 139 100       152 if ($exp >= length($dec)) {
268 95         133 $int = "$int$dec" . "0"x($exp-length($dec));
269 95         71 $dec = '';
270             } else {
271 44         55 $int .= substr($dec,0,$exp);
272 44         40 $dec = substr($dec,$exp);
273             }
274 139         107 $lsp += $exp;
275 139         214 $int =~ s/^0*//;
276 139 100       199 $int = '0' if (! $int);
277              
278             } elsif ($exp < 0) {
279 66 100       81 if (-$exp < length($int)) {
280 27         35 $dec = substr($int,$exp) . $dec;
281 27         40 $int = substr($int,0,length($int)+$exp);
282             } else {
283 39         58 $dec = "0"x(-$exp-length($int)) . "$int$dec";
284 39         27 $int = "0";
285             }
286 66         49 $lsp += $exp;
287             }
288              
289             # We have a decimal point if:
290             # There is a decimal section
291             # An integer ends with a significant 0 but is not exactly 0
292             # We prepend a sign to anything except for 0
293              
294 713         424 my $num;
295 713 100       705 if ($dec eq '') {
296 248         194 $num = $int;
297 248 100 100     701 $num .= "." if ($lsp == 0 && $int =~ /0$/ && $int ne '0');
      100        
298             } else {
299 465 100       590 $int = "0" if ($int eq '');
300 465         468 $num = "$int.$dec";
301             }
302 713 100       1168 $s = '' if ($num == 0);
303 713         674 $num = "$s$num";
304              
305 713         1913 return ($num,$sig,$lsp,$s,$int,$dec);
306             }
307              
308             1;
309             # Local Variables:
310             # mode: cperl
311             # indent-tabs-mode: nil
312             # cperl-indent-level: 3
313             # cperl-continued-statement-offset: 2
314             # cperl-continued-brace-offset: 0
315             # cperl-brace-offset: 0
316             # cperl-brace-imaginary-offset: 0
317             # cperl-label-offset: 0
318             # End: