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   95 use vars qw($VERSION);
  20         36  
  20         771  
4 20     20   95 use strict;
  20         34  
  20         45915  
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 766 my $ap = $_[0]; # perl appears to optimize these variables into registers
138 609         733 my $sr = $_[1]; # when they are set in this manner -- much faster!!
139 609         714 my $msk = $_[2];
140 609         741 my $sl = $_[3];
141 609         876 my $al = $#$ap -1;
142 609         768 my $i = 1;
143 609         1330 foreach (0..$al) {
144 975         1184 $ap->[$_] >>= $sr;
145             # $ap->[$_] |= ($ap->[$i] & $msk) << $sl;
146 975         1329 $ap->[$_] |= ($ap->[$i] << $sl) & $msk;
147 975         1269 $i++;
148             }
149 609         1506 $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 1498 my($ap,$n) = @_;
157 609         1415 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 139 my($ss,$d32p) = @_;
165 66         245 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 115 my($ss,$d32p) = @_;
179 54         92 my $bn = $dna{substr($ss,0,2)}; # 2 digits as a time => base 16
180 54         69 $bn <<= 4;
181 54         83 $bn += $dna{substr($ss,2,2)};
182 54         73 $bn <<= 4;
183 54         93 $bn += $dna{substr($ss,4,2)};
184 54         65 $bn <<= 4;
185 54         72 $bn += $dna{substr($ss,6,2)};
186 54         87 $bn <<= 4;
187 54         87 $bn += $dna{substr($ss,8,2)};
188 54         68 $bn <<= 4;
189 54         88 $bn += $dna{substr($ss,10,2)};
190 54         66 $bn <<= 4;
191 54         83 $bn += $dna{substr($ss,12,2)};
192 54         59 $bn <<= 4;
193 54         84 $bn += $dna{substr($ss,14,2)};
194 54         117 unshift @$d32p, $bn;
195             }
196              
197             sub bx3 { # base 8, 3 bits wide x10 = 30 bits - 07777777777
198 57     57 0 120 my($ss,$d32p) = @_;
199 57         111 unshift @$d32p, CORE::oct($ss) << 2;
200 57         139 shiftright($d32p,2);
201             }
202              
203             sub bx4 { # base 16, 4 bits wide x8 = 32 bits - 0xffffffff
204 58     58 0 114 my($ss,$d32p) = @_;
205 58         128 unshift @$d32p, CORE::hex($ss);
206             }
207              
208             sub bx5 { # base 32, 5 bits wide x6 = 30 bits - 555555
209 58     58 0 131 my($ss,$d32p,$hsh) = @_;
210 58         107 my $bn = $hsh->{substr($ss,0,1)};
211 58         67 $bn <<= 5;
212 58         96 $bn += $hsh->{substr($ss,1,1)};
213 58         68 $bn <<= 5;
214 58         91 $bn += $hsh->{substr($ss,2,1)};
215 58         67 $bn <<= 5;
216 58         111 $bn += $hsh->{substr($ss,3,1)};
217 58         71 $bn <<= 5;
218 58         95 $bn += $hsh->{substr($ss,4,1)};
219 58         70 $bn <<= 5;
220 58         118 unshift @$d32p, ($bn += $hsh->{substr($ss,5,1)}) << 2;
221 58         115 shiftright($d32p,2);
222             }
223              
224             sub bx6 { # base 64, 6 bits wide x5 = 30 bits - 66666
225 419     419 0 979 my($ss,$d32p,$hsh) = @_;
226 419         782 my $bn = $hsh->{substr($ss,0,1)};
227 419         496 $bn <<= 6;
228 419         683 $bn += $hsh->{substr($ss,1,1)};
229 419         460 $bn <<= 6;
230 419         586 $bn += $hsh->{substr($ss,2,1)};
231 419         524 $bn <<= 6;
232 419         649 $bn += $hsh->{substr($ss,3,1)};
233 419         454 $bn <<= 6;
234 419         899 unshift @$d32p, ($bn += $hsh->{substr($ss,4,1)}) << 2;
235 419         824 shiftright($d32p,2);
236             }
237              
238             sub bx7 { # base 128, 7 bits wide x4 = 28 bits - 7777
239 66     66 0 149 my($ss,$d32p,$hsh) = @_;
240 66         127 my $bn = $hsh->{substr($ss,0,1)};
241 66         76 $bn <<= 7;
242 66         113 $bn += $hsh->{substr($ss,1,1)};
243 66         79 $bn <<= 7;
244 66         112 $bn += $hsh->{substr($ss,2,1)};
245 66         79 $bn <<= 7;
246 66         142 unshift @$d32p, ($bn += $hsh->{substr($ss,3,1)}) << 4;
247 66         128 shiftright($d32p,4);
248             }
249              
250             sub bx8 { # base 256, 8 bits wide x4 = 32 bits - 8888
251 58     58 0 121 my($ss,$d32p,$hsh) = @_;
252 58         100 my $bn = $hsh->{substr($ss,0,1)};
253 58         66 $bn *= 256;
254 58         95 $bn += $hsh->{substr($ss,1,1)};
255 58         65 $bn *= 256;
256 58         95 $bn += $hsh->{substr($ss,2,1)};
257 58         67 $bn *= 256;
258 58         132 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 2970 my $bc = shift;
278 467         689 my($ary,$hsh,$base,$str) = @{$bc}{qw(from fhsh fbase nstr)};
  467         1200  
279 467         1260 my $bp = int(log($base)/log(2) +0.5);
280 467         584 my $len = length($str);
281 467 50       924 return ($bp,[0]) unless $len; # no value in zero length string
282              
283 467         704 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         692 my $basnam = ref $ary;
287 467         742 my $padchar = $ary->[0];
288 467 100       1692 if ($base == 16) { # should be hex
    100          
    100          
    100          
289 14 100       57 if ($basnam !~ /HEX$/i) {
290 2 50       15 $bc->{fHEX} = $bc->HEX() unless exists $bc->{fHEX};
291 2         4 my @h = @{$bc->{fHEX}};
  2         22  
292 2         132 $str =~ s/(.)/$h[$hsh->{$1}]/g; # translate string to HEX
293 2         7 $padchar = 0;
294             }
295             }
296             elsif ($base == 8) {
297 13 100       59 if ($basnam !~ /OCT$/i) {
298 2 50       15 $bc->{foct} = $bc->ocT() unless exists $bc->{foct};
299 2         4 my @o = @{$bc->{foct}};
  2         10  
300 2         159 $str =~ s/(.)/$o[$hsh->{$1}]/g;
301 2         6 $padchar = '0';
302             }
303             }
304             elsif ($base == 4) { # will map to hex
305 13 100       56 if ($basnam !~ /dna$/i) {
306 2 50       16 $bc->{fDNA} = $bc->DNA() unless exists $bc->{fDNA};
307 2         4 my @d = @{$bc->{fDNA}};
  2         14  
308 2         276 $str =~ s/(.)/$d[$hsh->{$1}]/g;
309 2         6 $padchar = 'A';
310             }
311             }
312             elsif ($base == 2) { # will map to binary
313 15 100       108 if ($basnam !~ /bin$/) {
314 1 50       7 $bc->{fbin} = $bc->bin() unless exists $bc->{fbin};
315 1         2 my @b = @{$bc->{fbin}};
  1         4  
316 1         90 $str =~ s/(.)/$b[$hsh->{$1}]/g;
317 1         2 $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         709 my $dpr = int (32 / $bp);
327 467         640 my $pad = $dpr - ($len % $dpr);
328 467 100       919 $pad = 0 if $pad == $dpr;
329 467 100       923 if ($pad) {
330 457         1132 $str = ($padchar x $pad) . $str; # pad string with zero value digit
331             }
332              
333             # number of iterations % digits/register
334 467         736 $len += $pad;
335 467         519 my $i = 0;
336 467         591 my @d32;
337 467         2534 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         2399 $useFROMbaseShortcuts[$bp]->(substr($str,$i,$dpr),\@d32,$hsh);
344 836         2578 $i += $dpr;
345             }
346 467   100     1516 while($#d32 && ! $d32[$#d32]) { # waste leading zeros
347 18         79 pop @d32;
348             }
349 467         1734 $bc->{b32str} = \@d32;
350             }
351              
352             # map non-standard user base to bitstream lookup
353             #
354             sub usrmap {
355 49     49 0 72 my($to,$map) = @_;
356 49         58 my %map;
357 49         215 while (my($key,$val) = each %$map) {
358 6168         20982 $map{$key} = $to->[$val];
359             }
360 49         140 \%map;
361             }
362              
363             sub useTObaseShortcuts {
364 486     486 1 5374 my $bc = shift;
365 486         645 my($base,$b32p,$to) = @{$bc}{qw( tbase b32str to )};
  486         1202  
366 486         1185 my $bp = int(log($base)/log(2) +0.5); # base power
367 486         1477 my $L = @$b32p;
368 486         849 my $packed = pack("N$L", reverse @{$b32p});
  486         1304  
369 486         3020 ref($to) =~ /([^:]+)$/; # extract to base name
370 486         900 my $bname = $1;
371 486         554 my $str;
372 486 100       1142 if ($bp == 1) { # binary
    100          
373 121         148 $L *= 32;
374 121         687 ($str = unpack("B$L",$packed)) =~ s/^0+//; # suppress leading zeros
375 121 100       465 $str =~ s/(.)/$to->[$1]/g if $bname eq 'user';
376             }
377             elsif ($bp == 4) { # hex / base 16
378 122         164 $L *= 8;
379 122         618 ($str = unpack("H$L",$packed)) =~ s/^0+//; # suppress leading zeros
380 122 100       420 $str =~ s/(.)/$to->[CORE::hex($1)]/g if $bname eq 'user';
381             }
382             else { # the rest
383 243         275 my $map;
384 243 100       591 if ($bname eq 'user') { # special map request
    100          
385 49 50       126 unless (exists $bc->{tmap}) {
386 49         106 $bc->{tmap} = usrmap($to,$xlt->[$bp]); # cache the map for speed
387             }
388 49         86 $map = $bc->{tmap};
389             }
390             elsif ($bp == 3) { # octal variant?
391 65         117 $map = $xlt->[$bp];
392             } else {
393 129         296 $map = $xlt->[0]->{$bname}; # standard map
394             }
395 243         335 $L *= 32;
396 243         1384 (my $bits = unpack("B$L",$packed)) =~ s/^0+//; # suppress leading zeros
397             #print "bp = $bp, BITS=\n$bits\n";
398 243         399 my $len = length($bits);
399 243         328 my $m = $len % $bp; # pad to even multiple base power
400             #my $z = $m;
401 243 100       533 if ($m) {
402 67         88 $m = $bp - $m;
403 67         176 $bits = ('0' x $m) . $bits;
404 67         120 $len += $m;
405             }
406             #print "len = $len, m_init = $z, m = $m, BITS PADDED\n$bits\n";
407 243         323 $str = '';
408 243         685 for (my $i = 0; $i < $len; $i += $bp) {
409 1829         4726 $str .= $map->{substr($bits,$i,$bp)};
410             #print "MAPPED i=$i, str=$str\n";
411             }
412             }
413 486         1671 $str;
414             }
415              
416             1;
417              
418             __END__