File Coverage

blib/lib/Math/Base/Convert.pm
Criterion Covered Total %
statement 123 132 93.1
branch 68 82 82.9
condition 26 37 70.2
subroutine 13 13 100.0
pod 5 7 71.4
total 235 271 86.7


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2              
3             package Math::Base::Convert;
4              
5             #use diagnostics;
6 20     20   770220 use Carp;
  20         38  
  20         1969  
7 20     20   160 use vars qw($VERSION @ISA @EXPORT_OK %EXPORT_TAGS @BASES $signedBase);
  20         39  
  20         5946  
8              
9             # @Bases, $signedBase imported from Math::Base::Convert::Bases
10              
11             require Exporter;
12             require Math::Base::Convert::Shortcuts;
13             require Math::Base::Convert::CalcPP;
14             require Math::Base::Convert::Bases; # drag in BASES
15              
16             @ISA = qw(
17             Math::Base::Convert::Shortcuts
18             Math::Base::Convert::CalcPP
19             Exporter
20             );
21              
22             $VERSION = do { my @r = (q$Revision: 0.13 $ =~ /\d+/g); sprintf "%d."."%02d" x $#r, @r };
23              
24             @EXPORT_OK = ( qw( cnv cnvpre cnvabs basemap ), @BASES );
25             %EXPORT_TAGS = (
26             all => [@EXPORT_OK],
27             base => [ 'basemap', @BASES ]
28             );
29              
30             my $functions = join '', keys %{__PACKAGE__ .'::'}; # before 'strict'
31              
32 20     20   143 use strict;
  20         46  
  20         4594  
33              
34             my $package = __PACKAGE__;
35             my $packageLen = length __PACKAGE__;
36             my $bs = $package .'::_bs::'; # indentify 'base sub'
37              
38             my %num2sub = (
39             2 => &bin,
40             4 => &DNA,
41             8 => &ocT,
42             10 => &dec,
43             16 => &HEX,
44             64 => &m64
45             );
46              
47             # return a hash map of the base array, including upper/lower case variants
48             #
49             sub basemap {
50 2463 50   2463 1 178425 shift if ref $_[0] eq $package; # waste if method call
51 2463         5257 my $base = validbase($_[0]); # return array pointer
52 2463         15232 ref($base) =~ /$bs(.+)/; # sub name is $1
53 2463 100       9300 if ($1 eq 'user') { # if user array
54 252         507 my $aryhsh = {};
55 252         965 @{$aryhsh}{@$base} = (0..$#$base);
  252         2666  
56 252         2627 return $aryhsh;
57             }
58 2211         16505 my @all = $functions =~ /$1/gi; # get all matching sub names regardless of case
59             # names are strings
60 20     20   141 no strict;
  20         62  
  20         38998  
61 2211         4306 my %aryhsh;
62 2211         5164 foreach (@all) {
63 3692         15364 $_ = $package->can($_); # return sub ref
64 3692         9178 $_ = &$_; # array pointer
65 3692         10854 foreach my $i (0..$#$_) {
66 72966         156899 $aryhsh{$_->[$i]} = $i; # map keys to index
67             }
68             }
69 2211         22434 return \%aryhsh;
70             }
71            
72             # check for internal base
73             sub validbase {
74 7396     7396 0 130829 my $base = shift;
75 7396         10927 my $ref;
76 7396 100       23954 if (($ref = ref $base)) {
    100          
77 3191 100       19123 if ($ref eq 'ARRAY') { # user supplied
    100          
78 526         893 my @base = @{$base};
  526         4323  
79 526         1050 my $len = @base;
80 526 50       1276 Carp::croak "base to short, < 2" unless $len > 1;
81 526 50       1144 Carp::croak "base to long, > 65535" unless $len < 65536;
82 526         1586 $base = bless \@base, $bs .'user';
83 526         1812 return bless $base, $bs . 'user';
84             }
85             elsif ($ref =~ /^$bs/) { # internal base
86 2664         6735 return $base;
87             }
88             else {
89 1         2 $base = 'reference';
90             }
91             }
92             elsif ($base =~ /\D/) { # is a string
93 4195         17335 my $rv = $package->can($base);
94 4195 100       13609 return &$rv if $rv;
95             } else {
96 10 100       78 return $num2sub{$base} if exists $num2sub{$base};
97             }
98 3         408 Carp::croak "not a valid base: $base";
99             }
100              
101             sub vet {
102 2458     2458 0 145986 my $class = shift;
103 2458   100     7629 my $from = shift || '';
104 2458   100     6182 my $to = shift || '';
105              
106 2458 100 100     12950 $to =~ s/\s+//g if $to && ! ref $to; # strip white space
107 2458 100 100     10070 $from =~ s/\s+//g if $from && ! ref $from;
108              
109 2458 100       5359 unless ($from) { # defaults if not defined
110 3         22 $to = &HEX;
111 3         12 $from = &dec;
112             }
113             else {
114 2455         5412 $from = validbase($from);
115 2455 100       5368 unless ($to) {
116 2         6 $to = &HEX;
117             } else {
118 2453         4819 $to = validbase($to);
119             }
120             }
121              
122             # convert sub ref's to variables
123             # $to = &$to;
124             # ($from, my $fhsh) = &$from;
125              
126 2458         5763 my $prefix = ref $to;
127 2458 100       12201 if ($prefix =~ /HEX$/i) {
    100          
    100          
128 412         789 $prefix = '0x';
129             }
130             elsif ($prefix =~ /OCT$/i) {
131 214         471 $prefix = '0';
132             }
133             elsif ($prefix =~ /bin$/) {
134 212         433 $prefix = '0b';
135             } else {
136 1620         3222 $prefix = '';
137             }
138              
139 2458         6067 bless {
140             to => $to,
141             tbase => scalar @$to,
142             from => $from,
143             fhsh => basemap($from),
144             fbase => scalar @$from,
145             prefix => $prefix
146             }, $class;
147             }
148              
149             sub new {
150 2451     2451 1 217235 my $proto = shift;
151 2451   33     25144 my $class = ref $proto || $proto || $package;
152 2451         6366 vet($class,@_);
153             }
154              
155             sub _cnv {
156 2527     2527   6722 my $bc = shift;
157 2527         4155 my $nstr;
158 2527 100 66     9314 if (ref $bc && ref($bc) eq $package) { # method call?
159 348         604 $nstr = shift; # yes, number to convert is next arg
160             } else {
161 2179         3853 $nstr = $bc; # no, first arg is number to convert
162 2179         6860 $bc = $package->new(@_);
163             }
164 2527 50       8263 return $nstr unless keys %$bc; # if there really is no conversion
165 2527 50       5516 $nstr = '' unless defined $nstr;
166              
167 2527         4284 my($from,$fbase,$fhsh) = @{$bc}{qw( from fbase fhsh )};
  2527         8295  
168              
169 2527         5083 my $ref = ref $from;
170 2527 100 66     10868 if ($ref eq 'user' || $fbase > $signedBase) { # known, signed character sets?
171 660         1709 $bc->{sign} = ''; # no
172             } else { # yes
173 1867         5548 $nstr =~ s/^([+-])//; # strip sign
174 1867 50 33     8298 $bc->{sign} = $1 && $1 eq '-' ? '-' : ''; # and save for possible restoration
175              
176 1867 100       7656 if ($ref =~ /(HEX)$/i) {
    100          
177 548         1948 $nstr =~ s/^0x//i; # snip prefix, including typo's
178             }
179             elsif ($ref =~ /bin/i) {
180 205         886 $nstr =~ s/^0b//i; # snip prefix, including typo's
181             }
182              
183 1867         13833 $nstr =~ s/^[$from->[0]]+//; # snip leading zeros
184             }
185              
186 2527         20026 my $fclass = join '', keys %$fhsh;
187 2527 50       69361 if ($nstr =~ /[^\Q$fclass\E]/) { # quote metacharacters
188 0         0 $ref =~ /([^:]+)$/;
189 0         0 Carp::croak "input character not in '$1'\nstring:\t$nstr\nbase:\t$fclass\n";
190             }
191              
192 2527         8954 $bc->{nstr} = $nstr;
193 2527         10773 $bc;
194             }
195              
196             #
197             # Our internal multiply & divide = base 32
198             # Maximum digit length for a binary base = 32*ln(2)/ln(base)
199             # 0bnnnnnnnnnnn
200             # 0nnnnnnnnnnnn
201             # 0xnnnnnnnnnnn
202             #
203              
204             my %maxdlen = (# digits, key is base
205             2 => 31, # 2^1
206             4 => 16, # 2^2
207             8 => 10, # 2^3
208             16 => 8, # 2^4
209             32 => 6, # 2^5
210             64 => 5, # 2^6
211             128 => 4, # 2^7
212             256 => 4 # 2^8
213             );
214              
215             sub cnv {
216 799     799 1 10721 my @rv = &cnvpre;
217 799 100       3626 return @rv if wantarray;
218 436         2043 return ($rv[0] . $rv[2]); # sign and string only
219             }
220              
221             sub cnvabs {
222 727     727 1 8655 my @rv = &cnvpre;
223 727 100       3031 return @rv if wantarray;
224 364         1449 return $rv[2] # string only
225             }
226              
227             sub cnvpre {
228 2252     2252 1 11907 my $bc = &_cnv;
229 2252 50       5997 return $bc unless ref $bc;
230 2252         4058 my($from,$fbase,$to,$tbase,$sign,$prefix,$nstr) = @{$bc}{qw( from fbase to tbase sign prefix nstr)};
  2252         9680  
231              
232 2252         4215 my $slen = length($nstr);
233 2252         4718 my $tref = ref($to);
234 2252 100       6373 unless ($slen) { # zero length input
    100          
235 1584         3033 $nstr = $to->[0]; # return zero
236             }
237 0         0 elsif (lc $tref eq lc ref($from)) {# no base conversion
238 54 50       175 if ($tref ne ref($from)) { # convert case?
239 0 0       0 if ($tref =~ /(?:DNA|HEX)/) {
240 0         0 $nstr = uc $nstr; # force upper case
241             } else {
242 0         0 $nstr = lc $nstr; # or force lower case
243             }
244             }
245             }
246             else { # convert
247              
248 614         1250 my $fblen = length($fbase);
249 614 100 66     3140 if ($fbase & $fbase -1 || # from base is not power of 2
    50 33        
250             $fblen > 256 ) { # no shortcuts,...
251 220         892 $bc->useFROMbaseto32wide;
252             }
253              
254             # if a large base and digit string will fit in a single 32 bit register
255             elsif ( $fblen > 32 && # big base
256             # exists $maxdlen{$fbase} && # has to exist
257             # ! $slen > $maxdlen{$fbase}) {
258             $slen <= $maxdlen{$fbase}) {
259 0         0 $bc->useFROMbaseto32wide; # CalcPP is faster
260             }
261             else { # shortcuts faster for big numbers
262 394         1487 $bc->useFROMbaseShortcuts;
263             }
264              
265             ################################
266             # input converted to base 2^32 #
267             ################################
268              
269 614 100 66     3285 if ($tbase & $tbase -1 || # from base is not power of 2
    100 100        
270             $tbase > 256 ) { # no shortcuts,...
271 130         473 $nstr = $bc->use32wideTObase;
272             }
273             # if big base and digit string fits in a single 32 bit register
274 86         292 elsif ( $tbase > 32 && @{$bc->{b32str}} == 1) {
275 75         245 $nstr = $bc->use32wideTObase; # CalcPP is faster
276             }
277             else {
278 409         1398 $nstr = $bc->useTObaseShortcuts; # shortcuts faster for big numbers
279             }
280             } # end convert
281              
282 2252 100       5591 $nstr = $to->[0] unless length($nstr);
283 2252 100       21971 return ($sign,$prefix,$nstr) if wantarray;
284 363 100 66     1471 if (#$prefix ne '' && # 0, 0x, 0b
285             $tbase <= $signedBase && # base in signed set
286             $tref ne 'user' ) { # base standard
287 264         2990 return ($sign . $prefix . $nstr);
288             }
289 99         1100 return ($prefix . $nstr);
290             }
291            
292             sub _cnvtst {
293 204     204   1277 my $bc = &_cnv;
294 204 50       352 return $bc unless ref $bc;
295 204         614 $bc->useFROMbaseto32wide;
296 204 50       696 return $bc->use32wideTObase unless wantarray;
297 0           return (@{$bc}{qw( sign prefix )},$bc->use32wideTObase);
  0            
298             }
299              
300             =head1 NAME
301              
302             Math::Base::Convert - very fast base to base conversion
303              
304             =head1 SYNOPSIS
305              
306             =head2 As a function
307              
308             use Math::Base::Convert qw( :all )
309             use Math::Base::Convert qw(
310              
311             cnv
312             cnvabs
313             cnvpre
314             basemap
315              
316             # comments
317             bin base 2 0,1
318             dna base 4 lower case dna
319             DNA base 4 upper case DNA
320             oct base 8 octal
321             dec base 10 decimal
322             hex base 16 lower case hex
323             HEX base 16 upper case HEX
324             b62 base 62
325             b64 base 64 month:C:12 day:V:31
326             m64 base 64 0-63 from MIME::Base64
327             iru base 64 P10 protocol - IRCu daemon
328             url base 64 url with no %2B %2F expansion of + - /
329             rex base 64 regular expression variant
330             id0 base 64 IDentifier style 0
331             id1 base 64 IDentifier style 1
332             xnt base 64 XML Name Tokens (Nmtoken)
333             xid base 64 XML identifiers (Name)
334             b85 base 85 RFC 1924 for IPv6 addresses
335             ascii base 95 7 bit printible 0x20 - 0x7F
336             );
337              
338             my $converted = cnv($number,optionalFROM,optionalTO);
339             my $basemap = basmap(base);
340              
341             =head2 As a method:
342              
343             use Math::Base::Convert;
344             use Math::Base::Convert qw(:base);
345              
346             my $bc = new Math::Base::Convert(optionalFROM,optionalTO);
347             my $converted = $bc->cnv($number);
348             my $basemap = $bc->basemap(base);
349              
350             =head1 DESCRIPTION
351              
352             This module provides fast functions and methods to convert between arbitrary number bases
353             from 2 (binary) thru 65535.
354              
355             This module is pure Perl, has no external dependencies, and is backward compatible
356             with old versions of Perl 5.
357              
358             =head1 PREFERRED USE
359              
360             Setting up the conversion parameters, context and error checking consume a significant portion of the execution time of a
361             B<single> base conversion. These operations are performed each time B<cnv> is called as a function.
362              
363             Using method calls eliminates a large portion of this overhead and will improve performance for
364             repetitive conversions. See the benchmarks sub-directory in this distribution.
365              
366             =head1 BUILT IN NUMBER SETS
367              
368             Number set variants courtesy of the authors of Math::Base:Cnv and
369             Math::BaseConvert.
370              
371             The functions below return a reference to an array
372              
373             $arrayref = function;
374              
375             bin => ['0', '1'] # binary
376             dna => ['a','t','c','g'] # lc dna
377             DNA => ['A','T','C','G'], {default} # uc DNA
378             oct => ['0'..'7'] # octal
379             dec => ['0'..'9'] # decimal
380             hex => ['0'..'9', 'a'..'f'] # lc hex
381             HEX => ['0'..'9', 'A'..'F'] {default} # uc HEX
382             b62 => ['0'..'9', 'a'..'z', 'A'..'Z'] # base 62
383             b64 => ['0'..'9', 'A'..'Z', 'a'..'z', '.', '_'] # m:C:12 d:V:31
384             m64 => ['A'..'Z', 'a'..'z', '0'..'9', '+', '/'] # MIMI::Base64
385             iru => ['A'..'Z', 'a'..'z', '0'..'9', '[', ']'] # P10 - IRCu
386             url => ['A'..'Z', 'a'..'z', '0'..'9', '*', '-'] # url no %2B %2F
387             rex => ['A'..'Z', 'a'..'z', '0'..'9', '!', '-'] # regex variant
388             id0 => ['A'..'Z', 'a'..'z', '0'..'9', '_', '-'] # ID 0
389             id1 => ['A'..'Z', 'a'..'z', '0'..'9', '.', '_'] # ID 1
390             xnt => ['A'..'Z', 'a'..'z', '0'..'9', '.', '-'] # XML (Nmtoken)
391             xid => ['A'..'Z', 'a'..'z', '0'..'9', '_', ':'] # XML (Name)
392             b85 => ['0'..'9', 'A'..'Z', 'a'..'z', '!', '#', # RFC 1924
393             '$', '%', '&', '(', ')', '*', '+', '-',
394             ';', '<', '=', '>', '?', '@', '^', '_',
395             '', '{', '|', '}', '~']
396             An arbitrary base 95 composed of printable 7 bit ascii
397             from 0x20 (space) through 0x7F (tilde ~)
398             ascii => [
399             ' ','!','"','#','$','%','&',"'",'(',')',
400             '*','+',',','-','.','/',
401             '0','1','2','3','4','5','6','7','8','9',
402             ':',';','<','=','>','?','@',
403             'A','B','C','D','E','F','G','H','I','J','K','L','M',
404             'N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
405             '[','\',']','^','_','`',
406             'a','b','c','d','e','f','g','h','i','j','k','l','m',
407             'n','o','p','q','r','s','t','u','v','w','x','y','z',
408             '{','|','}','~']
409              
410             NOTE: Clean text with =~ s/\s+/ /; before applying to ascii
411              
412             =head1 USAGE
413              
414             =over 4
415              
416             =item * $converted = cnv($number,[from],[to])
417              
418             SCALAR context: array context covered later in this document.
419              
420             To preserve similarity to other similar base conversion modules, B<cnv>
421             returns the converted number string with SIGN if both the input and output
422             base strings are in known signed set of bases in this module.
423              
424             In the case of binary, octal, hex, all leading base designator strings such as
425             '0b','0', '0x' are automatically stripped from the input. Base designator
426             strings are NOT applied to the output.
427              
428             The context of base FROM and TO is optional and flexible.
429              
430             Unconditional conversion from decimal to HEX [upper case]
431              
432             $converted = cnv($number);
433              
434             Example conversion from octal to default HEX [upper case] with different
435             context for the 'octal' designator.
436              
437             base as a number
438             $converted = cnv($number,8);
439              
440             base as a function (imported)
441             $converted = cnv($number,oct);
442              
443             base as text
444             $converted = convbase($number,'oct');
445              
446             Conversion to/from arbitrary bases i.e.
447              
448             $converted = cnv($number); # dec -> hex (default)
449             $converted = cnv($number,oct); # oct to HEX
450             $converted = cnv($number,10,HEX); # dec to uc HEX
451             $converted = cnv($number,10,hex); # dec to lc hex
452             $converted = cnv($number,dec,hex);# same
453              
454             pointer notation
455             $converted = cnv($number, oct => dec);
456              
457             $converted = cnv($number,10 => 23); # dec to base23
458             $converted = cnv($number,23 => 5); # b23 to base5
459             etc...
460              
461             =item * $bc = new Math::Base::Convert([from],[to]);
462              
463             This method has the same usage and syntax for FROM and TO as B<cnv> above.
464              
465             Setup for unconditional conversion from HEX to decimal
466              
467             $bc = new Math::Base::Convert();
468              
469             Example conversion from octal to decimal
470              
471             base number
472             $bc = new Math::Base::Convert(8);
473              
474             base function (imported)
475             $bc = new Math::Base::Convert(oct);
476              
477             base text
478             $bc = new Math::Base::Convert('oct')
479              
480             The number conversion for any of the above:
481              
482             NOTE: iterative conversions using a method pointer are ALWAYS faster than
483             calling B<cnv> as a function.
484              
485             $converted = $bc->cnv($number);
486              
487             =item * $converted = cnvpre($number,[from],[to])
488              
489             Same as B<cnv> except that base descriptor PREfixes are applied to B<binary>,
490             B<octal>, and B<hexadecimal> output strings.
491              
492             =item * $converted = cnvabs($number,[from],[to])
493              
494             Same as B<cnv> except that the ABSolute value of the number string is
495             returned without SIGN is returned. i.e. just the raw string.
496              
497             =item * ($sign,$prefix,$string) = cnv($number,[$from,[$to]])
498              
499             =item * ($sign,$prefix,$string) = cnv($number,[$from,[$to]])
500              
501             =item * ($sign,$prefix,$string) = cnv($number,[$from,[$to]])
502              
503             ARRAY context:
504              
505             All three functions return the same items in array context.
506              
507             sign the sign of the input number string
508              
509             prefix the prefix which would be applied to output
510              
511             string the raw output string
512              
513             =item * $basemap = basemap(base);
514              
515             =item * $basemap = $bc->basemap(base);
516              
517             This function / method returns a pointer to a hash that maps the keys of a base to its
518             numeric value for base conversion. It accepts B<base> in any of the forms
519             described for B<cnv>.
520              
521             The return basemap includes upper and lower case variants of the the number
522             base in cases such as B<hex> where upper and lower case a..f, A..F map to
523             the same numeric value for base conversion.
524              
525             i.e. $hex_ptr = {
526             0 => 0,
527             1 => 1,
528             2 => 2,
529             3 => 3,
530             4 => 4,
531             5 => 5,
532             6 => 6,
533             7 => 7,
534             8 => 8,
535             9 => 9,
536             A => 10,
537             B => 11,
538             C => 12,
539             D => 13,
540             E => 14,
541             F => 15,
542             a => 10,
543             b => 11,
544             c => 12,
545             d => 13,
546             e => 14,
547             f => 15
548             };
549              
550             =back
551              
552             =head1 BENCHMARKS
553              
554             Math::Base::Convert includes 2 development and one real world benchmark
555             sequences included in the test suite. Benchmark results for a 500mhz system
556             can be found in the 'benchmarks' source directory.
557              
558             make test BENCHMARK=1
559              
560             Provides comparison data for bi-directional conversion of an ascending
561             series of number strings in all base powers. The test sequence contains
562             number strings that go from a a single 32 bit register to several. Tested
563             bases are: (note: b32, b128, b256 not useful and are for testing only)
564              
565             base 2 4 8 16 32 64 85 128 256
566             bin, dna, oct, hex, b32, b64, b85, b128, b256
567              
568             Conversions are performed FROM all bases TO decimal and are repeated in the
569             opposing direction FROM decimal TO all bases.
570              
571             Benchmark 1 results indicate the Math::Base::Convert typically runs
572             significantly faster ( 10x to 100x) than Math::BigInt based
573             implementations used in similar modules.
574              
575             make test BENCHMARK=2
576              
577             Provides comparison data for the frontend and backend converters in
578             Math::Base::Convert's CalcPP and Shortcuts packages, and Math::Bigint
579             conversions if it is present on the system under test.
580              
581             make test BENCHMARK=3
582              
583             Checks the relative timing of short and long number string conversions. FROM
584             a base number to n*32 bit register and TO a base number from an n*32 bit
585             register set.
586              
587             i.e. strings that convert to and from 1, 2, 3... etc.. 32 bit registers
588              
589             =head1 DEPENDENCIES
590              
591             none
592              
593             Math::BigInt is conditionally used in
594             the test suite but is not a requirement
595              
596             =head1 EXPORT_OK
597              
598             Conditional EXPORT functions
599              
600             cnv
601             cnvabs
602             cnvpre
603             basemap
604             bin
605             oct
606             dec
607             heX
608             HEX
609             b62
610             b64
611             m64
612             iru
613             url
614             rex
615             id0
616             id1
617             xnt
618             xid
619             b85
620             ascii
621              
622             =head1 EXPORT_TAGS
623              
624             Conditional EXPORT function groups
625              
626             :all => all of above
627             :base => all except 'cnv,cnvabs,cnvpre'
628              
629             =head1 ACKNOWLEDGEMENTS
630              
631             This module was inspired by Math::BaseConvert maintained by Shane Warden
632             <chromatic@cpan.org> and forked from Math::BaseCnv, both authored by Pip
633             Stuart <Pip@CPAN.Org>
634              
635              
636             =head1 AUTHOR
637              
638             Michael Robinton, <miker@cpan.org>
639              
640             =head1 COPYRIGHT
641              
642             Copyright 2012-2015, Michael Robinton
643              
644             This program is free software; you may redistribute it and/or modify it
645             under the same terms as Perl itself.
646              
647             This program is distributed in the hope that it will be useful,
648             but WITHOUT ANY WARRANTY; without even the implied warranty of
649             MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
650              
651             =cut
652              
653             1;