File Coverage

lib/Math/NumberBase.pm
Criterion Covered Total %
statement 57 57 100.0
branch 14 14 100.0
condition n/a
subroutine 11 11 100.0
pod 8 8 100.0
total 90 90 100.0


line stmt bran cond sub pod time code
1             package Math::NumberBase;
2              
3 1     1   733 use 5.008;
  1         3  
  1         34  
4 1     1   5 use strict;
  1         2  
  1         28  
5 1     1   14 use warnings;
  1         1  
  1         622  
6              
7             our $VERSION = '0.02';
8              
9             =head1 NAME
10              
11             Math::NumberBase - Number converter from one base to another base
12              
13             =head1 SYNOPSIS
14              
15             use Math::NumberBase;
16              
17             # base 16 numbers: hexadecimal
18             my $base_16 = Math::NumberBase->new(16);
19              
20             # base 4 numbers, but with custom symbols:
21             # 'w' = 0
22             # 'x' = 1
23             # 'y' = 2
24             # 'z' = 3
25             my $base_4 = Math::NumberBase->new(4, 'wxyz');
26              
27             print $base_16->to_decimal('1ac2'), "\n";
28             print $base_16->from_decimal(325), "\n";
29             print $base_16->convert_to('1ac2', $base_4), "\n";
30             print $base_16->convert_from('yzxw', $base_4), "\n";
31              
32             =head1 DESCRIPTION
33              
34             This class can convert a number from one base to another base.
35              
36             By default, this class will use a subset of (0..9,'a'..'z') as the symbols.
37             That means for base-16 numbers, the default symbols are 0,1,2,3,4,5,6,7,8,9,'a','b','c','d','e','f'.
38             But you can always specify your own symbols by passing a string to the constructor.
39              
40             =head1 METHODS
41              
42             =head2 new(, )
43              
44             The constructor.
45              
46             Receives 2 optional parameters: $base and $symbols.
47              
48             If no paramteres passed to constructor, the base would be 10, and the symbols would be 0,1,2,3,4,5,6,7,8,9, thus it makes a normal decimal number system.
49              
50             If only $base is passed to constructor, the $symbols would be a subset of (0..9,'a'..'z'). That means if you pass a number greater than 36 to the constructor you have to define the symbols you want to use to represent the number.
51              
52             $base has to be an integer >= 2.
53              
54             $symbols should be a string.
55              
56             =cut
57              
58             sub new {
59 15     15 1 5882 my ($class, $base, $symbols) = @_;
60              
61 15 100       42 if (not defined $base) {
62             # default base = 10
63 1         2 $base = 10;
64             }
65              
66 15 100       30 if ($base < 2) {
67 1         8 die '$base can not be less than 2';
68             }
69              
70 14 100       30 if (int $base != $base) {
71 1         9 die '$base must be an integer';
72             }
73              
74 13         16 my @symbols_array = ();
75              
76 13 100       26 if (not defined $symbols) {
77             # create default symbols
78 3 100       7 if ($base > 36) {
79 1         9 die 'Can not guess what should be the $symbols when $base > 36 and $symbols is not defined';
80             }
81 2         15 my @numalpha = (0 .. 9, 'a' .. 'z');
82 2         11 @symbols_array = splice @numalpha, 0, $base;
83             }
84             else {
85 10         46 @symbols_array = split //, $symbols;
86             }
87              
88             # check duplicates
89 12         21 my $value = 0;
90 12         19 my %symbol_value_map = map { $_ => $value++ } @symbols_array;
  95         237  
91 12 100       46 if (scalar keys %symbol_value_map != scalar @symbols_array) {
92 1         9 die '$symbols contains duplicate(s)';
93             }
94              
95 11 100       22 if (scalar @symbols_array != $base) {
96 1         11 die '$symbols length is not equal to $base';
97             }
98              
99 10         51 my $self = bless {
100             '_base' => $base,
101             '_symbols' => \@symbols_array,
102             '_symbol_value_map' => \%symbol_value_map
103             }, $class;
104              
105 10         33 return $self;
106             }
107              
108             =head2 get_base( )
109              
110             Returns the base.
111              
112             =cut
113              
114             sub get_base {
115 30     30 1 320 return shift->{'_base'};
116             }
117              
118             =head2 get_symbols( )
119              
120             Returns an arrayref of symbols.
121              
122             my $base_3 = Math::NumberBase->new(3, 'abc');
123             my $symbols = $base_3->get_symbols();
124              
125             # $symbols = ['a', 'b', 'c'];
126              
127             =cut
128              
129             sub get_symbols {
130 14     14 1 34 return shift->{'_symbols'};
131             }
132              
133             =head2 get_symbol_value_map( )
134              
135             Returns a hashref of symbol => value map.
136              
137             my $base_3 = Math::NumberBase->new(3, 'abc');
138             my $symbol_map = $base_3->get_symbol_value_map();
139              
140             # $symbol_map = {
141             # 'a' => 0,
142             # 'b' => 1,
143             # 'c' => 2
144             # };
145              
146             =cut
147              
148             sub get_symbol_value_map {
149 17     17 1 39 return shift->{'_symbol_value_map'};
150             }
151              
152             =head2 to_decimal()
153              
154             Convert to decimal.
155              
156             my $base_3 = Math::NumberBase->new(3, 'abc');
157              
158             # convert 'cab' in base 3 to a decimal number
159             my $in_decimal = $base_3->to_decimal('cab');
160              
161             # $in_decimal = 19;
162              
163             =cut
164              
165             sub to_decimal {
166 16     16 1 40 my ($self, $string) = @_;
167              
168 16         30 my $base = $self->get_base();
169 16         30 my $symbol_value_map = $self->get_symbol_value_map();
170              
171 16         19 my $result = 0;
172              
173 16         17 my $power = 0;
174 16         32 while (length $string) {
175 106         145 my $char = chop $string;
176 106         141 $result += $symbol_value_map->{$char} * ($base ** $power);
177 106         177 $power++;
178             }
179              
180 16         68 return $result;
181             }
182              
183             =head2 from_decimal()
184              
185             Convert from decimal.
186              
187             my $base_3 = Math::NumberBase->new(3, 'abc');
188              
189             # convert 19 decimal to a base 3 number
190             my $in_base_3 = $base_3->from_decimal(19);
191              
192             # $in_base_3 = 'cab';
193              
194             =cut
195              
196             sub from_decimal {
197 12     12 1 19 my ($self, $in_decimal) = @_;
198              
199 12         23 my $base = $self->get_base();
200 12         22 my $symbols = $self->get_symbols();
201              
202 12         17 my $result = '';
203 12         26 while ($in_decimal) {
204 77         120 $result = $symbols->[$in_decimal % $base] . $result;
205 77         156 $in_decimal = int ($in_decimal / $base);
206             }
207              
208 12         51 return $result;
209             }
210              
211             =head2 convert_to(, )
212              
213             Convert a number from this base to another base.
214              
215             my $base_3 = Math::NumberBase->new(3, 'abc');
216             my $base_4 = Math::NumberBase->new(4);
217              
218             # convert 'cab' in base 3 to a base 4 number
219             my $in_base_4 = $base_3->convert_to('cab', $base_4);
220              
221             # $in_base_4 = '103';
222              
223             =cut
224              
225             sub convert_to {
226 4     4 1 15 my ($self, $string, $number_base) = @_;
227              
228 4         12 return $number_base->from_decimal($self->to_decimal($string));
229             }
230              
231             =head2 convert_from(, )
232              
233             Convert a number from another base to this base.
234              
235             my $base_3 = Math::NumberBase->new(3, 'abc');
236             my $base_4 = Math::NumberBase->new(4);
237              
238             # convert 'cab' in base 3 to a base 4 number
239             my $in_base_4 = $base_4->convert_from('cab', $base_3);
240              
241             # $in_base_4 = '103';
242              
243             =cut
244              
245             sub convert_from {
246 4     4 1 10 my ($self, $string, $number_base) = @_;
247              
248 4         10 return $self->from_decimal($number_base->to_decimal($string));
249             }
250              
251             =head1 AUTHOR
252              
253             Yehezkiel Syamsuhadi
254              
255             =head1 COPYRIGHT AND LICENSE
256              
257             Copyright (C) 2009 by Yehezkiel Syamsuhadi
258              
259             This library is free software; you can redistribute it and/or modify
260             it under the same terms as Perl itself, either Perl version 5.10.0 or,
261             at your option, any later version of Perl 5 you may have available.
262              
263             =cut
264              
265             1;