File Coverage

blib/lib/Math/Base/Convert/Shortcuts.pm
Criterion Covered Total %
statement 173 173 100.0
branch 40 46 86.9
condition 3 3 100.0
subroutine 15 15 100.0
pod 2 13 15.3
total 233 250 93.2


line stmt bran cond sub pod time code
1             package Math::Base::Convert::Shortcuts;
2              
3 20     20   128 use vars qw($VERSION);
  20         41  
  20         1188  
4 20     20   111 use strict;
  20         31  
  20         60706  
5              
6             $VERSION = do { my @r = (q$Revision: 0.05 $ =~ /\d+/g); sprintf "%d."."%02d" x $#r, @r };
7              
8             # load bitmaps
9              
10             my $xlt = require Math::Base::Convert::Bitmaps;
11              
12             #
13             # base 2 4 8 16 32 64
14             # base power 1 2 3 4 5 6
15             # xlt = [ \@standardbases, undef, \%_2wide, undef, undef, \%_5wide, \%_6wide ];
16             #
17             # base 2 maps directly to lookup key
18             # base 3 maps directly to standard lookup value
19             # base 4 converts directly to hex
20             #
21             # where @standardbases = (\{
22             # dna => {
23             # '00' => 'a',
24             # '01' => 'c',
25             # '10' => 't',
26             # '11' => 'g',
27             # },
28             # b64 => {
29             # '000000' => 0,
30             # '000001' => 1,
31             # * -
32             # * -
33             # '001010' => 'A',
34             # '001011' => 'B',
35             # * -
36             # * -
37             # '111111' => '_',
38             # },
39             # m64 => etc....
40             # iru
41             # url
42             # rex
43             # id0
44             # id1
45             # xnt
46             # xid
47             # });
48             #
49             # .... and
50             #
51             # hash arrays are bit to value maps of the form
52             #
53             # %_3wide = {
54             # '000' => 0,
55             # '001' => 1,
56             # '010' => 2,
57             # * -
58             # * -
59             # etc...
60             # };
61             #
62              
63             my @srindx = ( # accomodate up to 31 bit shifts
64             0, # 0 unused
65             1, # 1
66             3, # 2
67             7, # 3
68             0xf, # 4
69             0x1f, # 5
70             0x3f, # 6
71             0x7f, # 7
72             0xff, # 8
73             0x1ff, # 9
74             0x3ff, # 10
75             0x7ff, # 11
76             0xfff, # 12
77             0x1fff, # 13
78             0x3fff, # 14
79             0x7fff, # 15
80             0xffff, # 16
81             0x1ffff, # 17
82             0x3ffff, # 18
83             0x7ffff, # 19
84             0xfffff, # 20
85             0x1fffff, # 21
86             0x3fffff, # 22
87             0x7fffff, # 23
88             0xffffff, # 24
89             0x1ffffff, # 25
90             0x3ffffff, # 26
91             0x7ffffff, # 27
92             0xfffffff, # 28
93             0x1fffffff, # 29
94             0x3fffffff, # 30
95             0x7fffffff # 31
96             );
97              
98             my @srindx2 = ( # accomodate up to 31 bit shifts
99             0xffffffff, # 0 unused
100             0xfffffffe, # 1
101             0xfffffffc, # 2
102             0xfffffff8, # 3
103             0xfffffff0, # 4
104             0xffffffe0, # 5
105             0xffffffc0, # 6
106             0xffffff80, # 7
107             0xffffff00, # 8
108             0xfffffe00, # 9
109             0xfffffc00, # 10
110             0xfffff800, # 11
111             0xfffff000, # 12
112             0xffffe000, # 13
113             0xffffc000, # 14
114             0xffff8000, # 15
115             0xffff0000, # 16
116             0xfffe0000, # 17
117             0xfffc0000, # 18
118             0xfff80000, # 19
119             0xfff00000, # 20
120             0xffe00000, # 21
121             0xffc00000, # 22
122             0xff800000, # 23
123             0xff000000, # 24
124             0xfe000000, # 25
125             0xfc000000, # 26
126             0xf8000000, # 27
127             0xf0000000, # 28
128             0xe0000000, # 29
129             0xc0000000, # 30
130             0x80000000 # 31
131             );
132              
133             #
134             # $arraypointer, $shiftright, $mask, $shiftleft
135             #
136             sub longshiftright {
137 609     609 0 1012 my $ap = $_[0]; # perl appears to optimize these variables into registers
138 609         922 my $sr = $_[1]; # when they are set in this manner -- much faster!!
139 609         941 my $msk = $_[2];
140 609         975 my $sl = $_[3];
141 609         1305 my $al = $#$ap -1;
142 609         1061 my $i = 1;
143 609         1554 foreach (0..$al) {
144 975         1148 $ap->[$_] >>= $sr;
145             # $ap->[$_] |= ($ap->[$i] & $msk) << $sl;
146 975         1309 $ap->[$_] |= ($ap->[$i] << $sl) & $msk;
147 975         1216 $i++;
148             }
149 609         1836 $ap->[$#$ap] >>= $sr;
150             }
151              
152             # see the comments at "longshiftright" about the
153             # integration of calculations into the local subroutine
154             #
155             sub shiftright {
156 609     609 0 1570 my($ap,$n) = @_;
157 609         1473 longshiftright($ap,$n,$srindx2[$n],32 -$n);
158             }
159              
160             #
161             # fast direct conversion of base power of 2 sets to base 2^32
162             #
163             sub bx1 { # base 2, 1 bit wide x32 = 32 bits - 111 32 1's 111111111111111
164 66     66 0 151 my($ss,$d32p) = @_;
165 66         205 unshift @$d32p, unpack('N1',pack('B32',$ss));
166             }
167              
168             my %dna= ('AA', 0, 'AC', 1, 'AT', 2, 'AG', 3, 'CA', 4, 'CC', 5, 'CT', 6, 'CG', 7, 'TA', 8, 'TC', 9, 'TT', 10, 'TG', 11, 'GA', 12, 'GC', 13, 'GT', 14, 'GG', 15,
169             'Aa', 0, 'Ac', 1, 'At', 2, 'Ag', 3, 'Ca', 4, 'Cc', 5, 'Ct', 6, 'Cg', 7, 'Ta', 8, 'Tc', 9, 'Tt', 10, 'Tg', 11, 'Ga', 12, 'Gc', 13, 'Gt', 14, 'Gg', 15,
170             'aA', 0, 'aC', 1, 'aT', 2, 'aG', 3, 'cA', 4, 'cC', 5, 'cT', 6, 'cG', 7, 'tA', 8, 'tC', 9, 'tT', 10, 'tG', 11, 'gA', 12, 'gC', 13, 'gT', 14, 'gG', 15,
171             'aa', 0, 'ac', 1, 'at', 2, 'ag', 3, 'ca', 4, 'cc', 5, 'ct', 6, 'cg', 7, 'ta', 8, 'tc', 9, 'tt', 10, 'tg', 11, 'ga', 12, 'gc', 13, 'gt', 14, 'gg', 15,
172              
173             );
174              
175             # substr 4x faster than array lookup
176             #
177             sub bx2 { # base 4, 2 bits wide x16 = 32 bits - 3333333333333333
178 54     54 0 166 my($ss,$d32p) = @_;
179 54         115 my $bn = $dna{substr($ss,0,2)}; # 2 digits as a time => base 16
180 54         99 $bn <<= 4;
181 54         101 $bn += $dna{substr($ss,2,2)};
182 54         75 $bn <<= 4;
183 54         95 $bn += $dna{substr($ss,4,2)};
184 54         82 $bn <<= 4;
185 54         106 $bn += $dna{substr($ss,6,2)};
186 54         106 $bn <<= 4;
187 54         104 $bn += $dna{substr($ss,8,2)};
188 54         71 $bn <<= 4;
189 54         96 $bn += $dna{substr($ss,10,2)};
190 54         66 $bn <<= 4;
191 54         96 $bn += $dna{substr($ss,12,2)};
192 54         71 $bn <<= 4;
193 54         99 $bn += $dna{substr($ss,14,2)};
194 54         111 unshift @$d32p, $bn;
195             }
196              
197             sub bx3 { # base 8, 3 bits wide x10 = 30 bits - 07777777777
198 57     57 0 167 my($ss,$d32p) = @_;
199 57         196 unshift @$d32p, CORE::oct($ss) << 2;
200 57         109 shiftright($d32p,2);
201             }
202              
203             sub bx4 { # base 16, 4 bits wide x8 = 32 bits - 0xffffffff
204 58     58 0 165 my($ss,$d32p) = @_;
205 58         115 unshift @$d32p, CORE::hex($ss);
206             }
207              
208             sub bx5 { # base 32, 5 bits wide x6 = 30 bits - 555555
209 58     58 0 168 my($ss,$d32p,$hsh) = @_;
210 58         123 my $bn = $hsh->{substr($ss,0,1)};
211 58         95 $bn <<= 5;
212 58         112 $bn += $hsh->{substr($ss,1,1)};
213 58         203 $bn <<= 5;
214 58         107 $bn += $hsh->{substr($ss,2,1)};
215 58         93 $bn <<= 5;
216 58         113 $bn += $hsh->{substr($ss,3,1)};
217 58         114 $bn <<= 5;
218 58         118 $bn += $hsh->{substr($ss,4,1)};
219 58         84 $bn <<= 5;
220 58         143 unshift @$d32p, ($bn += $hsh->{substr($ss,5,1)}) << 2;
221 58         123 shiftright($d32p,2);
222             }
223              
224             sub bx6 { # base 64, 6 bits wide x5 = 30 bits - 66666
225 419     419 0 1519 my($ss,$d32p,$hsh) = @_;
226 419         1232 my $bn = $hsh->{substr($ss,0,1)};
227 419         776 $bn <<= 6;
228 419         1019 $bn += $hsh->{substr($ss,1,1)};
229 419         712 $bn <<= 6;
230 419         882 $bn += $hsh->{substr($ss,2,1)};
231 419         664 $bn <<= 6;
232 419         831 $bn += $hsh->{substr($ss,3,1)};
233 419         656 $bn <<= 6;
234 419         1231 unshift @$d32p, ($bn += $hsh->{substr($ss,4,1)}) << 2;
235 419         1067 shiftright($d32p,2);
236             }
237              
238             sub bx7 { # base 128, 7 bits wide x4 = 28 bits - 7777
239 66     66 0 150 my($ss,$d32p,$hsh) = @_;
240 66         148 my $bn = $hsh->{substr($ss,0,1)};
241 66         93 $bn <<= 7;
242 66         108 $bn += $hsh->{substr($ss,1,1)};
243 66         87 $bn <<= 7;
244 66         105 $bn += $hsh->{substr($ss,2,1)};
245 66         83 $bn <<= 7;
246 66         169 unshift @$d32p, ($bn += $hsh->{substr($ss,3,1)}) << 4;
247 66         129 shiftright($d32p,4);
248             }
249              
250             sub bx8 { # base 256, 8 bits wide x4 = 32 bits - 8888
251 58     58 0 105 my($ss,$d32p,$hsh) = @_;
252 58         87 my $bn = $hsh->{substr($ss,0,1)};
253 58         69 $bn *= 256;
254 58         80 $bn += $hsh->{substr($ss,1,1)};
255 58         85 $bn *= 256;
256 58         73 $bn += $hsh->{substr($ss,2,1)};
257 58         89 $bn *= 256;
258 58         119 unshift @$d32p, $bn += $hsh->{substr($ss,3,1)};
259             }
260              
261             my @useFROMbaseShortcuts = ( 0, # unused
262             \&bx1, # base 2, 1 bit wide x32 = 32 bits - 111 32 1's 111111111111111
263             \&bx2, # base 4, 2 bits wide x16 = 32 bits - 3333333333333333
264             \&bx3, # base 8, 3 bits wide x10 = 30 bits - 07777777777
265             \&bx4, # base 16, 4 bits wide x8 = 32 bits - 0xffffffff
266             \&bx5, # base 32, 5 bits wide x6 = 30 bits - 555555
267             \&bx6, # base 64, 6 bits wide x5 = 30 bits - 66666
268             \&bx7, # base 128, 7 bits wide x4 = 28 bits - 7777
269             \&bx8, # and base 256, 8 bits wide x4 = 32 bits - 8888
270             );
271              
272             # 1) find number of digits of base that will fit in 2^32
273             # 2) pad msb's
274             # 3) substr digit groups and get value
275              
276             sub useFROMbaseShortcuts {
277 467     467 1 4057 my $bc = shift;
278 467         823 my($ary,$hsh,$base,$str) = @{$bc}{qw(from fhsh fbase nstr)};
  467         1676  
279 467         1804 my $bp = int(log($base)/log(2) +0.5);
280 467         842 my $len = length($str);
281 467 50       1141 return ($bp,[0]) unless $len; # no value in zero length string
282              
283 467         987 my $shrink = 32 % ($bp * $base); # bits short of 16 bits
284              
285             # convert any strings in standard convertable bases that are NOT standard strings to the standard
286 467         877 my $basnam = ref $ary;
287 467         1126 my $padchar = $ary->[0];
288 467 100       1706 if ($base == 16) { # should be hex
    100          
    100          
    100          
289 14 100       79 if ($basnam !~ /HEX$/i) {
290 2 50       17 $bc->{fHEX} = $bc->HEX() unless exists $bc->{fHEX};
291 2         24 my @h = @{$bc->{fHEX}};
  2         13  
292 2         156 $str =~ s/(.)/$h[$hsh->{$1}]/g; # translate string to HEX
293 2         9 $padchar = 0;
294             }
295             }
296             elsif ($base == 8) {
297 13 100       67 if ($basnam !~ /OCT$/i) {
298 2 50       76 $bc->{foct} = $bc->ocT() unless exists $bc->{foct};
299 2         4 my @o = @{$bc->{foct}};
  2         7  
300 2         114 $str =~ s/(.)/$o[$hsh->{$1}]/g;
301 2         7 $padchar = '0';
302             }
303             }
304             elsif ($base == 4) { # will map to hex
305 13 100       68 if ($basnam !~ /dna$/i) {
306 2 50       20 $bc->{fDNA} = $bc->DNA() unless exists $bc->{fDNA};
307 2         4 my @d = @{$bc->{fDNA}};
  2         10  
308 2         151 $str =~ s/(.)/$d[$hsh->{$1}]/g;
309 2         6 $padchar = 'A';
310             }
311             }
312             elsif ($base == 2) { # will map to binary
313 15 100       60 if ($basnam !~ /bin$/) {
314 1 50       6 $bc->{fbin} = $bc->bin() unless exists $bc->{fbin};
315 1         3 my @b = @{$bc->{fbin}};
  1         3  
316 1         112 $str =~ s/(.)/$b[$hsh->{$1}]/g;
317 1         4 $padchar = '0';
318             }
319             }
320              
321             # digits per 32 bit register - $dpr
322             # $dpr = int(32 / $bp) = 32 / digit bit width
323             #
324             # number of digits to pad string so the last digit fits exactly in a 32 bit register
325             # $pad = digits_per_reg - (string_length % $dpr)
326 467         2485 my $dpr = int (32 / $bp);
327 467         887 my $pad = $dpr - ($len % $dpr);
328 467 100       1040 $pad = 0 if $pad == $dpr;
329 467 100       1052 if ($pad) {
330 457         1303 $str = ($padchar x $pad) . $str; # pad string with zero value digit
331             }
332              
333             # number of iterations % digits/register
334 467         811 $len += $pad;
335 467         760 my $i = 0;
336 467         855 my @d32;
337 467         1133 while ($i < $len) {
338             #
339             # base16 digit = sub bx[base power](string fragment )
340             # where base power is the width of each nibble and
341             # base is the symbol value width in bits
342              
343 836         3320 $useFROMbaseShortcuts[$bp]->(substr($str,$i,$dpr),\@d32,$hsh);
344 836         2548 $i += $dpr;
345             }
346 467   100     1514 while($#d32 && ! $d32[$#d32]) { # waste leading zeros
347 18         76 pop @d32;
348             }
349 467         2210 $bc->{b32str} = \@d32;
350             }
351              
352             # map non-standard user base to bitstream lookup
353             #
354             sub usrmap {
355 49     49 0 138 my($to,$map) = @_;
356 49         78 my %map;
357 49         205 while (my($key,$val) = each %$map) {
358 6168         18183 $map{$key} = $to->[$val];
359             }
360 49         161 \%map;
361             }
362              
363             sub useTObaseShortcuts {
364 486     486 1 10819 my $bc = shift;
365 486         907 my($base,$b32p,$to) = @{$bc}{qw( tbase b32str to )};
  486         1607  
366 486         1529 my $bp = int(log($base)/log(2) +0.5); # base power
367 486         891 my $L = @$b32p;
368 486         1184 my $packed = pack("N$L", reverse @{$b32p});
  486         2712  
369 486         3906 ref($to) =~ /([^:]+)$/; # extract to base name
370 486         1505 my $bname = $1;
371 486         812 my $str;
372 486 100       1490 if ($bp == 1) { # binary
    100          
373 121         254 $L *= 32;
374 121         1033 ($str = unpack("B$L",$packed)) =~ s/^0+//; # suppress leading zeros
375 121 100       534 $str =~ s/(.)/$to->[$1]/g if $bname eq 'user';
376             }
377             elsif ($bp == 4) { # hex / base 16
378 122         266 $L *= 8;
379 122         918 ($str = unpack("H$L",$packed)) =~ s/^0+//; # suppress leading zeros
380 122 100       466 $str =~ s/(.)/$to->[CORE::hex($1)]/g if $bname eq 'user';
381             }
382             else { # the rest
383 243         1694 my $map;
384 243 100       638 if ($bname eq 'user') { # special map request
    100          
385 49 50       158 unless (exists $bc->{tmap}) {
386 49         129 $bc->{tmap} = usrmap($to,$xlt->[$bp]); # cache the map for speed
387             }
388 49         98 $map = $bc->{tmap};
389             }
390             elsif ($bp == 3) { # octal variant?
391 65         168 $map = $xlt->[$bp];
392             } else {
393 129         372 $map = $xlt->[0]->{$bname}; # standard map
394             }
395 243         403 $L *= 32;
396 243         2123 (my $bits = unpack("B$L",$packed)) =~ s/^0+//; # suppress leading zeros
397             #print "bp = $bp, BITS=\n$bits\n";
398 243         527 my $len = length($bits);
399 243         438 my $m = $len % $bp; # pad to even multiple base power
400             #my $z = $m;
401 243 100       552 if ($m) {
402 67         117 $m = $bp - $m;
403 67         163 $bits = ('0' x $m) . $bits;
404 67         107 $len += $m;
405             }
406             #print "len = $len, m_init = $z, m = $m, BITS PADDED\n$bits\n";
407 243         433 $str = '';
408 243         748 for (my $i = 0; $i < $len; $i += $bp) {
409 1829         3961 $str .= $map->{substr($bits,$i,$bp)};
410             #print "MAPPED i=$i, str=$str\n";
411             }
412             }
413 486         1758 $str;
414             }
415              
416             1;
417              
418             __END__
419              
420             =head1 NAME
421              
422             Math::Base::Convert::Shortcuts - methods for converting powers of 2 bases
423              
424             =head1 DESCRIPTION
425              
426             This module contains two primary methods that convert bases that are exact
427             powers of 2 to and from base 2^32 faster than can be done by pure perl math.
428              
429             =over 4
430              
431             =item * $bc->useFROMbaseShortcuts
432              
433             This method converts FROM an input base number to a long n*32 bit register
434              
435             =item * $output = $bc->useTObaseShortcuts;
436              
437             This method converts an n*32 bit registers TO an output base number.
438              
439             =item * EXPORTS
440              
441             None
442              
443             =back
444              
445             =head1 AUTHOR
446            
447             Michael Robinton, michael@bizsystems.com
448              
449             =head1 COPYRIGHT
450              
451             Copyright 2012-2015, Michael Robinton
452              
453             This program is free software; you may redistribute it and/or modify it
454             under the same terms as Perl itself.
455              
456             This program is distributed in the hope that it will be useful,
457             but WITHOUT ANY WARRANTY; without even the implied warranty of
458             MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
459            
460             =cut
461              
462             1;