File Coverage

lib/Math/SigFigs.pm
Criterion Covered Total %
statement 151 151 100.0
branch 88 88 100.0
condition 29 29 100.0
subroutine 15 15 100.0
pod 6 6 100.0
total 289 289 100.0


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