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   102 use vars qw($VERSION);
  20         35  
  20         875  
4 20     20   102 use strict;
  20         35  
  20         46266  
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 787 my $ap = $_[0]; # perl appears to optimize these variables into registers
138 609         788 my $sr = $_[1]; # when they are set in this manner -- much faster!!
139 609         721 my $msk = $_[2];
140 609         744 my $sl = $_[3];
141 609         916 my $al = $#$ap -1;
142 609         829 my $i = 1;
143 609         1529 foreach (0..$al) {
144 975         1140 $ap->[$_] >>= $sr;
145             # $ap->[$_] |= ($ap->[$i] & $msk) << $sl;
146 975         1493 $ap->[$_] |= ($ap->[$i] << $sl) & $msk;
147 975         1323 $i++;
148             }
149 609         1677 $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 2457 my($ap,$n) = @_;
157 609         1400 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 131 my($ss,$d32p) = @_;
165 66         232 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 145 my($ss,$d32p) = @_;
179 54         105 my $bn = $dna{substr($ss,0,2)}; # 2 digits as a time => base 16
180 54         70 $bn <<= 4;
181 54         108 $bn += $dna{substr($ss,2,2)};
182 54         71 $bn <<= 4;
183 54         88 $bn += $dna{substr($ss,4,2)};
184 54         62 $bn <<= 4;
185 54         90 $bn += $dna{substr($ss,6,2)};
186 54         66 $bn <<= 4;
187 54         84 $bn += $dna{substr($ss,8,2)};
188 54         62 $bn <<= 4;
189 54         80 $bn += $dna{substr($ss,10,2)};
190 54         60 $bn <<= 4;
191 54         80 $bn += $dna{substr($ss,12,2)};
192 54         64 $bn <<= 4;
193 54         79 $bn += $dna{substr($ss,14,2)};
194 54         123 unshift @$d32p, $bn;
195             }
196              
197             sub bx3 { # base 8, 3 bits wide x10 = 30 bits - 07777777777
198 57     57 0 135 my($ss,$d32p) = @_;
199 57         121 unshift @$d32p, CORE::oct($ss) << 2;
200 57         118 shiftright($d32p,2);
201             }
202              
203             sub bx4 { # base 16, 4 bits wide x8 = 32 bits - 0xffffffff
204 58     58 0 111 my($ss,$d32p) = @_;
205 58         125 unshift @$d32p, CORE::hex($ss);
206             }
207              
208             sub bx5 { # base 32, 5 bits wide x6 = 30 bits - 555555
209 58     58 0 129 my($ss,$d32p,$hsh) = @_;
210 58         106 my $bn = $hsh->{substr($ss,0,1)};
211 58         69 $bn <<= 5;
212 58         88 $bn += $hsh->{substr($ss,1,1)};
213 58         67 $bn <<= 5;
214 58         86 $bn += $hsh->{substr($ss,2,1)};
215 58         63 $bn <<= 5;
216 58         85 $bn += $hsh->{substr($ss,3,1)};
217 58         63 $bn <<= 5;
218 58         87 $bn += $hsh->{substr($ss,4,1)};
219 58         63 $bn <<= 5;
220 58         120 unshift @$d32p, ($bn += $hsh->{substr($ss,5,1)}) << 2;
221 58         114 shiftright($d32p,2);
222             }
223              
224             sub bx6 { # base 64, 6 bits wide x5 = 30 bits - 66666
225 419     419 0 1009 my($ss,$d32p,$hsh) = @_;
226 419         910 my $bn = $hsh->{substr($ss,0,1)};
227 419         534 $bn <<= 6;
228 419         734 $bn += $hsh->{substr($ss,1,1)};
229 419         506 $bn <<= 6;
230 419         692 $bn += $hsh->{substr($ss,2,1)};
231 419         493 $bn <<= 6;
232 419         640 $bn += $hsh->{substr($ss,3,1)};
233 419         535 $bn <<= 6;
234 419         1018 unshift @$d32p, ($bn += $hsh->{substr($ss,4,1)}) << 2;
235 419         910 shiftright($d32p,2);
236             }
237              
238             sub bx7 { # base 128, 7 bits wide x4 = 28 bits - 7777
239 66     66 0 144 my($ss,$d32p,$hsh) = @_;
240 66         115 my $bn = $hsh->{substr($ss,0,1)};
241 66         78 $bn <<= 7;
242 66         109 $bn += $hsh->{substr($ss,1,1)};
243 66         74 $bn <<= 7;
244 66         105 $bn += $hsh->{substr($ss,2,1)};
245 66         72 $bn <<= 7;
246 66         138 unshift @$d32p, ($bn += $hsh->{substr($ss,3,1)}) << 4;
247 66         130 shiftright($d32p,4);
248             }
249              
250             sub bx8 { # base 256, 8 bits wide x4 = 32 bits - 8888
251 58     58 0 124 my($ss,$d32p,$hsh) = @_;
252 58         99 my $bn = $hsh->{substr($ss,0,1)};
253 58         65 $bn *= 256;
254 58         94 $bn += $hsh->{substr($ss,1,1)};
255 58         66 $bn *= 256;
256 58         88 $bn += $hsh->{substr($ss,2,1)};
257 58         60 $bn *= 256;
258 58         138 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 10493 my $bc = shift;
278 467         763 my($ary,$hsh,$base,$str) = @{$bc}{qw(from fhsh fbase nstr)};
  467         1443  
279 467         1469 my $bp = int(log($base)/log(2) +0.5);
280 467         638 my $len = length($str);
281 467 50       992 return ($bp,[0]) unless $len; # no value in zero length string
282              
283 467         787 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         726 my $basnam = ref $ary;
287 467         757 my $padchar = $ary->[0];
288 467 100       1788 if ($base == 16) { # should be hex
    100          
    100          
    100          
289 14 100       64 if ($basnam !~ /HEX$/i) {
290 2 50       17 $bc->{fHEX} = $bc->HEX() unless exists $bc->{fHEX};
291 2         4 my @h = @{$bc->{fHEX}};
  2         17  
292 2         127 $str =~ s/(.)/$h[$hsh->{$1}]/g; # translate string to HEX
293 2         8 $padchar = 0;
294             }
295             }
296             elsif ($base == 8) {
297 13 100       62 if ($basnam !~ /OCT$/i) {
298 2 50       19 $bc->{foct} = $bc->ocT() unless exists $bc->{foct};
299 2         5 my @o = @{$bc->{foct}};
  2         12  
300 2         213 $str =~ s/(.)/$o[$hsh->{$1}]/g;
301 2         9 $padchar = '0';
302             }
303             }
304             elsif ($base == 4) { # will map to hex
305 13 100       65 if ($basnam !~ /dna$/i) {
306 2 50       20 $bc->{fDNA} = $bc->DNA() unless exists $bc->{fDNA};
307 2         7 my @d = @{$bc->{fDNA}};
  2         24  
308 2         315 $str =~ s/(.)/$d[$hsh->{$1}]/g;
309 2         8 $padchar = 'A';
310             }
311             }
312             elsif ($base == 2) { # will map to binary
313 15 100       119 if ($basnam !~ /bin$/) {
314 1 50       10 $bc->{fbin} = $bc->bin() unless exists $bc->{fbin};
315 1         2 my @b = @{$bc->{fbin}};
  1         4  
316 1         92 $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         751 my $dpr = int (32 / $bp);
327 467         703 my $pad = $dpr - ($len % $dpr);
328 467 100       950 $pad = 0 if $pad == $dpr;
329 467 100       1031 if ($pad) {
330 457         1168 $str = ($padchar x $pad) . $str; # pad string with zero value digit
331             }
332              
333             # number of iterations % digits/register
334 467         791 $len += $pad;
335 467         557 my $i = 0;
336 467         564 my @d32;
337 467         1109 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         2543 $useFROMbaseShortcuts[$bp]->(substr($str,$i,$dpr),\@d32,$hsh);
344 836         2718 $i += $dpr;
345             }
346 467   100     1679 while($#d32 && ! $d32[$#d32]) { # waste leading zeros
347 18         83 pop @d32;
348             }
349 467         2307 $bc->{b32str} = \@d32;
350             }
351              
352             # map non-standard user base to bitstream lookup
353             #
354             sub usrmap {
355 49     49 0 71 my($to,$map) = @_;
356 49         58 my %map;
357 49         201 while (my($key,$val) = each %$map) {
358 6168         21240 $map{$key} = $to->[$val];
359             }
360 49         143 \%map;
361             }
362              
363             sub useTObaseShortcuts {
364 486     486 1 5503 my $bc = shift;
365 486         615 my($base,$b32p,$to) = @{$bc}{qw( tbase b32str to )};
  486         1192  
366 486         1324 my $bp = int(log($base)/log(2) +0.5); # base power
367 486         755 my $L = @$b32p;
368 486         832 my $packed = pack("N$L", reverse @{$b32p});
  486         1579  
369 486         3055 ref($to) =~ /([^:]+)$/; # extract to base name
370 486         907 my $bname = $1;
371 486         540 my $str;
372 486 100       1250 if ($bp == 1) { # binary
    100          
373 121         161 $L *= 32;
374 121         790 ($str = unpack("B$L",$packed)) =~ s/^0+//; # suppress leading zeros
375 121 100       466 $str =~ s/(.)/$to->[$1]/g if $bname eq 'user';
376             }
377             elsif ($bp == 4) { # hex / base 16
378 122         210 $L *= 8;
379 122         680 ($str = unpack("H$L",$packed)) =~ s/^0+//; # suppress leading zeros
380 122 100       399 $str =~ s/(.)/$to->[CORE::hex($1)]/g if $bname eq 'user';
381             }
382             else { # the rest
383 243         287 my $map;
384 243 100       605 if ($bname eq 'user') { # special map request
    100          
385 49 50       127 unless (exists $bc->{tmap}) {
386 49         113 $bc->{tmap} = usrmap($to,$xlt->[$bp]); # cache the map for speed
387             }
388 49         90 $map = $bc->{tmap};
389             }
390             elsif ($bp == 3) { # octal variant?
391 65         120 $map = $xlt->[$bp];
392             } else {
393 129         305 $map = $xlt->[0]->{$bname}; # standard map
394             }
395 243         317 $L *= 32;
396 243         1469 (my $bits = unpack("B$L",$packed)) =~ s/^0+//; # suppress leading zeros
397             #print "bp = $bp, BITS=\n$bits\n";
398 243         408 my $len = length($bits);
399 243         345 my $m = $len % $bp; # pad to even multiple base power
400             #my $z = $m;
401 243 100       558 if ($m) {
402 67         90 $m = $bp - $m;
403 67         169 $bits = ('0' x $m) . $bits;
404 67         118 $len += $m;
405             }
406             #print "len = $len, m_init = $z, m = $m, BITS PADDED\n$bits\n";
407 243         345 $str = '';
408 243         676 for (my $i = 0; $i < $len; $i += $bp) {
409 1829         5093 $str .= $map->{substr($bits,$i,$bp)};
410             #print "MAPPED i=$i, str=$str\n";
411             }
412             }
413 486         1771 $str;
414             }
415              
416             1;
417              
418             __END__