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   247154 use Carp;
  20         40  
  20         2064  
7 20     20   95 use vars qw($VERSION @ISA @EXPORT_OK %EXPORT_TAGS @BASES $signedBase);
  20         28  
  20         4556  
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.09 $ =~ /\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   103 use strict;
  20         37  
  20         4168  
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 6105 shift if ref $_[0] eq $package; # waste if method call
51 2463         4370 my $base = validbase($_[0]); # return array pointer
52 2463         9958 ref($base) =~ /$bs(.+)/; # sub name is $1
53 2463 100       6626 if ($1 eq 'user') { # if user array
54 252         393 my $aryhsh = {};
55 252         641 @{$aryhsh}{@$base} = (0..$#$base);
  252         2355  
56 252         1835 return $aryhsh;
57             }
58 2211         13401 my @all = $functions =~ /$1/gi; # get all matching sub names regardless of case
59             # names are strings
60 20     20   114 no strict;
  20         33  
  20         32237  
61 2211         3190 my %aryhsh;
62 2211         4027 foreach (@all) {
63 3692         12387 $_ = $package->can($_); # return sub ref
64 3692         9172 $_ = &$_; # array pointer
65 3692         8253 foreach my $i (0..$#$_) {
66 72966         131386 $aryhsh{$_->[$i]} = $i; # map keys to index
67             }
68             }
69 2211         14464 return \%aryhsh;
70             }
71            
72             # check for internal base
73             sub validbase {
74 7396     7396 0 9243 my $base = shift;
75 7396         7922 my $ref;
76 7396 100       21528 if (($ref = ref $base)) {
    100          
77 3191 100       14143 if ($ref eq 'ARRAY') { # user supplied
    100          
78 526         609 my @base = @{$base};
  526         3500  
79 526         773 my $len = @base;
80 526 50       1098 Carp::croak "base to short, < 2" unless $len > 1;
81 526 50       985 Carp::croak "base to long, > 65535" unless $len < 65536;
82 526         1125 $base = bless \@base, $bs .'user';
83 526         1533 return bless $base, $bs . 'user';
84             }
85             elsif ($ref =~ /^$bs/) { # internal base
86 2664         5961 return $base;
87             }
88             else {
89 1         3 $base = 'reference';
90             }
91             }
92             elsif ($base =~ /\D/) { # is a string
93 4195         13807 my $rv = $package->can($base);
94 4195 100       14466 return &$rv if $rv;
95             } else {
96 10 100       52 return $num2sub{$base} if exists $num2sub{$base};
97             }
98 3         479 Carp::croak "not a valid base: $base";
99             }
100              
101             sub vet {
102 2458     2458 0 3006 my $class = shift;
103 2458   100     4951 my $from = shift || '';
104 2458   100     4826 my $to = shift || '';
105              
106 2458 100 100     11627 $to =~ s/\s+//g if $to && ! ref $to; # strip white space
107 2458 100 100     10382 $from =~ s/\s+//g if $from && ! ref $from;
108              
109 2458 100       4075 unless ($from) { # defaults if not defined
110 3         17 $to = &HEX;
111 3         13 $from = &dec;
112             }
113             else {
114 2455         4466 $from = validbase($from);
115 2455 100       4703 unless ($to) {
116 2         12 $to = &HEX;
117             } else {
118 2453         4456 $to = validbase($to);
119             }
120             }
121              
122             # convert sub ref's to variables
123             # $to = &$to;
124             # ($from, my $fhsh) = &$from;
125              
126 2458         4034 my $prefix = ref $to;
127 2458 100       9894 if ($prefix =~ /HEX$/i) {
    100          
    100          
128 412         612 $prefix = '0x';
129             }
130             elsif ($prefix =~ /OCT$/i) {
131 214         309 $prefix = '0';
132             }
133             elsif ($prefix =~ /bin$/) {
134 212         340 $prefix = '0b';
135             } else {
136 1620         2356 $prefix = '';
137             }
138              
139 2458         5166 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 7968 my $proto = shift;
151 2451   33     10370 my $class = ref $proto || $proto || $package;
152 2451         4490 vet($class,@_);
153             }
154              
155             sub _cnv {
156 2527     2527   6186 my $bc = shift;
157 2527         2651 my $nstr;
158 2527 100 66     7165 if (ref $bc && ref($bc) eq $package) { # method call?
159 348         545 $nstr = shift; # yes, number to convert is next arg
160             } else {
161 2179         2413 $nstr = $bc; # no, first arg is number to convert
162 2179         5460 $bc = $package->new(@_);
163             }
164 2527 50       6683 return $nstr unless keys %$bc; # if there really is no conversion
165 2527 50       4656 $nstr = '' unless defined $nstr;
166              
167 2527         3190 my($from,$fbase,$fhsh) = @{$bc}{qw( from fbase fhsh )};
  2527         5423  
168              
169 2527         3954 my $ref = ref $from;
170 2527 100 66     10202 if ($ref eq 'user' || $fbase > $signedBase) { # known, signed character sets?
171 660         1294 $bc->{sign} = ''; # no
172             } else { # yes
173 1867         3483 $nstr =~ s/^([+-])//; # strip sign
174 1867 50 33     7224 $bc->{sign} = $1 && $1 eq '-' ? '-' : ''; # and save for possible restoration
175              
176 1867 100       7072 if ($ref =~ /(HEX)$/i) {
    100          
177 548         1373 $nstr =~ s/^0x//i; # snip prefix, including typo's
178             }
179             elsif ($ref =~ /bin/i) {
180 205         561 $nstr =~ s/^0b//i; # snip prefix, including typo's
181             }
182              
183 1867         8596 $nstr =~ s/^[$from->[0]]+//; # snip leading zeros
184             }
185              
186 2527         14839 my $fclass = join '', keys %$fhsh;
187 2527 50       56097 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         7407 $bc->{nstr} = $nstr;
193 2527         6323 $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 7558 my @rv = &cnvpre;
217 799 100       2828 return @rv if wantarray;
218 436         1340 return ($rv[0] . $rv[2]); # sign and string only
219             }
220              
221             sub cnvabs {
222 727     727 1 6116 my @rv = &cnvpre;
223 727 100       2769 return @rv if wantarray;
224 364         993 return $rv[2] # string only
225             }
226              
227             sub cnvpre {
228 2252     2252 1 8945 my $bc = &_cnv;
229 2252 50       4910 return $bc unless ref $bc;
230 2252         2910 my($from,$fbase,$to,$tbase,$sign,$prefix,$nstr) = @{$bc}{qw( from fbase to tbase sign prefix nstr)};
  2252         6100  
231              
232 2252         3367 my $slen = length($nstr);
233 2252         3216 my $tref = ref($to);
234 2252 100       5250 unless ($slen) { # zero length input
    100          
235 1584         2284 $nstr = $to->[0]; # return zero
236             }
237             elsif (lc $tref eq lc ref($from)) {# no base conversion
238 54 50       134 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         859 my $fblen = length($fbase);
249 614 100 66     2923 if ($fbase & $fbase -1 || # from base is not power of 2
    50 33        
250             $fblen > 256 ) { # no shortcuts,...
251 220         678 $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         1127 $bc->useFROMbaseShortcuts;
262             }
263              
264             ################################
265             # input converted to base 2^32 #
266             ################################
267              
268 614 100 66     3033 if ($tbase & $tbase -1 || # from base is not power of 2
    100 100        
269             $tbase > 256 ) { # no shortcuts,...
270 130         362 $nstr = $bc->use32wideTObase;
271             }
272             # if big base and digit string fits in a single 32 bit register
273 86         285 elsif ( $tbase > 32 && @{$bc->{b32str}} == 1) {
274 75         205 $nstr = $bc->use32wideTObase; # CalcPP is faster
275             }
276             else {
277 409         1144 $nstr = $bc->useTObaseShortcuts; # shortcuts faster for big numbers
278             }
279             } # end convert
280              
281 2252 100       4507 $nstr = $to->[0] unless length($nstr);
282 2252 100       16770 return ($sign,$prefix,$nstr) if wantarray;
283 363 100 66     1349 if (#$prefix ne '' && # 0, 0x, 0b
284             $tbase <= $signedBase && # base in signed set
285             $tref ne 'user' ) { # base standard
286 264         2098 return ($sign . $prefix . $nstr);
287             }
288 99         739 return ($prefix . $nstr);
289             }
290            
291             sub _cnvtst {
292 204     204   1386 my $bc = &_cnv;
293 204 50       470 return $bc unless ref $bc;
294 204         614 $bc->useFROMbaseto32wide;
295 204 50       787 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 varient
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 varients 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 varient
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 arbitary 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 varients 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;