File Coverage

blib/lib/Math/Base/Convert.pm
Criterion Covered Total %
statement 123 131 93.8
branch 68 82 82.9
condition 26 37 70.2
subroutine 13 13 100.0
pod 5 7 71.4
total 235 270 87.0


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   250582 use Carp;
  20         37  
  20         1701  
7 20     20   96 use vars qw($VERSION @ISA @EXPORT_OK %EXPORT_TAGS @BASES $signedBase);
  20         28  
  20         4863  
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.11 $ =~ /\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   100 use strict;
  20         42  
  20         4235  
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 6678 shift if ref $_[0] eq $package; # waste if method call
51 2463         4617 my $base = validbase($_[0]); # return array pointer
52 2463         10835 ref($base) =~ /$bs(.+)/; # sub name is $1
53 2463 100       6860 if ($1 eq 'user') { # if user array
54 252         398 my $aryhsh = {};
55 252         707 @{$aryhsh}{@$base} = (0..$#$base);
  252         2459  
56 252         2019 return $aryhsh;
57             }
58 2211         14858 my @all = $functions =~ /$1/gi; # get all matching sub names regardless of case
59             # names are strings
60 20     20   114 no strict;
  20         32  
  20         32509  
61 2211         3169 my %aryhsh;
62 2211         4670 foreach (@all) {
63 3692         13609 $_ = $package->can($_); # return sub ref
64 3692         9519 $_ = &$_; # array pointer
65 3692         8856 foreach my $i (0..$#$_) {
66 72966         140635 $aryhsh{$_->[$i]} = $i; # map keys to index
67             }
68             }
69 2211         15572 return \%aryhsh;
70             }
71            
72             # check for internal base
73             sub validbase {
74 7396     7396 0 13336 my $base = shift;
75 7396         7963 my $ref;
76 7396 100       22874 if (($ref = ref $base)) {
    100          
77 3191 100       15024 if ($ref eq 'ARRAY') { # user supplied
    100          
78 526         767 my @base = @{$base};
  526         3649  
79 526         858 my $len = @base;
80 526 50       1324 Carp::croak "base to short, < 2" unless $len > 1;
81 526 50       1143 Carp::croak "base to long, > 65535" unless $len < 65536;
82 526         1283 $base = bless \@base, $bs .'user';
83 526         1631 return bless $base, $bs . 'user';
84             }
85             elsif ($ref =~ /^$bs/) { # internal base
86 2664         6519 return $base;
87             }
88             else {
89 1         3 $base = 'reference';
90             }
91             }
92             elsif ($base =~ /\D/) { # is a string
93 4195         15197 my $rv = $package->can($base);
94 4195 100       15222 return &$rv if $rv;
95             } else {
96 10 100       52 return $num2sub{$base} if exists $num2sub{$base};
97             }
98 3         501 Carp::croak "not a valid base: $base";
99             }
100              
101             sub vet {
102 2458     2458 0 3321 my $class = shift;
103 2458   100     5208 my $from = shift || '';
104 2458   100     5142 my $to = shift || '';
105              
106 2458 100 100     11993 $to =~ s/\s+//g if $to && ! ref $to; # strip white space
107 2458 100 100     12559 $from =~ s/\s+//g if $from && ! ref $from;
108              
109 2458 100       4771 unless ($from) { # defaults if not defined
110 3         18 $to = &HEX;
111 3         14 $from = &dec;
112             }
113             else {
114 2455         4573 $from = validbase($from);
115 2455 100       5159 unless ($to) {
116 2         7 $to = &HEX;
117             } else {
118 2453         4561 $to = validbase($to);
119             }
120             }
121              
122             # convert sub ref's to variables
123             # $to = &$to;
124             # ($from, my $fhsh) = &$from;
125              
126 2458         4193 my $prefix = ref $to;
127 2458 100       10724 if ($prefix =~ /HEX$/i) {
    100          
    100          
128 412         626 $prefix = '0x';
129             }
130             elsif ($prefix =~ /OCT$/i) {
131 214         355 $prefix = '0';
132             }
133             elsif ($prefix =~ /bin$/) {
134 212         326 $prefix = '0b';
135             } else {
136 1620         2430 $prefix = '';
137             }
138              
139 2458         5506 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 9941 my $proto = shift;
151 2451   33     10916 my $class = ref $proto || $proto || $package;
152 2451         4931 vet($class,@_);
153             }
154              
155             sub _cnv {
156 2527     2527   6409 my $bc = shift;
157 2527         2864 my $nstr;
158 2527 100 66     7598 if (ref $bc && ref($bc) eq $package) { # method call?
159 348         541 $nstr = shift; # yes, number to convert is next arg
160             } else {
161 2179         2923 $nstr = $bc; # no, first arg is number to convert
162 2179         5557 $bc = $package->new(@_);
163             }
164 2527 50       7262 return $nstr unless keys %$bc; # if there really is no conversion
165 2527 50       5161 $nstr = '' unless defined $nstr;
166              
167 2527         3604 my($from,$fbase,$fhsh) = @{$bc}{qw( from fbase fhsh )};
  2527         6118  
168              
169 2527         4455 my $ref = ref $from;
170 2527 100 66     10560 if ($ref eq 'user' || $fbase > $signedBase) { # known, signed character sets?
171 660         1360 $bc->{sign} = ''; # no
172             } else { # yes
173 1867         3842 $nstr =~ s/^([+-])//; # strip sign
174 1867 50 33     7676 $bc->{sign} = $1 && $1 eq '-' ? '-' : ''; # and save for possible restoration
175              
176 1867 100       7009 if ($ref =~ /(HEX)$/i) {
    100          
177 548         1420 $nstr =~ s/^0x//i; # snip prefix, including typo's
178             }
179             elsif ($ref =~ /bin/i) {
180 205         577 $nstr =~ s/^0b//i; # snip prefix, including typo's
181             }
182              
183 1867         9196 $nstr =~ s/^[$from->[0]]+//; # snip leading zeros
184             }
185              
186 2527         15862 my $fclass = join '', keys %$fhsh;
187 2527 50       63701 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         7717 $bc->{nstr} = $nstr;
193 2527         6610 $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 7838 my @rv = &cnvpre;
217 799 100       2761 return @rv if wantarray;
218 436         1400 return ($rv[0] . $rv[2]); # sign and string only
219             }
220              
221             sub cnvabs {
222 727     727 1 8710 my @rv = &cnvpre;
223 727 100       2871 return @rv if wantarray;
224 364         1085 return $rv[2] # string only
225             }
226              
227             sub cnvpre {
228 2252     2252 1 13791 my $bc = &_cnv;
229 2252 50       5480 return $bc unless ref $bc;
230 2252         3464 my($from,$fbase,$to,$tbase,$sign,$prefix,$nstr) = @{$bc}{qw( from fbase to tbase sign prefix nstr)};
  2252         6605  
231              
232 2252         3335 my $slen = length($nstr);
233 2252         3324 my $tref = ref($to);
234 2252 100       5364 unless ($slen) { # zero length input
    100          
235 1584         2711 $nstr = $to->[0]; # return zero
236             }
237             elsif (lc $tref eq lc ref($from)) {# no base conversion
238 54 50       127 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         948 my $fblen = length($fbase);
249 614 100 66     2900 if ($fbase & $fbase -1 || # from base is not power of 2
    50 33        
250             $fblen > 256 ) { # no shortcuts,...
251 220         840 $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 0         0 $bc->useFROMbaseto32wide; # CalcPP is faster
259             }
260             else { # shortcuts faster for big numbers
261 394         1221 $bc->useFROMbaseShortcuts;
262             }
263              
264             ################################
265             # input converted to base 2^32 #
266             ################################
267              
268 614 100 66     3185 if ($tbase & $tbase -1 || # from base is not power of 2
    100 100        
269             $tbase > 256 ) { # no shortcuts,...
270 130         395 $nstr = $bc->use32wideTObase;
271             }
272             # if big base and digit string fits in a single 32 bit register
273 86         319 elsif ( $tbase > 32 && @{$bc->{b32str}} == 1) {
274 75         225 $nstr = $bc->use32wideTObase; # CalcPP is faster
275             }
276             else {
277 409         1235 $nstr = $bc->useTObaseShortcuts; # shortcuts faster for big numbers
278             }
279             } # end convert
280              
281 2252 100       4980 $nstr = $to->[0] unless length($nstr);
282 2252 100       18362 return ($sign,$prefix,$nstr) if wantarray;
283 363 100 66     1514 if (#$prefix ne '' && # 0, 0x, 0b
284             $tbase <= $signedBase && # base in signed set
285             $tref ne 'user' ) { # base standard
286 264         2371 return ($sign . $prefix . $nstr);
287             }
288 99         846 return ($prefix . $nstr);
289             }
290            
291             sub _cnvtst {
292 204     204   1513 my $bc = &_cnv;
293 204 50       494 return $bc unless ref $bc;
294 204         666 $bc->useFROMbaseto32wide;
295 204 50       838 return $bc->use32wideTObase unless wantarray;
296 0           return (@{$bc}{qw( sign prefix )},$bc->use32wideTObase);
  0            
297             }
298              
299             =head1 NAME
300              
301             Math::Base::Convert - very fast base to base conversion
302              
303             =head1 SYNOPSIS
304              
305             =head2 As a function
306              
307             use Math::Base::Convert qw( :all )
308             use Math::Base::Convert qw(
309              
310             cnv
311             cnvabs
312             cnvpre
313             basemap
314              
315             # comments
316             bin base 2 0,1
317             dna base 4 lower case dna
318             DNA base 4 upper case DNA
319             oct base 8 octal
320             dec base 10 decimal
321             hex base 16 lower case hex
322             HEX base 16 upper case HEX
323             b62 base 62
324             b64 base 64 month:C:12 day:V:31
325             m64 base 64 0-63 from MIME::Base64
326             iru base 64 P10 protocol - IRCu daemon
327             url base 64 url with no %2B %2F expansion of + - /
328             rex base 64 regular expression variant
329             id0 base 64 IDentifier style 0
330             id1 base 64 IDentifier style 1
331             xnt base 64 XML Name Tokens (Nmtoken)
332             xid base 64 XML identifiers (Name)
333             b85 base 85 RFC 1924 for IPv6 addresses
334             ascii base 96 7 bit printible 0x20 - 0x7F
335             );
336              
337             my $converted = cnv($number,optionalFROM,optionalTO);
338             my $basemap = basmap(base);
339              
340             =head2 As a method:
341              
342             use Math::Base::Convert;
343             use Math::Base::Convert qw(:base);
344              
345             my $bc = new Math::Base::Convert(optionalFROM,optionalTO);
346             my $converted = $bc->cnv($number);
347             my $basemap = $bc->basemap(base);
348              
349             =head1 DESCRIPTION
350              
351             This module provides fast functions and methods to convert between arbitrary number bases
352             from 2 (binary) thru 65535.
353              
354             This module is pure Perl, has no external dependencies, and is backward compatible
355             with old versions of Perl 5.
356              
357             =head1 PREFERRED USE
358              
359             Setting up the conversion parameters, context and error checking consume a significant portion of the execution time of a
360             B base conversion. These operations are performed each time B is called as a function.
361              
362             Using method calls eliminates a large portion of this overhead and will improve performance for
363             repetitive conversions. See the benchmarks sub-directory in this distribution.
364              
365             =head1 BUILT IN NUMBER SETS
366              
367             Number set variants courtesy of the authors of Math::Base:Cnv and
368             Math::BaseConvert.
369              
370             The functions below return a reference to an array
371              
372             $arrayref = function;
373              
374             bin => ['0', '1'] # binary
375             dna => ['a','t','c','g'] # lc dna
376             DNA => ['A','T','C','G'], {default} # uc DNA
377             oct => ['0'..'7'] # octal
378             dec => ['0'..'9'] # decimal
379             hex => ['0'..'9', 'a'..'f'] # lc hex
380             HEX => ['0'..'9', 'A'..'F'] {default} # uc HEX
381             b62 => ['0'..'9', 'a'..'z', 'A'..'Z'] # base 62
382             b64 => ['0'..'9', 'A'..'Z', 'a'..'z', '.', '_'] # m:C:12 d:V:31
383             m64 => ['A'..'Z', 'a'..'z', '0'..'9', '+', '/'] # MIMI::Base64
384             iru => ['A'..'Z', 'a'..'z', '0'..'9', '[', ']'] # P10 - IRCu
385             url => ['A'..'Z', 'a'..'z', '0'..'9', '*', '-'] # url no %2B %2F
386             rex => ['A'..'Z', 'a'..'z', '0'..'9', '!', '-'] # regex variant
387             id0 => ['A'..'Z', 'a'..'z', '0'..'9', '_', '-'] # ID 0
388             id1 => ['A'..'Z', 'a'..'z', '0'..'9', '.', '_'] # ID 1
389             xnt => ['A'..'Z', 'a'..'z', '0'..'9', '.', '-'] # XML (Nmtoken)
390             xid => ['A'..'Z', 'a'..'z', '0'..'9', '_', ':'] # XML (Name)
391             b85 => ['0'..'9', 'A'..'Z', 'a'..'z', '!', '#', # RFC 1924
392             '$', '%', '&', '(', ')', '*', '+', '-',
393             ';', '<', '=', '>', '?', '@', '^', '_',
394             '', '{', '|', '}', '~']
395             An arbitrary base 96 composed of printable 7 bit ascii
396             from 0x20 (space) through 0x7F (tilde ~)
397             ascii => [
398             ' ','!','"','#','$','%','&',"'",'(',')',
399             '*','+',',','-','.','/',
400             '0','1','2','3','4','5','6','7','8','9',
401             ':',';','<','=','>','?','@',
402             'A','B','C','D','E','F','G','H','I','J','K','L','M',
403             'N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
404             '[','\',']','^','_','`',
405             'a','b','c','d','e','f','g','h','i','j','k','l','m',
406             'n','o','p','q','r','s','t','u','v','w','x','y','z',
407             '{','|','}','~']
408              
409             NOTE: Clean text with =~ s/\s+/ /; before applying to ascii
410              
411             =head1 USAGE
412              
413             =over 4
414              
415             =item * $converted = cnv($number,[from],[to])
416              
417             SCALAR context: array context covered later in this document.
418              
419             To preserve similarity to other similar base conversion modules, B
420             returns the converted number string with SIGN if both the input and output
421             base strings are in known signed set of bases in this module.
422              
423             In the case of binary, octal, hex, all leading base designator strings such as
424             '0b','0', '0x' are automatically stripped from the input. Base designator
425             strings are NOT applied to the output.
426              
427             The context of base FROM and TO is optional and flexible.
428              
429             Unconditional conversion from decimal to HEX [upper case]
430              
431             $converted = cnv($number);
432              
433             Example conversion from octal to default HEX [upper case] with different
434             context for the 'octal' designator.
435              
436             base as a number
437             $converted = cnv($number,8);
438              
439             base as a function (imported)
440             $converted = cnv($number,oct);
441              
442             base as text
443             $converted = convbase($number,'oct');
444              
445             Conversion to/from arbitrary bases i.e.
446              
447             $converted = cnv($number); # dec -> hex (default)
448             $converted = cnv($number,oct); # oct to HEX
449             $converted = cnv($number,10,HEX); # dec to uc HEX
450             $converted = cnv($number,10,hex); # dec to lc hex
451             $converted = cnv($number,dec,hex);# same
452              
453             pointer notation
454             $converted = cnv($number, oct => dec);
455              
456             $converted = cnv($number,10 => 23); # dec to base23
457             $converted = cnv($number,23 => 5); # b23 to base5
458             etc...
459              
460             =item * $bc = new Math::Base::Convert([from],[to]);
461              
462             This method has the same usage and syntax for FROM and TO as B above.
463              
464             Setup for unconditional conversion from HEX to decimal
465              
466             $bc = new Math::Base::Convert();
467              
468             Example conversion from octal to decimal
469              
470             base number
471             $bc = new Math::Base::Convert(8);
472              
473             base function (imported)
474             $bc = new Math::Base::Convert(oct);
475              
476             base text
477             $bc = new Math::Base::Convert('oct')
478              
479             The number conversion for any of the above:
480              
481             NOTE: iterative conversions using a method pointer are ALWAYS faster than
482             calling B as a function.
483              
484             $converted = $bc->cnv($number);
485              
486             =item * $converted = cnvpre($number,[from],[to])
487              
488             Same as B except that base descriptor PREfixes are applied to B,
489             B, and B output strings.
490              
491             =item * $converted = cnvabs($number,[from],[to])
492              
493             Same as B except that the ABSolute value of the number string is
494             returned without SIGN is returned. i.e. just the raw string.
495              
496             =item * ($sign,$prefix,$string) = cnv($number,[$from,[$to]])
497              
498             =item * ($sign,$prefix,$string) = cnv($number,[$from,[$to]])
499              
500             =item * ($sign,$prefix,$string) = cnv($number,[$from,[$to]])
501              
502             ARRAY context:
503              
504             All three functions return the same items in array context.
505              
506             sign the sign of the input number string
507              
508             prefix the prefix which would be applied to output
509              
510             string the raw output string
511              
512             =item * $basemap = basemap(base);
513              
514             =item * $basemap = $bc->basemap(base);
515              
516             This function / method returns a pointer to a hash that maps the keys of a base to its
517             numeric value for base conversion. It accepts B in any of the forms
518             described for B.
519              
520             The return basemap includes upper and lower case variants of the the number
521             base in cases such as B where upper and lower case a..f, A..F map to
522             the same numeric value for base conversion.
523              
524             i.e. $hex_ptr = {
525             0 => 0,
526             1 => 1,
527             2 => 2,
528             3 => 3,
529             4 => 4,
530             5 => 5,
531             6 => 6,
532             7 => 7,
533             8 => 8,
534             9 => 9,
535             A => 10,
536             B => 11,
537             C => 12,
538             D => 13,
539             E => 14,
540             F => 15,
541             a => 10,
542             b => 11,
543             c => 12,
544             d => 13,
545             e => 14,
546             f => 15
547             };
548              
549             =back
550              
551             =head1 BENCHMARKS
552              
553             Math::Base::Convert includes 2 development and one real world benchmark
554             sequences included in the test suite. Benchmark results for a 500mhz system
555             can be found in the 'benchmarks' source directory.
556              
557             make test BENCHMARK=1
558              
559             Provides comparison data for bi-directional conversion of an ascending
560             series of number strings in all base powers. The test sequence contains
561             number strings that go from a a single 32 bit register to several. Tested
562             bases are: (note: b32, b128, b256 not useful and are for testing only)
563              
564             base 2 4 8 16 32 64 85 128 256
565             bin, dna, oct, hex, b32, b64, b85, b128, b256
566              
567             Conversions are performed FROM all bases TO decimal and are repeated in the
568             opposing direction FROM decimal TO all bases.
569              
570             Benchmark 1 results indicate the Math::Base::Convert typically runs
571             significantly faster ( 10x to 100x) than Math::BigInt based
572             implementations used in similar modules.
573              
574             make test BENCHMARK=2
575              
576             Provides comparison data for the frontend and backend converters in
577             Math::Base::Convert's CalcPP and Shortcuts packages, and Math::Bigint
578             conversions if it is present on the system under test.
579              
580             make test BENCHMARK=3
581              
582             Checks the relative timing of short and long number string conversions. FROM
583             a base number to n*32 bit register and TO a base number from an n*32 bit
584             register set.
585              
586             i.e. strings that convert to and from 1, 2, 3... etc.. 32 bit registers
587              
588             =head1 DEPENDENCIES
589              
590             none
591              
592             Math::BigInt is conditionally used in
593             the test suite but is not a requirement
594              
595             =head1 EXPORT_OK
596              
597             Conditional EXPORT functions
598              
599             cnv
600             cnvabs
601             cnvpre
602             basemap
603             bin
604             oct
605             dec
606             heX
607             HEX
608             b62
609             b64
610             m64
611             iru
612             url
613             rex
614             id0
615             id1
616             xnt
617             xid
618             b85
619             ascii
620              
621             =head1 EXPORT_TAGS
622              
623             Conditional EXPORT function groups
624              
625             :all => all of above
626             :base => all except 'cnv,cnvabs,cnvpre'
627              
628             =head1 ACKNOWLEDGEMENTS
629              
630             This module was inspired by Math::BaseConvert maintained by Shane Warden
631             and forked from Math::BaseCnv, both authored by Pip
632             Stuart
633              
634              
635             =head1 AUTHOR
636              
637             Michael Robinton,
638              
639             =head1 COPYRIGHT
640              
641             Copyright 2012-2015, Michael Robinton
642              
643             This program is free software; you may redistribute it and/or modify it
644             under the same terms as Perl itself.
645              
646             This program is distributed in the hope that it will be useful,
647             but WITHOUT ANY WARRANTY; without even the implied warranty of
648             MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
649              
650             =cut
651              
652             1;