File Coverage

blib/lib/Number/Denominal.pm
Criterion Covered Total %
statement 85 85 100.0
branch 43 46 93.4
condition 18 20 90.0
subroutine 12 12 100.0
pod 3 3 100.0
total 161 166 96.9


line stmt bran cond sub pod time code
1             package Number::Denominal;
2              
3 8     8   230420 use strict;
  8         20  
  8         318  
4 8     8   45 use warnings;
  8         17  
  8         250  
5 8     8   6522 use List::ToHumanString 1.002;
  8         5174  
  8         527  
6 8     8   55 use Carp;
  8         16  
  8         11579  
7             require Exporter;
8             our @ISA = qw(Exporter);
9             our @EXPORT = qw(denominal denominal_hashref denominal_list);
10              
11             our $VERSION = '2.001001'; # VERSION
12              
13             sub denominal {
14 20280     20280 1 1152825 my ( $num, @den ) = @_;
15 20280         44497 return _denominal( $num, \@den, 'string' );
16             }
17              
18             sub denominal_list {
19 13     13 1 16602 my ( $num, @den ) = @_;
20 13         53 return _denominal( $num, \@den, 'list' );
21             }
22              
23             sub denominal_hashref {
24 12     12 1 21955 my ( $num, @den ) = @_;
25 12         33 return _denominal( $num, \@den, 'hashref' );
26             }
27              
28             sub _denominal {
29 20305     20305   33486 my ( $num, $den, $mode ) = @_;
30              
31 20305         23281 $num = abs $num;
32 20305         35546 my $args = _prepare_extra_args( $den );
33 20304         36424 $den = _process_den( $mode, $den );
34              
35 20304         25052 my @bits;
36 20304         25501 my $step = 1; # steps for precision, if enabled
37 20304         32534 my $pre_bit; # a "pre" bit, again for precision to handle rounding.
38             my @ordered_bit_names; # need this for missing bits in denominal_list
39 20304         41847 for my $bit ( _get_bits( @$den ) ) {
40 71380         125632 push @ordered_bit_names, $bit->{name}[0];
41              
42 71380         214484 $bit->{num} = sprintf '%d', $num / $bit->{divisor};
43 71380         137195 $num = $num - $bit->{num} * $bit->{divisor};
44              
45 71380 100 100     310663 if ( @bits
      100        
46             and $args->{precision} and ++$step > $args->{precision}
47             ) {
48 10265 100       22366 last unless $bit->{num};
49              
50             # add current element and pre bit, for sake of calculations
51             # I should really really refactor this crap to something sane
52 8856 100       17728 unshift @bits, $pre_bit if defined $pre_bit;
53 8856         34239 push @bits, +{ %$bit };
54              
55 8856         21770 for ( reverse 0 .. $#bits ) {
56             # increase the previous number if we have to round
57 17223 100       62574 $bits[$_-1]{num}++
58             if $bits[$_]{num} * $bits[$_]{divisor}
59             / $bits[$_-1]{divisor} >= 0.5;
60              
61 17223 100       50616 $bits[$_]{num} = 0
62             if $bits[$_]{num} * $bits[$_]{divisor}
63             / $bits[$_-1]{divisor} == 1;
64              
65             # stop checking previous numbers if we ran out of them
66             # or they haven't reached their limit (divisor) yet.
67 17223 100 100     77244 last if $_ == 0
68             or $bits[$_-1]{num} * $bits[$_-1]{divisor}
69             < $bits[$_-2]{divisor};
70             }
71              
72             # pop last element off, since it was temporary
73 8856         12280 pop @bits;
74              
75             # get rid of els with zero nums (including pre bit)
76 8856         36960 @bits = grep $_->{num}, @bits;
77 8856         16591 last;
78             }
79              
80 61115 100       141555 $pre_bit = $bit unless $bit->{num};
81 61115 100       126456 $bit->{num} or next; # don't insert the bit, if it's zero
82 53971         248241 push @bits, +{ %$bit };
83             }
84              
85 20304         90577 my @result;
86             # process the bits into output format, depending on what type
87             # ... of output is wanted
88 20304         36629 for ( @bits ) {
89 53968 100       276985 push @result, $mode eq 'hashref' ? ( $_->{name}[0] => $_->{num} )
    100          
    100          
90             : $mode eq 'list' ? { num => $_->{num}, name => $_->{name}[0] }
91             : $_->{num} . ' ' . $_->{name}[ $_->{num} == 1 ? 0 : 1 ];
92             }
93              
94 20304 100       47973 if ( $mode eq 'list' ) {
95 13         30 my @temp_result = @result;
96 13         26 @result = ();
97 13         20 my %bits_in_result;
98 13         103 @bits_in_result{ map $_->{name}, @temp_result }
99             = map $_->{num}, @temp_result;
100              
101 13         34 for ( @ordered_bit_names ) {
102 43 100       165 push @result, exists $bits_in_result{ $_ }
103             ? $bits_in_result{ $_ } : 0;
104             }
105             }
106              
107 20304 100       82730 return $mode eq 'hashref'
    100          
108             ? +{@result} :
109             $mode eq 'list'
110             ? @result : to_human_string '|list|', @result;
111             }
112              
113             sub _get_bits {
114 20304     20304   50470 my @den = @_;
115              
116 20304         27107 my @bits;
117 20304         23990 my $divisor = 1;
118 20304         96688 for ( grep !($_%2), 0..$#den ) {
119 101454 100       211341 if ( not ref $den[ $_ ] ) {
120 101433         264693 $den[ $_ ] = [
121             $den[ $_ ],
122             $den[ $_ ] . 's',
123             ];
124             }
125              
126 101454         251899 push @bits, {
127             name => $den[ $_ ],
128             divisor => $divisor,
129             };
130              
131 101454   100     333966 $divisor *= $den[ $_+1 ] || 1;
132             }
133              
134 20304         83505 return reverse @bits;
135             }
136              
137             sub _process_den {
138 20304     20304   29445 my ( $mode, $den ) = @_;
139              
140 20304 100 100     171605 if ( @$den == 1 and ref $den->[0] eq 'ARRAY' ) {
    100 66        
141 1         2 my $idx = 0;
142 1         2 @$den = map +( 'el' . $idx++ => $_ ), @{ $den->[0] };
  1         11  
143 1         4 push @$den, 'el', $idx;
144 1         5 $mode = 'list';
145             }
146             elsif ( @$den == 1 and ref $den->[0] eq 'SCALAR' ) {
147 20271         21865 my $unit_shortcut = ${ $den->[0] };
  20271         37142  
148 20271         37195 my $values_for_unit = _get_units()->{ $unit_shortcut };
149              
150 20271 50       178647 croak qq{Unknown unit shortcut ``$unit_shortcut''}
151             unless $values_for_unit;
152 20271         41881 $den = $values_for_unit;
153             }
154              
155 20304         37628 return $den;
156             }
157              
158             sub _prepare_extra_args {
159 20305     20305   25981 my $den = shift;
160              
161 20305 100       65984 return unless ref $den->[-1] eq 'HASH';
162              
163 10271         12364 my %extra_args = %{ delete $den->[-1] };
  10271         35657  
164              
165 10271 50       28978 if ( exists $extra_args{precision} ) {
166 10271         15038 my $p = $extra_args{precision};
167 10271 50 66     66192 croak q{precision argument takes positive integers only,}
    100          
168             . q{ but its value is } . (defined $p ? $p : '[undefined]')
169             unless $p and $p =~ /\A\d+\z/;
170             }
171              
172 10270         23799 return \%extra_args;
173             }
174              
175             sub _get_units {
176             return {
177 20272     20272   370956 time => [
178             second => 60 => minute => 60 => hour => 24 => day => 7 => 'week'
179             ],
180             weight => [
181             gram => 1000 => kilogram => 1000 => 'tonne',
182             ],
183             weight_imperial => [
184             ounce => 16 => pound => 14 => stone => 160 => 'ton',
185             ],
186             length => [
187             meter => 1000 => kilometer => 9_460_730_472.5808 => 'light year',
188             ],
189             length_mm => [
190             millimeter => 10 => centimeter => 100 => meter => 1000
191             => kilometer => 9_460_730_472.5808 => 'light year',
192             ],
193             length_imperial => [
194             [qw/inch inches/] => 12 =>
195             [qw/foot feet/] => 3 => yard => 1760
196             => [qw/mile miles/],
197             ],
198             volume => [
199             milliliter => 1000 => 'Liter',
200             ],
201             volume_imperial => [
202             'fluid ounce' => 20 => pint => 2 => quart => 4 => 'gallon',
203             ],
204             info => [
205             bit => 8 => byte => 1000 => kilobyte => 1000 => megabyte => 1000
206             => gigabyte => 1000 => terabyte => 1000 => petabyte => 1000
207             => exabyte => 1000 => zettabyte => 1000 => 'yottabyte',
208             ],
209             info_1024 => [
210             bit => 8 => byte => 1024 => kibibyte => 1024 => mebibyte => 1024
211             => gibibyte => 1024 => tebibyte => 1024 => pebibyte => 1024
212             => exbibyte => 1024 => zebibyte => 1024 => 'yobibyte',
213             ],
214             };
215             }
216              
217             q|
218             Q: how many programmers does it take to change a light bulb?
219             A: none, that's a hardware problem
220             |;
221              
222             __END__