File Coverage

blib/lib/Math/Float32.pm
Criterion Covered Total %
statement 171 204 83.8
branch 83 136 61.0
condition 55 77 71.4
subroutine 32 32 100.0
pod 0 23 0.0
total 341 472 72.2


line stmt bran cond sub pod time code
1 10     10   41825 use strict;
  10         17  
  10         272  
2 10     10   77 use warnings;
  10         16  
  10         614  
3             package Math::Float32;
4              
5 10     10   47 use constant flt_EMIN => -148;
  10         24  
  10         1007  
6 10     10   49 use constant flt_EMAX => 128;
  10         37  
  10         537  
7 10     10   40 use constant flt_MANTBITS => 24;
  10         12  
  10         1679  
8              
9              
10             use overload
11 10         178 '+' => \&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   5335 ;
  10         15247  
35              
36             require Exporter;
37             *import = \&Exporter::import;
38             require DynaLoader;
39              
40             our $VERSION = '0.03';
41             Math::Float32->DynaLoader::bootstrap($VERSION);
42              
43 10     10 0 3344 sub dl_load_flags {0} # Prevent DynaLoader from complaining and croaking
44              
45              
46              
47             my @tagged = qw( flt_to_NV
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 1369467 shift if (@_ > 0 && !ref($_[0]) && _itsa($_[0]) == 4 && $_[0] eq "Math::Float32");
      66        
      33        
82 9022 100       16696 if(!@_) { return _fromPV('NaN');}
  11         93  
83 9011 50       14321 die "Too many args given to new()" if @_ > 1;
84 9011         16474 my $itsa = _itsa($_[0]);
85 9011 50       14864 if($itsa) {
86 9011         14404 my $coderef = $Math::Float32::handler{$itsa};
87 9011 100 100     59656 return $coderef->(bin2hex($_[0]))
88             if($itsa == 4 && $_[0] =~ /^(\s+)?[\-\+]?0b/i);
89 8933         18571 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 6 die "flt_set expects to receive precisely 2 arguments" if @_ != 2;
96 1         3 my $itsa = _itsa($_[1]);
97 1 50       3 if($itsa == 22) { _flt_set(@_) }
  0         0  
98             else {
99 1         2 my $coderef = $Math::Float32::handler{$itsa};
100 1         2 _flt_set( $_[0], $coderef->($_[1]));
101             }
102             }
103              
104             sub oload_add {
105 101     101 0 3996 my $itsa = _itsa($_[1]);
106 101 100       173 return _oload_add(@_) if $itsa == 22;
107 100 50       169 if($itsa < 5) {
108 100         115 my $coderef = $Math::Float32::handler{$itsa};
109 100 100 100     206 return _oload_add($_[0], $coderef->(bin2hex($_[1])), 0)
110             if($itsa == 4 && $_[1] =~ /^(\s+)?[\-\+]?0b/i);
111 99         142 return _oload_add($_[0], $coderef->($_[1]), 0);
112             }
113 0 0       0 return Math::Bfloat16::oload_add($_[1], $_[0], $_[2]) if $itsa == 20;
114 0         0 die "Unrecognized 2nd argument passed to oload_add() function";
115             }
116              
117             sub oload_mul {
118 141     141 0 4782 my $itsa = _itsa($_[1]);
119 141 100       607 return _oload_mul(@_) if $itsa == 22;
120 77 50       140 if($itsa < 5) {
121 77         116 my $coderef = $Math::Float32::handler{$itsa};
122 77 100 100     220 return _oload_mul($_[0], $coderef->(bin2hex($_[1])), 0)
123             if($itsa == 4 && $_[1] =~ /^(\s+)?[\-\+]?0b/i);
124 76         125 return _oload_mul($_[0], $coderef->($_[1]), 0);
125             }
126 0 0       0 return Math::Bfloat16::oload_mul($_[1], $_[0], $_[2]) if $itsa == 20;
127 0         0 die "Unrecognized 2nd argument passed to oload_mul() function";
128             }
129              
130             sub oload_sub {
131 181     181 0 6568 my $itsa = _itsa($_[1]);
132 181 100       459 return _oload_sub(@_) if $itsa == 22;
133 179 50       373 if($itsa < 5) {
134 179         337 my $coderef = $Math::Float32::handler{$itsa};
135 179 100 100     522 return _oload_sub($_[0], $coderef->(bin2hex($_[1])), $_[2])
136             if($itsa == 4 && $_[1] =~ /^(\s+)?[\-\+]?0b/i);
137 177         391 return _oload_sub($_[0], $coderef->($_[1]), $_[2]);
138             }
139 0 0       0 return Math::Bfloat16::oload_sub($_[1], $_[0], 1) if $itsa == 20;
140 0         0 die "Unrecognized 2nd argument passed to oload_sub() function";
141             }
142              
143             sub oload_div {
144 107     107 0 4391 my $itsa = _itsa($_[1]);
145 107 50       276 return _oload_div(@_) if $itsa == 22;
146 107 50       253 if($itsa < 5) {
147 107         163 my $coderef = $Math::Float32::handler{$itsa};
148 107 100 100     327 return _oload_div($_[0], $coderef->(bin2hex($_[1])), $_[2])
149             if($itsa == 4 && $_[1] =~ /^(\s+)?[\-\+]?0b/i);
150 105         194 return _oload_div($_[0], $coderef->($_[1]), $_[2]);
151             }
152 0 0       0 return Math::Bfloat16::oload_div($_[1], $_[0], 1) if $itsa == 20;
153 0         0 die "Unrecognized 2nd argument passed to oload_div() function";
154             }
155              
156             sub oload_fmod {
157 12     12 0 350 my $itsa = _itsa($_[1]);
158 12 100       99 return _oload_fmod(@_) if $itsa == 22;
159 2 50       5 if($itsa < 5) {
160 2         4 my $coderef = $Math::Float32::handler{$itsa};
161 2 50 33     21 return _oload_fmod($_[0], $coderef->(bin2hex($_[1])), $_[2])
162             if($itsa == 4 && $_[1] =~ /^(\s+)?[\-\+]?0b/i);
163 0         0 return _oload_fmod($_[0], $coderef->($_[1]), $_[2]);
164             }
165 0 0       0 return Math::Bfloat16::oload_fmod($_[1], $_[0], 1) if $itsa == 20;
166 0         0 die "Unrecognized 2nd argument passed to oload_fmod() function";
167             }
168              
169             sub oload_pow {
170 28     28 0 76 my $itsa = _itsa($_[1]);
171 28 50       92 return _oload_pow(@_) if $itsa == 22;
172 28 50       53 if($itsa < 5) {
173 28         38 my $coderef = $Math::Float32::handler{$itsa};
174 28 100 66     79 return _oload_pow($_[0], $coderef->(bin2hex($_[1])), $_[2])
175             if($itsa == 4 && $_[1] =~ /^(\s+)?[\-\+]?0b/i);
176 26         44 return _oload_pow($_[0], $coderef->($_[1]), $_[2]);
177             }
178 0 0       0 return Math::Bfloat16::oload_pow($_[1], $_[0], 1) if $itsa == 20;
179 0         0 die "Unrecognized 2nd argument passed to oload_pow() function";
180             }
181              
182             sub oload_abs {
183 2 100   2 0 10 return $_[0] * -1 if $_[0] < 0;
184 1         3 return $_[0];
185             }
186              
187             sub oload_equiv {
188 8662     8662 0 2548727 my $itsa = _itsa($_[1]);
189 8662 50 66     23628 if($itsa == 22 || $itsa < 5) {
190 8662         14584 my $coderef = $Math::Float32::handler{$itsa};
191 8662 100 100     15418 return _oload_equiv($_[0], $coderef->(bin2hex($_[1])), 0)
192             if($itsa == 4 && $_[1] =~ /^(\s+)?[\-\+]?0b/i);
193 8660         15985 return _oload_equiv($_[0], $coderef->($_[1]), 0);
194             }
195 0 0       0 return Math::Bfloat16::oload_equiv($_[1], $_[0], 0) if $itsa == 20;
196 0         0 die "Unrecognized 2nd argument passed to oload_equiv() function";
197             }
198              
199             sub oload_not_equiv {
200 14     14 0 5120 my $itsa = _itsa($_[1]);
201 14 50 66     47 if($itsa == 22 || $itsa < 5) {
202 14         25 my $coderef = $Math::Float32::handler{$itsa};
203 14 50 66     29 return _oload_not_equiv($_[0], $coderef->(bin2hex($_[1])), 0)
204             if($itsa == 4 && $_[1] =~ /^(\s+)?[\-\+]?0b/i);
205 14         63 return _oload_not_equiv($_[0], $coderef->($_[1]), 0);
206             }
207 0 0       0 return Math::Bfloat16::oload_not_equiv($_[1], $_[0], 0) if $itsa == 20;
208 0         0 die "Unrecognized 2nd argument passed to oload_not_equiv() function";
209             }
210              
211             sub oload_gt {
212 26     26 0 3091 my $itsa = _itsa($_[1]);
213 26 50 33     75 if($itsa == 22 || $itsa < 5) {
214 26         36 my $coderef = $Math::Float32::handler{$itsa};
215 26 100 100     65 return _oload_gt($_[0], $coderef->(bin2hex($_[1])), $_[2])
216             if($itsa == 4 && $_[1] =~ /^(\s+)?[\-\+]?0b/i);
217 24         34 return _oload_gt($_[0], $coderef->($_[1]), $_[2]);
218             }
219 0 0       0 return Math::Bfloat16::oload_lt($_[1], $_[0], 0) if $itsa == 20;
220 0         0 die "Unrecognized 2nd argument passed to oload_gt() function";
221             }
222              
223             sub oload_gte {
224 66     66 0 15388 my $itsa = _itsa($_[1]);
225 66 50 33     240 if($itsa == 22 || $itsa < 5) {
226 66         117 my $coderef = $Math::Float32::handler{$itsa};
227 66 100 100     145 return _oload_gte($_[0], $coderef->(bin2hex($_[1])), $_[2])
228             if($itsa == 4 && $_[1] =~ /^(\s+)?[\-\+]?0b/i);
229 62         95 return _oload_gte($_[0], $coderef->($_[1]), $_[2]);
230             }
231 0 0       0 return Math::Bfloat16::oload_lte($_[1], $_[0], 0) if $itsa == 20;
232 0         0 die "Unrecognized 2nd argument passed to oload_gte() function";
233             }
234              
235             sub oload_lt {
236 28     28 0 3136 my $itsa = _itsa($_[1]);
237 28 50 33     89 if($itsa == 22 || $itsa < 5) {
238 28         37 my $coderef = $Math::Float32::handler{$itsa};
239 28 100 100     92 return _oload_lt($_[0], $coderef->(bin2hex($_[1])), $_[2])
240             if($itsa == 4 && $_[1] =~ /^(\s+)?[\-\+]?0b/i);
241 26         41 return _oload_lt($_[0], $coderef->($_[1]), $_[2]);
242             }
243 0 0       0 return Math::Bfloat16::oload_gt($_[1], $_[0], 0) if $itsa == 20;
244 0         0 die "Unrecognized 2nd argument passed to oload_lt() function";
245             }
246              
247             sub oload_lte {
248 66     66 0 15432 my $itsa = _itsa($_[1]);
249 66 50 33     249 if($itsa == 22 || $itsa < 5) {
250 66         106 my $coderef = $Math::Float32::handler{$itsa};
251 66 100 100     160 return _oload_lte($_[0], $coderef->(bin2hex($_[1])), $_[2])
252             if($itsa == 4 && $_[1] =~ /^(\s+)?[\-\+]?0b/i);
253 62         100 return _oload_lte($_[0], $coderef->($_[1]), $_[2]);
254             }
255 0 0       0 return Math::Bfloat16::oload_gte($_[1], $_[0], 0) if $itsa == 20;
256 0         0 die "Unrecognized 2nd argument passed to oload_lte() function";
257             }
258              
259             sub oload_spaceship {
260 48     48 0 1452 my $itsa = _itsa($_[1]);
261 48 50 33     135 if($itsa == 22 || $itsa < 5) {
262 48         63 my $coderef = $Math::Float32::handler{$itsa};
263 48 100 100     138 return _oload_spaceship($_[0], $coderef->(bin2hex($_[1])), $_[2])
264             if($itsa == 4 && $_[1] =~ /^(\s+)?[\-\+]?0b/i);
265 46         66 return _oload_spaceship($_[0], $coderef->($_[1]), $_[2]);
266             }
267 0 0       0 if($itsa == 20) {
268 0         0 my $ret = Math::Bfloat16::oload_spaceship($_[1], $_[0], 0);
269 0 0       0 return undef if !defined($ret);
270 0         0 return $ret * -1;
271             }
272 0         0 die "Unrecognized 2nd argument passed to oload_spaceship() function";
273             }
274              
275             sub oload_interp {
276 270     270 0 3300 return sprintf("%.9g", flt_to_NV($_[0]));
277             }
278              
279             sub is_flt_zero {
280 3 50   3 0 1408 if($_[0] == 0) {
281 3 100       8 return -1 if flt_signbit($_[0]);
282 2         9 return 1;
283             }
284 0         0 return 0;
285             }
286              
287             sub flt_signbit {
288 13 100   13 0 1499 return 1 if hex(substr(unpack_flt_hex($_[0]), 0, 1)) >= 8;
289 7         21 return 0;
290             }
291              
292             sub unpack_flt_hex {
293 8310     8310 0 6987105 my @ret = _unpack_flt_hex($_[0]);
294 8310         29730 return join('', @ret);
295             }
296              
297             sub pack_flt_hex {
298 8284     8284 0 25477 my $arg = shift;
299 8284         11390 my $is_neg = '';
300 8284 50 33     49387 die "Invalid argument ($arg) given to pack_flt_hex"
301             if(length($arg) != 8 || $arg =~ /[^0-9a-fA-F]/);
302              
303 8284         33490 my $binstr = unpack 'B32', pack 'H8', $arg;
304 8284 100       22051 $is_neg = '-' if substr($binstr, 0, 1) eq '1';
305 8284         22965 my $power = oct('0b' .substr($binstr,1, 8)) - 127;
306 8284         9654 my $prefix = '1';
307 8284 100       15144 if($power < -126) { # Subnormal
308 4142         4726 $power = -126;
309 4142         5010 $prefix = '0';
310             }
311              
312             # Unfortunately, C's strtof function (which is used by
313             # Math::Float32::new() does not accommodate binary strings,
314             # so we have to convert the binary string to its hex
315             # equivalent before passing it to new().
316 8284         10841 $power -= 23;
317 8284         28836 my $hexstring = '0x' . lc(unpack 'H6', pack('B24', $prefix . substr($binstr,9, 23)));
318 8284         33879 return Math::Float32->new($is_neg . $hexstring . "p$power");
319             }
320              
321             sub bin2hex {
322 152     152 0 232892 my $arg = shift; # It is assumed that $arg =~ /^(\s+)?[\-\+]?0b/i
323 152         378 $arg =~ s/^\s+//;
324 152 50       452 die "Illegal character(s) in arg ($arg) passed to bin2hex"
325             if $arg =~ /[^0-9peb\.\-\+]/i;
326 152         280 my($is_neg, $point_index) = ('');
327 152 100       321 $is_neg = '-' if $arg =~ /^\-/;
328              
329 152         570 $arg =~ s/^[\-\+]?0b//i;
330              
331             # Remove all leading zeroes, but retain a leading
332             # '0' if (and only if) it is succeeded by a '.'.
333 152         560 substr($arg, 0, 1, '') while $arg =~ /^0[^\.]/;
334              
335 152         292 $arg =~ s/e/p/i;
336 152         424 my @args = split /p/i, $arg;
337              
338             { # Start no warnings 'uninitialized'
339              
340 10     10   35266 no warnings 'uninitialized'; # $args[0] might be uninitialized
  10         13  
  10         4584  
  152         205  
341             # Remove trailing zeroes from beyond the
342             # radix point and remove a trailing '.' (if present)
343 152 100       525 $args[0] =~ s/0+$// if $args[0] =~ /\./;
344 152         308 $args[0] =~ s/\.$//;
345              
346 152   100     410 $args[1] //= 0;
347 152         253 $point_index = index($args[0], '.');
348 152 100       308 if ($args[0] =~ s/^0\.//) {
349 8         21 $args[1]--;
350 8         25 while($args[0] =~ /^0/) {
351 12         32 substr($args[0], 0, 1, '');
352 12         27 $args[1]--;
353             }
354             }
355 152 100       572 return $is_neg . '0x0p0' if $args[0] !~ /1/;
356              
357             } # End no warnings 'uninitialized'
358              
359 122         279 $args[0] =~ s/\.//;
360              
361 122         206 my $pad = length($args[0]) % 4;
362 122 100       217 if($pad) {
363 44         43 $pad = 4 - $pad;
364 44         77 $args[0] .= '0' x $pad;
365 44 100       78 $args[1] -= $pad if $point_index < 0; # The string did not contain a radix point
366             }
367              
368 122         174 my $B_quantity = length($args[0]);
369 122         221 my $H_quantity = $B_quantity / 4;
370              
371             # It may well be that the case (ie "lower" or "upper") makes no difference.
372             # Out of caution, I'll specify lower case and use the (matching) '0x' prefix.
373 122         869 my $mantissa = lc(unpack "H$H_quantity", pack "B$B_quantity", $args[0]);
374              
375 122 100       431 return $is_neg . '0x' . $mantissa . "p$args[1]" if $point_index < 0;
376 70         160 my $exponent = $point_index - $B_quantity + $args[1];
377 70         355 return $is_neg . '0x' . $mantissa . "p$exponent";
378             }
379              
380             sub _get_norm_max {
381 10     10   13 my $ret = 0;
382 10         24 for my $p(1 .. flt_MANTBITS) { $ret += 2 ** (flt_EMAX - $p) }
  240         254  
383 10         33 return $ret;
384             }
385              
386             sub _get_denorm_max {
387 10     10   12 my $ret = 0;
388 10         14 my $max = -(flt_EMIN - 1);
389 10         15 my $min = $max - (flt_MANTBITS - 2);
390 10         27 for my $p($min .. $max) { $ret += 2 ** -$p }
  230         272  
391 10         27 return $ret;
392             }
393              
394             1;
395              
396             __END__