File Coverage

blib/lib/Math/Float32.pm
Criterion Covered Total %
statement 171 188 90.9
branch 83 108 76.8
condition 55 77 71.4
subroutine 32 32 100.0
pod 0 23 0.0
total 341 428 79.6


line stmt bran cond sub pod time code
1 10     10   59238 use strict;
  10         20  
  10         374  
2 10     10   115 use warnings;
  10         17  
  10         738  
3             package Math::Float32;
4              
5 10     10   63 use constant flt_EMIN => -148;
  10         33  
  10         1196  
6 10     10   63 use constant flt_EMAX => 128;
  10         23  
  10         664  
7 10     10   61 use constant flt_MANTBITS => 24;
  10         20  
  10         2318  
8              
9              
10             use overload
11 10         197 '+' => \&oload_add,
12             '-' => \&oload_sub,
13             '*' => \&oload_mul,
14             '/' => \&oload_div,
15             '%' => \&oload_fmod,
16             '**' => \&oload_pow,
17              
18             '==' => \&oload_equiv,
19             '!=' => \&oload_not_equiv,
20             '>' => \&oload_gt,
21             '>=' => \&oload_gte,
22             '<' => \&oload_lt,
23             '<=' => \&oload_lte,
24             '<=>' => \&oload_spaceship,
25              
26             'abs' => \&oload_abs,
27             '""' => \&oload_interp,
28             'sqrt' => \&_oload_sqrt,
29             'exp' => \&_oload_exp,
30             'log' => \&_oload_log,
31             'int' => \&_oload_int,
32             '!' => \&_oload_not,
33             'bool' => \&_oload_bool,
34 10     10   6723 ;
  10         19574  
35              
36             require Exporter;
37             *import = \&Exporter::import;
38             require DynaLoader;
39              
40             our $VERSION = '0.02';
41             Math::Float32->DynaLoader::bootstrap($VERSION);
42              
43 10     10 0 4248 sub dl_load_flags {0} # Prevent DynaLoader from complaining and croaking
44              
45              
46              
47             my @tagged = qw( flt_to_NV flt_to_MPFR
48             is_flt_nan is_flt_inf is_flt_zero flt_set_nan flt_set_inf flt_set_zero
49             flt_signbit
50             flt_set
51             flt_nextabove flt_nextbelow
52             unpack_flt_hex pack_flt_hex
53             flt_EMIN flt_EMAX flt_MANTBITS
54             );
55              
56             @Math::Float32::EXPORT = ();
57             @Math::Float32::EXPORT_OK = @tagged;
58             %Math::Float32::EXPORT_TAGS = (all => \@tagged);
59              
60              
61             %Math::Float32::handler = (1 => sub {print "OK: 1\n"},
62             2 => sub {return _fromIV(shift)},
63             4 => sub {return _fromPV(shift)},
64             3 => sub {return _fromNV(shift)},
65              
66             22 => sub {return _fromFloat32(shift)},
67             );
68              
69             $Math::Float32::flt_DENORM_MIN = Math::Float32->new(2) ** (flt_EMIN - 1); # 1.40129846e-45
70             $Math::Float32::flt_DENORM_MAX = Math::Float32->new(_get_denorm_max()); # 1.17549421e-38
71             $Math::Float32::flt_NORM_MIN = Math::Float32->new(2) ** (flt_EMIN + (flt_MANTBITS - 2)); # 1.17549435e-38
72             $Math::Float32::flt_NORM_MAX = Math::Float32->new(_get_norm_max()); # 3.402823467e+38
73              
74              
75             # Skip signed zero tests in the test suite if C's strtof()
76             # does not handle '-0' correctly.
77             my $signed_zero_tester = Math::Float32->new('-0.0');
78             $Math::Float32::broken_signed_zero = "$signed_zero_tester" =~ /^\-/ ? 0 : 1;
79              
80             sub new {
81 9022 50 66 9022 0 2291944 shift if (@_ > 0 && !ref($_[0]) && _itsa($_[0]) == 4 && $_[0] eq "Math::Float32");
      66        
      33        
82 9022 100       24632 if(!@_) { return _fromPV('NaN');}
  11         145  
83 9011 50       20263 die "Too many args given to new()" if @_ > 1;
84 9011         18666 my $itsa = _itsa($_[0]);
85 9011 50       20578 if($itsa) {
86 9011         21221 my $coderef = $Math::Float32::handler{$itsa};
87 9011 100 100     90275 return $coderef->(bin2hex($_[0]))
88             if($itsa == 4 && $_[0] =~ /^(\s+)?[\-\+]?0b/i);
89 8933         31566 return $coderef->($_[0]);
90             }
91 0         0 die "Unrecognized 1st argument passed to new() function";
92             }
93              
94             sub flt_set {
95 1 50   1 0 34 die "flt_set expects to receive precisely 2 arguments" if @_ != 2;
96 1         6 my $itsa = _itsa($_[1]);
97 1 50       6 if($itsa == 22) { _flt_set(@_) }
  0         0  
98             else {
99 1         4 my $coderef = $Math::Float32::handler{$itsa};
100 1         3 _flt_set( $_[0], $coderef->($_[1]));
101             }
102             }
103              
104             sub oload_add {
105 101     101 0 6894 my $itsa = _itsa($_[1]);
106 101 100       261 return _oload_add(@_) if $itsa == 22;
107 100 50       267 if($itsa < 5) {
108 100         200 my $coderef = $Math::Float32::handler{$itsa};
109 100 100 100     354 return _oload_add($_[0], $coderef->(bin2hex($_[1])), 0)
110             if($itsa == 4 && $_[1] =~ /^(\s+)?[\-\+]?0b/i);
111 99         262 return _oload_add($_[0], $coderef->($_[1]), 0);
112             }
113 0         0 die "Unrecognized 2nd argument passed to oload_add() function";
114             }
115              
116             sub oload_mul {
117 141     141 0 7657 my $itsa = _itsa($_[1]);
118 141 100       718 return _oload_mul(@_) if $itsa == 22;
119 77 50       204 if($itsa < 5) {
120 77         175 my $coderef = $Math::Float32::handler{$itsa};
121 77 100 100     295 return _oload_mul($_[0], $coderef->(bin2hex($_[1])), 0)
122             if($itsa == 4 && $_[1] =~ /^(\s+)?[\-\+]?0b/i);
123 76         172 return _oload_mul($_[0], $coderef->($_[1]), 0);
124             }
125 0         0 die "Unrecognized 2nd argument passed to oload_mul() function";
126             }
127              
128             sub oload_sub {
129 181     181 0 13260 my $itsa = _itsa($_[1]);
130 181 100       13424 return _oload_sub(@_) if $itsa == 22;
131 179 50       460 if($itsa < 5) {
132 179         435 my $coderef = $Math::Float32::handler{$itsa};
133 179 100 100     642 return _oload_sub($_[0], $coderef->(bin2hex($_[1])), $_[2])
134             if($itsa == 4 && $_[1] =~ /^(\s+)?[\-\+]?0b/i);
135 177         416 return _oload_sub($_[0], $coderef->($_[1]), $_[2]);
136             }
137 0         0 die "Unrecognized 2nd argument passed to oload_sub() function";
138             }
139              
140             sub oload_div {
141 107     107 0 8201 my $itsa = _itsa($_[1]);
142 107 50       250 return _oload_div(@_) if $itsa == 22;
143 107 50       244 if($itsa < 5) {
144 107         220 my $coderef = $Math::Float32::handler{$itsa};
145 107 100 100     389 return _oload_div($_[0], $coderef->(bin2hex($_[1])), $_[2])
146             if($itsa == 4 && $_[1] =~ /^(\s+)?[\-\+]?0b/i);
147 105         215 return _oload_div($_[0], $coderef->($_[1]), $_[2]);
148             }
149 0         0 die "Unrecognized 2nd argument passed to oload_div() function";
150             }
151              
152             sub oload_fmod {
153 12     12 0 455 my $itsa = _itsa($_[1]);
154 12 100       106 return _oload_fmod(@_) if $itsa == 22;
155 2 50       7 if($itsa < 5) {
156 2         6 my $coderef = $Math::Float32::handler{$itsa};
157 2 50 33     30 return _oload_fmod($_[0], $coderef->(bin2hex($_[1])), $_[2])
158             if($itsa == 4 && $_[1] =~ /^(\s+)?[\-\+]?0b/i);
159 0         0 return _oload_fmod($_[0], $coderef->($_[1]), $_[2]);
160             }
161 0         0 die "Unrecognized 2nd argument passed to oload_fmod() function";
162             }
163              
164             sub oload_pow {
165 28     28 0 128 my $itsa = _itsa($_[1]);
166 28 50       94 return _oload_pow(@_) if $itsa == 22;
167 28 50       77 if($itsa < 5) {
168 28         60 my $coderef = $Math::Float32::handler{$itsa};
169 28 100 66     104 return _oload_pow($_[0], $coderef->(bin2hex($_[1])), $_[2])
170             if($itsa == 4 && $_[1] =~ /^(\s+)?[\-\+]?0b/i);
171 26         60 return _oload_pow($_[0], $coderef->($_[1]), $_[2]);
172             }
173 0         0 die "Unrecognized 2nd argument passed to oload_pow() function";
174             }
175              
176             sub oload_abs {
177 2 100   2 0 10 return $_[0] * -1 if $_[0] < 0;
178 1         3 return $_[0];
179             }
180              
181             sub oload_equiv {
182 8662     8662 0 3757263 my $itsa = _itsa($_[1]);
183 8662 50 66     34960 if($itsa == 22 || $itsa < 5) {
184 8662         23419 my $coderef = $Math::Float32::handler{$itsa};
185 8662 100 100     28554 return _oload_equiv($_[0], $coderef->(bin2hex($_[1])), 0)
186             if($itsa == 4 && $_[1] =~ /^(\s+)?[\-\+]?0b/i);
187 8660         26576 return _oload_equiv($_[0], $coderef->($_[1]), 0);
188             }
189 0         0 die "Unrecognized 2nd argument passed to oload_equiv() function";
190             }
191              
192             sub oload_not_equiv {
193 14     14 0 8729 my $itsa = _itsa($_[1]);
194 14 50 66     68 if($itsa == 22 || $itsa < 5) {
195 14         42 my $coderef = $Math::Float32::handler{$itsa};
196 14 50 66     69 return _oload_not_equiv($_[0], $coderef->(bin2hex($_[1])), 0)
197             if($itsa == 4 && $_[1] =~ /^(\s+)?[\-\+]?0b/i);
198 14         43 return _oload_not_equiv($_[0], $coderef->($_[1]), 0);
199             }
200 0         0 die "Unrecognized 2nd argument passed to oload_not_equiv() function";
201             }
202              
203             sub oload_gt {
204 26     26 0 6442 my $itsa = _itsa($_[1]);
205 26 50 33     171 if($itsa == 22 || $itsa < 5) {
206 26         67 my $coderef = $Math::Float32::handler{$itsa};
207 26 100 100     124 return _oload_gt($_[0], $coderef->(bin2hex($_[1])), $_[2])
208             if($itsa == 4 && $_[1] =~ /^(\s+)?[\-\+]?0b/i);
209 24         64 return _oload_gt($_[0], $coderef->($_[1]), $_[2]);
210             }
211 0         0 die "Unrecognized 2nd argument passed to oload_gt() function";
212             }
213              
214             sub oload_gte {
215 66     66 0 25134 my $itsa = _itsa($_[1]);
216 66 50 33     818 if($itsa == 22 || $itsa < 5) {
217 66         183 my $coderef = $Math::Float32::handler{$itsa};
218 66 100 100     286 return _oload_gte($_[0], $coderef->(bin2hex($_[1])), $_[2])
219             if($itsa == 4 && $_[1] =~ /^(\s+)?[\-\+]?0b/i);
220 62         156 return _oload_gte($_[0], $coderef->($_[1]), $_[2]);
221             }
222 0         0 die "Unrecognized 2nd argument passed to oload_gte() function";
223             }
224              
225             sub oload_lt {
226 28     28 0 5222 my $itsa = _itsa($_[1]);
227 28 50 33     192 if($itsa == 22 || $itsa < 5) {
228 28         69 my $coderef = $Math::Float32::handler{$itsa};
229 28 100 100     116 return _oload_lt($_[0], $coderef->(bin2hex($_[1])), $_[2])
230             if($itsa == 4 && $_[1] =~ /^(\s+)?[\-\+]?0b/i);
231 26         63 return _oload_lt($_[0], $coderef->($_[1]), $_[2]);
232             }
233 0         0 die "Unrecognized 2nd argument passed to oload_lt() function";
234             }
235              
236             sub oload_lte {
237 66     66 0 24083 my $itsa = _itsa($_[1]);
238 66 50 33     379 if($itsa == 22 || $itsa < 5) {
239 66         156 my $coderef = $Math::Float32::handler{$itsa};
240 66 100 100     273 return _oload_lte($_[0], $coderef->(bin2hex($_[1])), $_[2])
241             if($itsa == 4 && $_[1] =~ /^(\s+)?[\-\+]?0b/i);
242 62         188 return _oload_lte($_[0], $coderef->($_[1]), $_[2]);
243             }
244 0         0 die "Unrecognized 2nd argument passed to oload_lte() function";
245             }
246              
247             sub oload_spaceship {
248 48     48 0 2426 my $itsa = _itsa($_[1]);
249 48 50 33     355 if($itsa == 22 || $itsa < 5) {
250 48         158 my $coderef = $Math::Float32::handler{$itsa};
251 48 100 100     202 return _oload_spaceship($_[0], $coderef->(bin2hex($_[1])), $_[2])
252             if($itsa == 4 && $_[1] =~ /^(\s+)?[\-\+]?0b/i);
253 46         102 return _oload_spaceship($_[0], $coderef->($_[1]), $_[2]);
254             }
255 0         0 die "Unrecognized 2nd argument passed to oload_spaceship() function";
256             }
257              
258             sub oload_interp {
259 270     270 0 3784 return sprintf("%.9g", flt_to_NV($_[0]));
260             }
261              
262             sub is_flt_zero {
263 3 50   3 0 2508 if($_[0] == 0) {
264 3 100       12 return -1 if flt_signbit($_[0]);
265 2         44 return 1;
266             }
267 0         0 return 0;
268             }
269              
270             sub flt_signbit {
271 13 100   13 0 2243 return 1 if hex(substr(unpack_flt_hex($_[0]), 0, 1)) >= 8;
272 7         28 return 0;
273             }
274              
275             sub flt_nextabove {
276             if(is_flt_zero($_[0])) {
277             flt_set($_[0], $Math::Float32::flt_DENORM_MIN);
278             }
279             elsif($_[0] < $Math::Float32::flt_NORM_MIN && $_[0] >= -$Math::Float32::flt_NORM_MIN ) {
280             $_[0] += $Math::Float32::flt_DENORM_MIN;
281             flt_set_zero($_[0], -1) if is_flt_zero($_[0]);
282             }
283             else {
284             _flt_nextabove($_[0]);
285             }
286             }
287              
288             sub flt_nextbelow {
289             if(is_flt_zero($_[0])) {
290             flt_set($_[0], -$Math::Float32::flt_DENORM_MIN);
291             }
292             elsif($_[0] <= $Math::Float32::flt_NORM_MIN && $_[0] > -$Math::Float32::flt_NORM_MIN ) {
293             $_[0] -= $Math::Float32::flt_DENORM_MIN;
294             }
295             else {
296             _flt_nextbelow($_[0]);
297             }
298             }
299              
300             sub unpack_flt_hex {
301 8310     8310 0 10361712 my @ret = _unpack_flt_hex($_[0]);
302 8310         57995 return join('', @ret);
303             }
304              
305             sub pack_flt_hex {
306 8284     8284 0 41126 my $arg = shift;
307 8284         15406 my $is_neg = '';
308 8284 50 33     81880 die "Invalid argument ($arg) given to pack_flt_hex"
309             if(length($arg) != 8 || $arg =~ /[^0-9a-fA-F]/);
310              
311 8284         51247 my $binstr = unpack 'B32', pack 'H8', $arg;
312 8284 100       34443 $is_neg = '-' if substr($binstr, 0, 1) eq '1';
313 8284         33286 my $power = oct('0b' .substr($binstr,1, 8)) - 127;
314 8284         13020 my $prefix = '1';
315 8284 100       23385 if($power < -126) { # Subnormal
316 4142         6559 $power = -126;
317 4142         6676 $prefix = '0';
318             }
319              
320             # Unfortunately, C's strtof function (which is used by
321             # Math::Float32::new() does not accommodate binary strings,
322             # so we have to convert the binary string to its hex
323             # equivalent before passing it to new().
324 8284         13896 $power -= 23;
325 8284         44100 my $hexstring = '0x' . lc(unpack 'H6', pack('B24', $prefix . substr($binstr,9, 23)));
326 8284         54363 return Math::Float32->new($is_neg . $hexstring . "p$power");
327             }
328              
329             sub bin2hex {
330 152     152 0 276722 my $arg = shift; # It is assumed that $arg =~ /^(\s+)?[\-\+]?0b/i
331 152         483 $arg =~ s/^\s+//;
332 152 50       575 die "Illegal character(s) in arg ($arg) passed to bin2hex"
333             if $arg =~ /[^0-9peb\.\-\+]/i;
334 152         377 my($is_neg, $point_index) = ('');
335 152 100       432 $is_neg = '-' if $arg =~ /^\-/;
336              
337 152         662 $arg =~ s/^[\-\+]?0b//i;
338              
339             # Remove all leading zeroes, but retain a leading
340             # '0' if (and only if) it is succeeded by a '.'.
341 152         692 substr($arg, 0, 1, '') while $arg =~ /^0[^\.]/;
342              
343 152         336 $arg =~ s/e/p/i;
344 152         472 my @args = split /p/i, $arg;
345              
346             { # Start no warnings 'uninitialized'
347              
348 10     10   45838 no warnings 'uninitialized'; # $args[0] might be uninitialized
  10         22  
  10         8141  
  152         256  
349             # Remove trailing zeroes from beyond the
350             # radix point and remove a trailing '.' (if present)
351 152 100       709 $args[0] =~ s/0+$// if $args[0] =~ /\./;
352 152         357 $args[0] =~ s/\.$//;
353              
354 152   100     543 $args[1] //= 0;
355 152         274 $point_index = index($args[0], '.');
356 152 100       406 if ($args[0] =~ s/^0\.//) {
357 8         19 $args[1]--;
358 8         24 while($args[0] =~ /^0/) {
359 12         31 substr($args[0], 0, 1, '');
360 12         28 $args[1]--;
361             }
362             }
363 152 100       587 return $is_neg . '0x0p0' if $args[0] !~ /1/;
364              
365             } # End no warnings 'uninitialized'
366              
367 122         264 $args[0] =~ s/\.//;
368              
369 122         255 my $pad = length($args[0]) % 4;
370 122 100       311 if($pad) {
371 44         79 $pad = 4 - $pad;
372 44         126 $args[0] .= '0' x $pad;
373 44 100       129 $args[1] -= $pad if $point_index < 0; # The string did not contain a radix point
374             }
375              
376 122         189 my $B_quantity = length($args[0]);
377 122         268 my $H_quantity = $B_quantity / 4;
378              
379             # It may well be that the case (ie "lower" or "upper") makes no difference.
380             # Out of caution, I'll specify lower case and use the (matching) '0x' prefix.
381 122         1056 my $mantissa = lc(unpack "H$H_quantity", pack "B$B_quantity", $args[0]);
382              
383 122 100       541 return $is_neg . '0x' . $mantissa . "p$args[1]" if $point_index < 0;
384 70         211 my $exponent = $point_index - $B_quantity + $args[1];
385 70         354 return $is_neg . '0x' . $mantissa . "p$exponent";
386             }
387              
388             sub _get_norm_max {
389 10     10   18 my $ret = 0;
390 10         28 for my $p(1 .. flt_MANTBITS) { $ret += 2 ** (flt_EMAX - $p) }
  240         379  
391 10         47 return $ret;
392             }
393              
394             sub _get_denorm_max {
395 10     10   15 my $ret = 0;
396 10         42 my $max = -(flt_EMIN - 1);
397 10         19 my $min = $max - (flt_MANTBITS - 2);
398 10         33 for my $p($min .. $max) { $ret += 2 ** -$p }
  230         391  
399 10         42 return $ret;
400             }
401              
402             1;
403              
404             __END__