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   126 use vars qw($VERSION);
  20         38  
  20         843  
4 20     20   97 use strict;
  20         31  
  20         44785  
5              
6             $VERSION = do { my @r = (q$Revision: 0.04 $ =~ /\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 746 my $ap = $_[0]; # perl appears to optimize these variables into registers
138 609         716 my $sr = $_[1]; # when they are set in this manner -- much faster!!
139 609         690 my $msk = $_[2];
140 609         703 my $sl = $_[3];
141 609         817 my $al = $#$ap -1;
142 609         748 my $i = 1;
143 609         1245 foreach (0..$al) {
144 975         1113 $ap->[$_] >>= $sr;
145             # $ap->[$_] |= ($ap->[$i] & $msk) << $sl;
146 975         1274 $ap->[$_] |= ($ap->[$i] << $sl) & $msk;
147 975         1260 $i++;
148             }
149 609         1497 $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 1456 my($ap,$n) = @_;
157 609         1275 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 140 my($ss,$d32p) = @_;
165 66         226 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 112 my($ss,$d32p) = @_;
179 54         102 my $bn = $dna{substr($ss,0,2)}; # 2 digits as a time => base 16
180 54         62 $bn <<= 4;
181 54         82 $bn += $dna{substr($ss,2,2)};
182 54         69 $bn <<= 4;
183 54         86 $bn += $dna{substr($ss,4,2)};
184 54         60 $bn <<= 4;
185 54         80 $bn += $dna{substr($ss,6,2)};
186 54         61 $bn <<= 4;
187 54         85 $bn += $dna{substr($ss,8,2)};
188 54         60 $bn <<= 4;
189 54         78 $bn += $dna{substr($ss,10,2)};
190 54         54 $bn <<= 4;
191 54         79 $bn += $dna{substr($ss,12,2)};
192 54         63 $bn <<= 4;
193 54         82 $bn += $dna{substr($ss,14,2)};
194 54         107 unshift @$d32p, $bn;
195             }
196              
197             sub bx3 { # base 8, 3 bits wide x10 = 30 bits - 07777777777
198 57     57 0 118 my($ss,$d32p) = @_;
199 57         111 unshift @$d32p, CORE::oct($ss) << 2;
200 57         108 shiftright($d32p,2);
201             }
202              
203             sub bx4 { # base 16, 4 bits wide x8 = 32 bits - 0xffffffff
204 58     58 0 120 my($ss,$d32p) = @_;
205 58         130 unshift @$d32p, CORE::hex($ss);
206             }
207              
208             sub bx5 { # base 32, 5 bits wide x6 = 30 bits - 555555
209 58     58 0 130 my($ss,$d32p,$hsh) = @_;
210 58         114 my $bn = $hsh->{substr($ss,0,1)};
211 58         72 $bn <<= 5;
212 58         87 $bn += $hsh->{substr($ss,1,1)};
213 58         70 $bn <<= 5;
214 58         86 $bn += $hsh->{substr($ss,2,1)};
215 58         70 $bn <<= 5;
216 58         91 $bn += $hsh->{substr($ss,3,1)};
217 58         69 $bn <<= 5;
218 58         80 $bn += $hsh->{substr($ss,4,1)};
219 58         69 $bn <<= 5;
220 58         122 unshift @$d32p, ($bn += $hsh->{substr($ss,5,1)}) << 2;
221 58         117 shiftright($d32p,2);
222             }
223              
224             sub bx6 { # base 64, 6 bits wide x5 = 30 bits - 66666
225 419     419 0 911 my($ss,$d32p,$hsh) = @_;
226 419         768 my $bn = $hsh->{substr($ss,0,1)};
227 419         489 $bn <<= 6;
228 419         655 $bn += $hsh->{substr($ss,1,1)};
229 419         499 $bn <<= 6;
230 419         593 $bn += $hsh->{substr($ss,2,1)};
231 419         431 $bn <<= 6;
232 419         598 $bn += $hsh->{substr($ss,3,1)};
233 419         430 $bn <<= 6;
234 419         923 unshift @$d32p, ($bn += $hsh->{substr($ss,4,1)}) << 2;
235 419         777 shiftright($d32p,2);
236             }
237              
238             sub bx7 { # base 128, 7 bits wide x4 = 28 bits - 7777
239 66     66 0 143 my($ss,$d32p,$hsh) = @_;
240 66         109 my $bn = $hsh->{substr($ss,0,1)};
241 66         85 $bn <<= 7;
242 66         105 $bn += $hsh->{substr($ss,1,1)};
243 66         73 $bn <<= 7;
244 66         94 $bn += $hsh->{substr($ss,2,1)};
245 66         77 $bn <<= 7;
246 66         140 unshift @$d32p, ($bn += $hsh->{substr($ss,3,1)}) << 4;
247 66         126 shiftright($d32p,4);
248             }
249              
250             sub bx8 { # base 256, 8 bits wide x4 = 32 bits - 8888
251 58     58 0 139 my($ss,$d32p,$hsh) = @_;
252 58         111 my $bn = $hsh->{substr($ss,0,1)};
253 58         78 $bn *= 256;
254 58         93 $bn += $hsh->{substr($ss,1,1)};
255 58         73 $bn *= 256;
256 58         93 $bn += $hsh->{substr($ss,2,1)};
257 58         70 $bn *= 256;
258 58         142 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 2997 my $bc = shift;
278 467         613 my($ary,$hsh,$base,$str) = @{$bc}{qw(from fhsh fbase nstr)};
  467         1107  
279 467         1137 my $bp = int(log($base)/log(2) +0.5);
280 467         592 my $len = length($str);
281 467 50       882 return ($bp,[0]) unless $len; # no value in zero length string
282              
283 467         671 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         604 my $basnam = ref $ary;
287 467         692 my $padchar = $ary->[0];
288 467 100       1662 if ($base == 16) { # should be hex
    100          
    100          
    100          
289 14 100       66 if ($basnam !~ /HEX$/i) {
290 2 50       20 $bc->{fHEX} = $bc->HEX() unless exists $bc->{fHEX};
291 2         6 my @h = @{$bc->{fHEX}};
  2         18  
292 2         146 $str =~ s/(.)/$h[$hsh->{$1}]/g; # translate string to HEX
293 2         7 $padchar = 0;
294             }
295             }
296             elsif ($base == 8) {
297 13 100       58 if ($basnam !~ /OCT$/i) {
298 2 50       14 $bc->{foct} = $bc->ocT() unless exists $bc->{foct};
299 2         5 my @o = @{$bc->{foct}};
  2         10  
300 2         162 $str =~ s/(.)/$o[$hsh->{$1}]/g;
301 2         6 $padchar = '0';
302             }
303             }
304             elsif ($base == 4) { # will map to hex
305 13 100       53 if ($basnam !~ /dna$/i) {
306 2 50       14 $bc->{fDNA} = $bc->DNA() unless exists $bc->{fDNA};
307 2         5 my @d = @{$bc->{fDNA}};
  2         62  
308 2         289 $str =~ s/(.)/$d[$hsh->{$1}]/g;
309 2         6 $padchar = 'A';
310             }
311             }
312             elsif ($base == 2) { # will map to binary
313 15 100       111 if ($basnam !~ /bin$/) {
314 1 50       8 $bc->{fbin} = $bc->bin() unless exists $bc->{fbin};
315 1         2 my @b = @{$bc->{fbin}};
  1         3  
316 1         91 $str =~ s/(.)/$b[$hsh->{$1}]/g;
317 1         3 $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         688 my $dpr = int (32 / $bp);
327 467         641 my $pad = $dpr - ($len % $dpr);
328 467 100       820 $pad = 0 if $pad == $dpr;
329 467 100       947 if ($pad) {
330 457         1085 $str = ($padchar x $pad) . $str; # pad string with zero value digit
331             }
332              
333             # number of iterations % digits/register
334 467         729 $len += $pad;
335 467         559 my $i = 0;
336 467         517 my @d32;
337 467         1015 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         2343 $useFROMbaseShortcuts[$bp]->(substr($str,$i,$dpr),\@d32,$hsh);
344 836         2451 $i += $dpr;
345             }
346 467   100     1458 while($#d32 && ! $d32[$#d32]) { # waste leading zeros
347 18         88 pop @d32;
348             }
349 467         1741 $bc->{b32str} = \@d32;
350             }
351              
352             # map non-standard user base to bitstream lookup
353             #
354             sub usrmap {
355 49     49 0 69 my($to,$map) = @_;
356 49         60 my %map;
357 49         188 while (my($key,$val) = each %$map) {
358 6168         20493 $map{$key} = $to->[$val];
359             }
360 49         133 \%map;
361             }
362              
363             sub useTObaseShortcuts {
364 486     486 1 2657 my $bc = shift;
365 486         587 my($base,$b32p,$to) = @{$bc}{qw( tbase b32str to )};
  486         1066  
366 486         1062 my $bp = int(log($base)/log(2) +0.5); # base power
367 486         618 my $L = @$b32p;
368 486         790 my $packed = pack("N$L", reverse @{$b32p});
  486         1153  
369 486         2622 ref($to) =~ /([^:]+)$/; # extract to base name
370 486         880 my $bname = $1;
371 486         570 my $str;
372 486 100       1133 if ($bp == 1) { # binary
    100          
373 121         155 $L *= 32;
374 121         580 ($str = unpack("B$L",$packed)) =~ s/^0+//; # suppress leading zeros
375 121 100       457 $str =~ s/(.)/$to->[$1]/g if $bname eq 'user';
376             }
377             elsif ($bp == 4) { # hex / base 16
378 122         149 $L *= 8;
379 122         549 ($str = unpack("H$L",$packed)) =~ s/^0+//; # suppress leading zeros
380 122 100       391 $str =~ s/(.)/$to->[CORE::hex($1)]/g if $bname eq 'user';
381             }
382             else { # the rest
383 243         273 my $map;
384 243 100       575 if ($bname eq 'user') { # special map request
    100          
385 49 50       132 unless (exists $bc->{tmap}) {
386 49         101 $bc->{tmap} = usrmap($to,$xlt->[$bp]); # cache the map for speed
387             }
388 49         87 $map = $bc->{tmap};
389             }
390             elsif ($bp == 3) { # octal varient?
391 65         105 $map = $xlt->[$bp];
392             } else {
393 129         244 $map = $xlt->[0]->{$bname}; # standard map
394             }
395 243         288 $L *= 32;
396 243         1251 (my $bits = unpack("B$L",$packed)) =~ s/^0+//; # suppress leading zeros
397             #print "bp = $bp, BITS=\n$bits\n";
398 243         405 my $len = length($bits);
399 243         306 my $m = $len % $bp; # pad to even multiple base power
400             #my $z = $m;
401 243 100       528 if ($m) {
402 67         77 $m = $bp - $m;
403 67         163 $bits = ('0' x $m) . $bits;
404 67         113 $len += $m;
405             }
406             #print "len = $len, m_init = $z, m = $m, BITS PADDED\n$bits\n";
407 243         319 $str = '';
408 243         659 for (my $i = 0; $i < $len; $i += $bp) {
409 1829         4529 $str .= $map->{substr($bits,$i,$bp)};
410             #print "MAPPED i=$i, str=$str\n";
411             }
412             }
413 486         1562 $str;
414             }
415              
416             1;
417              
418             __END__