File Coverage

blib/lib/Math/SigDig.pm
Criterion Covered Total %
statement 81 110 73.6
branch 52 78 66.6
condition 11 24 45.8
subroutine 7 7 100.0
pod 0 2 0.0
total 151 221 68.3


line stmt bran cond sub pod time code
1             package Math::SigDig;
2              
3             #Robert W. Leach
4             #Princeton University
5             #Carl Icahn Laboratory
6             #Lewis Sigler Institute for Integrative Genomics
7             #Bioinformatics Group
8             #Room 133A
9             #Princeton, NJ 08544
10             #rleach@genomics.princeton.edu
11             #Copyright 2014
12              
13             #NOTICE
14             #
15             #This software (Math::SigDig) and ancillary information (herein
16             #called "SOFTWARE") is free: you can redistribute it and/or modify
17             #it under the terms of the GNU General Public License as published by
18             #the Free Software Foundation, either version 3 of the License, or
19             #(at your option) any later version.
20             #
21             #This software is distributed in the hope that it will be useful,
22             #but WITHOUT ANY WARRANTY; without even the implied warranty of
23             #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24             #GNU General Public License for more details.
25             #
26             #You should have received a copy of the GNU General Public License
27             #along with this program. If not, see .
28             #
29             #If SOFTWARE is modified to produce derivative works, such modified
30             #SOFTWARE should be clearly marked, so as not to confuse it with this
31             #version.
32              
33              
34              
35 1     1   31226 use 5.012003;
  1         4  
  1         35  
36 1     1   6 use strict;
  1         2  
  1         38  
37 1     1   6 use warnings;
  1         6  
  1         48  
38              
39 1     1   6 use Exporter;
  1         1  
  1         47  
40 1     1   5 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
  1         2  
  1         1775  
41              
42             $VERSION = 1.00;
43             @ISA = qw(Exporter);
44             @EXPORT = qw(sigdig getsigdig);
45             @EXPORT_OK = ();
46             %EXPORT_TAGS = ();
47              
48             sub sigdig
49             {
50 9     9 0 1126 my $num = $_[0];
51 9 100       18 my $places = defined($_[1]) ? $_[1] : 3;
52              
53             #Pad with 0's by adding decimal places up to $places significant digits, or
54             #if $places is 0, up to the pad value (note there can still be numbers in
55             #this case with more significant digits than specified by the pad value)
56 9 100 66     28 my $pad = defined($_[2]) ? ($places > 0 && $_[2] ? $places : $_[2]) : 0;
    100          
57              
58 9         12 my $new_num = '';
59              
60 9 50 33     88 if(!defined($places) || $places !~ /\d/ || $places =~ /\D/ || $places < 0)
    100 33        
      33        
61             {
62 0         0 print STDERR ("ERROR:sigdig(): An invalid number of significant ",
63             "digits was specified.");
64 0         0 return($num);
65             }
66             #0 means all are significant digits
67             elsif($places == 0)
68             {
69 2         3 $new_num = $num;
70 2         3 my $cur_sig_dig = getsigdig($new_num);
71 2 100       7 if($pad > $cur_sig_dig)
72             {
73             #If there's an exponent
74 1 50       14 if($new_num =~ /^([+\-]?[0-9\.]+)(e[+\-]?[0-9\.]+)$/i)
    50          
75             {
76 0         0 my $pree = $1;
77 0         0 my $e = $2;
78 0         0 return(sigdig($pree,$places,$pad).$e);
79             }
80             #If there's an exponent and no preceding number
81             elsif($new_num =~ /^([+\-]?)e[+\-]?[0-9\.]+$/i)
82             {
83 0         0 my $sign = $1;
84 0         0 my $e = $2;
85 0         0 return(sigdig($sign.'1',$places,$pad).$e);
86             }
87             #Pad the number
88             else
89             {
90 1 50       42 if($new_num !~ /\./)
  1         3  
91             {$new_num .= '.'}
92 1         3 $new_num .= '0' x ($pad - $cur_sig_dig);
93 1         3 return($new_num);
94             }
95             }
96              
97 1         3 return($new_num);
98             }
99              
100             #If there's an exponent with a preceding number
101 7 100 33     108 if($num =~ /^([+\-]?[0-9\.]*)(e[+\-]?[0-9\.]+)$/i)
    50 33        
    50          
102             {
103 1         3 my $pree = $1;
104 1         3 my $e = $2;
105 1 50       4 $pree = $pree . '1' if($pree !~ /\d/);
106 1         14 return(sigdig($pree,$places,$pad).$e);
107             }
108             elsif($num =~ /[^0-9\.+\-]/ || $num =~ /\..*\./ || $num =~ /.[+\-]/)
109             {
110 0         0 print STDERR ("ERROR:sigdig(): Invalid number format: [$num].");
111 0         0 return($num);
112             }
113             elsif($num == 0)
114             {
115 0 0       0 if($pad)
116             {
117 0         0 $new_num = '0.';
118 0 0       0 if($new_num !~ /\./)
  0         0  
119             {$new_num .= '.'}
120 0         0 $new_num .= '0' x $pad;
121 0         0 return($new_num);
122             }
123 0         0 return(0);
124             }
125              
126 6         7 my $first_real = 0;
127 6         6 my $num_added = 0;
128 6         7 my $decimal_seen = 0;
129 6         6 my $last_digit = 0;
130 6         7 my $sign = '';
131 6         35 foreach my $digit (unpack("(A)*",$num))
132             {
133 34 100       80 if($digit =~ /\+|-/)
134             {
135 1         3 $sign = $digit;
136 1         2 next;
137             }
138 33 100       75 if($digit =~ /[1-9]/)
  23 100       25  
139             {$first_real = 1}
140             elsif($digit eq '.')
141             {
142 6         7 $decimal_seen = 1;
143 6 50       19 if($new_num eq '')
  0         0  
144             {$new_num = '0'}
145 6 100       11 if($num_added < $places)
  5 50       7  
146 0         0 {$new_num .= '.'}
147             elsif($num_added > $places)
148             {last}
149 6         6 next;
150             }
151              
152 27 50       37 if($first_real)
    0          
153 0         0 {
154 27 100       37 if($num_added < $places)
    50          
    0          
155             {
156 21         21 $new_num .= $digit;
157 21         21 $num_added++;
158             }
159             elsif($num_added == $places)
160             {
161 6 100       14 if($digit >= 5)
162             {
163             #This gets rid of the decimal
164 1         8 my $tmp_num = join("",split(/\D*/,$new_num)) + 1;
165 1 50       6 if($new_num =~ /\.(\d*)$/)
166             {
167 1         2 my $len = length($1);
168 1 50       17 unless($tmp_num =~ s/(?=\d{$len}\Z)/./)
169             {
170 0 0       0 if($new_num =~ /^(0\.0+)/)
  0         0  
171             {$tmp_num = "$1$tmp_num"}
172             }
173             }
174 1         3 $new_num = $tmp_num;
175             }
176              
177             #If we haven't gotten to the end of the whole number yet
178 6 50       8 if(!$decimal_seen)
179 6         10 {
180 0         0 $new_num .= '0';
181 0         0 $num_added++;
182             }
183             else
184             {last}
185             }
186             elsif(!$decimal_seen)
187 0         0 {
188 0         0 $new_num .= '0';
189 0         0 $num_added++;
190             }
191             else
192             {last}
193 21         25 $last_digit = $digit;
194             }
195             elsif($decimal_seen)
196             {$new_num .= '0'}
197             }
198              
199 6 100       22 if($pad)
    100          
200             {
201 1         3 my $cur_sig_dig = getsigdig($new_num);
202 1 50 33     5 if($pad > $cur_sig_dig && $new_num !~ /\./)
  0         0  
203             {$new_num .= '.'}
204 1         2 $new_num .= '0' x ($pad - $cur_sig_dig);
205             }
206             #Trim the trailing zeros
207             elsif($new_num =~ /\./i)
208             {
209 4         8 $new_num =~ s/0+$//;
210 4         6 $new_num =~ s/\.$//;
211             }
212              
213 6         26 return("$sign$new_num");
214             }
215              
216             sub getsigdig
217             {
218 11     11 0 1331 my $num = $_[0];
219 11 100       19 my $nowholetail = defined($_[1]) ? $_[1] : 0;
220 11 100       18 my $nodecimaltail = defined($_[2]) ? $_[2] : 0;
221              
222             #Remove sign
223 11         28 $num =~ s/[+\-]+//g;
224              
225             #Repair exponents with assumed 1s
226 11 50       32 if($num =~ /^e/i)
  0         0  
227             {$num = "1$num"}
228             #Remove exponent
229 11         35 $num =~ s/e.*//i;
230              
231             #Remove leading zeros
232 11         18 $num =~ s/^0+//;
233              
234             #If $nowholetail is true and there's no decimal point, remove trailing
235             #whole-number zeros
236 11 100 100     31 $num =~ s/0+$// if($nowholetail && $num !~ /\./);
237              
238             #Remove decimal point
239 11         31 $num =~ s/\.//;
240              
241             #If $nodecimaltail is true, remove all trailing zeros
242 11 100       20 $num =~ s/0+$// if($nodecimaltail);
243              
244 11         22 return(length($num));
245             }
246              
247              
248              
249              
250              
251              
252             1;
253             __END__