File Coverage

blib/lib/Data/BitStream/Code/Fibonacci.pm
Criterion Covered Total %
statement 168 242 69.4
branch 68 132 51.5
condition 9 27 33.3
subroutine 10 16 62.5
pod 6 6 100.0
total 261 423 61.7


line stmt bran cond sub pod time code
1             package Data::BitStream::Code::Fibonacci;
2 28     28   22943 use strict;
  28         57  
  28         936  
3 28     28   153 use warnings;
  28         67  
  28         1419  
4             BEGIN {
5 28     28   67 $Data::BitStream::Code::Fibonacci::AUTHORITY = 'cpan:DANAJ';
6 28         6000 $Data::BitStream::Code::Fibonacci::VERSION = '0.08';
7             }
8              
9             our $CODEINFO = [ { package => __PACKAGE__,
10             name => 'Fibonacci',
11             universal => 1,
12             params => 0,
13             encodesub => sub {shift->put_fib(@_)},
14             decodesub => sub {shift->get_fib(@_)},
15             },
16             { package => __PACKAGE__,
17             name => 'FibC2',
18             universal => 1,
19             params => 0,
20             encodesub => sub {shift->put_fib_c2(@_)},
21             decodesub => sub {shift->get_fib_c2(@_)},
22             },
23             { package => __PACKAGE__,
24             name => 'FibGen',
25             universal => 1,
26             params => 1,
27             encodesub => sub {shift->put_fibgen(@_)},
28             decodesub => sub {shift->get_fibgen(@_)},
29             },
30             ];
31              
32 28     28   158 use Moo::Role;
  28         57  
  28         294  
33             requires qw(write put_string get_unary read);
34              
35             # Fraenkel/Klein 1996 C1 code (based on work by Apostolico/Fraenkel 1985)
36             #
37             # The C2 code is also supported, though not efficiently. C3 is not supported.
38             #
39             # While most codes we use are 'instantaneous' codes (also variously called
40             # prefix codes or prefix-free codes), the C2 code is not. It has to look at
41             # the first bit of the next code to determine when it has ended. This has the
42             # distinct disadvantage that is does not play well with other codes in the
43             # same stream. For example, if a C2 code is followed by a zero-based unary
44             # code then incorrect parsing will ensue.
45             #
46             # The first set of methods, get_fib() and put_fib(), are specifically written
47             # for m=2 -- codes using the traditional Fibonacci sequence. There are also
48             # generalized versions, which Klein et al. shows are useful for some
49             # applications. The generalized implementation is typically slower.
50              
51             # General order m >= 2 sequences. Generate enough to encode any integer
52             # from 0 to ~0. Note that the first 0,1 for all sequences are removed.
53             my @fibs_order;
54             my @fib_sums_order;
55             sub _calc_fibs_for_order_m {
56 30     30   96 my $m = shift;
57 30 50       112 die "Internal Fibonacci error" unless $m >= 2;
58 30         129 my @fibm = (0) x ($m-1);
59 30         90 push @fibm, 1, 1, 2;
60 30         74 my $v1 = $fibm[-1];
61 30         129 while ($v1 <= ~0) {
62 2456         3094 foreach my $i (2 .. $m) { $v1 += $fibm[-$i]; }
  4032         5866  
63 2456         6334 push(@fibm, $v1);
64             }
65 30         95 splice(@fibm, 0, $m); # remove the first elements
66 30         94 $fibs_order[$m] = \@fibm;
67              
68             # Calculate sums (with 0 at start)
69 30         96 my @fsums = (0);
70 30         74 foreach my $f (@fibm) { push @fsums, $fsums[-1] + $f; }
  2516         3154  
71 30         123 $fib_sums_order[$m] = \@fsums;
72             }
73              
74             # Since calculating the Fibonacci codes are relatively expensive, cache the
75             # size and code for small values.
76             my $fib_code_cache_size = 128;
77             my @fib_code_cache;
78              
79             sub put_fib {
80 4401     4401 1 25638 my $self = shift;
81 4401 50       22884 $self->error_stream_mode('write') unless $self->writing;
82              
83 4401 100       9494 _calc_fibs_for_order_m(2) unless defined $fibs_order[2];
84 4401         11624 my @fibs = @{$fibs_order[2]}; # arguably we should just use the reference
  4401         43802  
85              
86 4401         8686 foreach my $val (@_) {
87 7386 100 100     32018 $self->error_code('zeroval') unless defined $val and $val >= 0;
88              
89 7384 100 100     25179 if ( ($val < $fib_code_cache_size) && (defined $fib_code_cache[$val]) ) {
90 3629         4225 $self->write( @{$fib_code_cache[$val]} );
  3629         12555  
91 3629         9927 next;
92             }
93              
94 3755         5266 my $d = $val+1;
95 3755 100       8624 my $s = ($d < $fibs[20]) ? 0 : ($d < $fibs[40]) ? 21 : 41;
    100          
96 3755         51147 $s++ while ($d >= $fibs[$s]);
97              
98             # Generate 32-bit word directly if possible
99 3755 100       7385 if ($s <= 31) {
100 2060         3321 my $word = 1;
101 2060         7162 foreach my $f (reverse 0 .. $s) {
102 38419 100       70696 if ($d >= $fibs[$f]) {
103 10323         10656 $d -= $fibs[$f];
104 10323         17029 $word |= 1 << ($s-$f);
105             }
106             }
107 2060 100       5621 if ($val < $fib_code_cache_size) {
108 389         1478 $fib_code_cache[$val] = [ $s+1, $word ];
109             }
110 2060         6895 $self->write($s+1, $word);
111 2060         5014 next;
112             }
113              
114             # Generate the string code.
115 1695         2214 my $r = '11';
116 1695         2381 $d = $val - $fibs[--$s] + 1; # (this makes $val = ~0 encode correctly)
117 1695         4110 while ($s-- > 0) {
118 90987 100       139199 if ($d >= $fibs[$s]) {
119 18855         19673 $d -= $fibs[$s];
120 18855         34573 $r .= '1';
121             } else {
122 72132         129329 $r .= '0';
123             }
124             }
125 1695         7858 $self->put_string(scalar reverse $r);
126             }
127 4399         26724 1;
128             }
129              
130             # We can implement get_fib a lot of different ways.
131             #
132             # Simple:
133             #
134             # my $last = 0;
135             # while (1) {
136             # my $code = $self->read(1);
137             # die "Read off end of fib" unless defined $code;
138             # last if $code && $last;
139             # $val += $fibs[$b] if $code;
140             # $b++;
141             # $last = $code;
142             # }
143             #
144             # Exploit knowledge that we have lots of zeros and get_unary is fast. This
145             # is 2-10 times faster than reading single bits.
146             #
147             # while (1) {
148             # my $code = $self->get_unary();
149             # die "Read off end of fib" unless defined $code;
150             # last if ($code == 0) && ($b > 0);
151             # $b += $code;
152             # $val += $fibs[$b];
153             # $b++;
154             # }
155             #
156             # Use readahead(8) and look up the result in a precreated array of all the
157             # first 8 bit values mapped to the associated prefix code. While this is
158             # a neat idea, in practice it is slow in this framework.
159             #
160             # Use readahead to read 32-bit chunks at a time and parse them here.
161              
162             sub get_fib {
163 4358     4358 1 37811 my $self = shift;
164 4358 50       12077 $self->error_stream_mode('read') if $self->writing;
165              
166 4358 100       9770 _calc_fibs_for_order_m(2) unless defined $fibs_order[2];
167 4358         6194 my @fibs = @{$fibs_order[2]}; # arguably we should just use the reference
  4358         46350  
168              
169 4358         6462 my $count = shift;
170 4358 100       8629 if (!defined $count) { $count = 1; }
  4334 50       5805  
    0          
171 24         49 elsif ($count < 0) { $count = ~0; } # Get everything
172 0         0 elsif ($count == 0) { return; }
173              
174 4358         4920 my @vals;
175 4358         12805 $self->code_pos_start('Fibonacci');
176 4358         131390 while ($count-- > 0) {
177 7058         19861 $self->code_pos_set;
178 7058         226006 my $code = $self->get_unary;
179 7055 100       15722 last unless defined $code;
180             # Start with -1 here instead of subtracting it later. No overflow issues.
181 7006         8629 my $val = -1;
182 7006         7401 my $b = -1;
183 7006         9894 do {
184 34626         42123 $b += $code+1;
185 34626 50       67184 $self->error_code('overflow') unless defined $fibs[$b];
186 34626         50400 $val += $fibs[$b];
187 34626         97204 $code = $self->get_unary;
188 34626 100       117629 $self->error_off_stream unless defined $code;
189             } while ($code != 0);
190 7005         20068 push @vals, $val;
191             }
192 4354         12584 $self->code_pos_end;
193 4354 100       146116 wantarray ? @vals : $vals[-1];
194             }
195              
196              
197             ########## Generalized Fibonacci codes
198              
199             sub put_fibgen {
200 816     816 1 15138 my $self = shift;
201 816 50       7362 $self->error_stream_mode('write') unless $self->writing;
202 816         1343 my $m = shift;
203 816 50 33     5117 $self->error_code('param', 'm must be in range 2-16') unless $m >= 2 && $m <= 16;
204              
205 816 100       2493 _calc_fibs_for_order_m($m) unless defined $fibs_order[$m];
206 816         2523 my @fibm = @{$fibs_order[$m]};
  816         8453  
207 816         1196 my @fsums = @{$fib_sums_order[$m]};
  816         10547  
208 816         1649 my $term = ~(~0 << $m);
209              
210 816         5234 foreach my $val (@_) {
211 3492 50 33     15704 $self->error_code('zeroval') unless defined $val and $val >= 0;
212              
213 3492 100       9495 if ($val == 0) { $self->write($m, $term); next; }
  225 100       650  
  225         406  
214 39         151 elsif ($val == 1) { $self->write($m+1, $term); next; }
  39         75  
215              
216             # The way these codes are built are a different way of thinking about it
217             # than the simple m=2 case. See Salomon VLC p. 117.
218             # However, the end result is identical for m=2.
219              
220             # Determine how many bits we will encode
221 3228         3866 my $s = 1;
222 3228         51635 $s++ while ($val > $fsums[$s+1]);
223 3228         4706 my $d = $val - $fsums[$s] - 1;
224              
225             # Generate 32-bit word directly if possible
226 3228         11356 my $sm = $s + $m;
227 3228 100       6063 if ($sm <= 31) {
228 2481         2568 my $word = $term;
229 2481         6596 foreach my $f (reverse 0 .. $s-1) {
230 22436 100       42899 if ($d >= $fibm[$f]) {
231 8573         11530 $d -= $fibm[$f];
232 8573         12940 $word |= 1 << ($sm-$f);
233             }
234             }
235 2481         11063 $self->write($sm+1, $word);
236 2481         5915 next;
237             }
238              
239             # Encode the bits using string functions
240 747         1712 my $r = '1' x $m . '0';
241 747         1679 while ($s-- > 0) {
242 34701 100       64454 if ($d >= $fibm[$s]) {
243 13224         13522 $d -= $fibm[$s];
244 13224         23325 $r .= '1';
245             } else {
246 21477         36886 $r .= '0';
247             }
248             }
249              
250 747         3184 $self->put_string(scalar reverse $r);
251             }
252 816         6934 1;
253             }
254              
255             sub get_fibgen {
256 856     856 1 17739 my $self = shift;
257 856 50       2721 $self->error_stream_mode('read') if $self->writing;
258 856         1546 my $m = shift;
259 856 50 33     4442 $self->error_code('param', 'm must be in range 2-16') unless $m >= 2 && $m <= 16;
260              
261 856 100       2170 _calc_fibs_for_order_m($m) unless defined $fibs_order[$m];
262 856         1043 my @fibm = @{$fibs_order[$m]};
  856         8718  
263 856         1186 my @fsums = @{$fib_sums_order[$m]};
  856         8964  
264 856         10745 my $term = ~(~0 << $m);
265              
266 856         1171 my $count = shift;
267 856 100       1905 if (!defined $count) { $count = 1; }
  832 50       1065  
    0          
268 24         61 elsif ($count < 0) { $count = ~0; } # Get everything
269 0         0 elsif ($count == 0) { return; }
270              
271 856         1013 my @vals;
272 856         4222 $self->code_pos_start("FibGen($m)");
273 856         25257 while ($count-- > 0) {
274 3556         10178 $self->code_pos_set;
275              
276 3556         122604 my $code = $self->read($m);
277 3556 100       7593 last unless defined $code;
278 3532 100       7298 if ($code == $term) {
279 227         587 push @vals, 0;
280 227         580 next;
281             }
282              
283 3305         3827 my $fullcode = $code;
284 3305         3455 my $s = 0;
285 3305         8233 my $val = 1;
286 3305         3748 while (1) {
287              
288             # Count 1 bits on the left
289 27296         28801 my $count = 0;
290 27296         79692 $count++ while ($fullcode & (1 << $count));
291              
292             # Read as many more as we can while looking for 1 repeated $m times
293             # We will be reading 1-$m bits at a time.
294 27296         30963 my $codelen = $m-$count;
295 27296 100       50958 last if $codelen == 0;
296 24010         64180 $code = $self->read($codelen);
297 24010 50       56630 $self->error_off_stream unless defined $code;
298              
299             # Add latest read to full code in progress
300 24010         32882 $fullcode = ($fullcode << $codelen) | $code;
301              
302             # Process leftmost bits
303 24010         26359 my $left = $fullcode >> $m;
304 24010         3512902 foreach my $c (reverse 0 .. $codelen-1) {
305 61970 100       109664 $self->error_code('overflow') unless defined $fibm[$s];
306 61951 100       121632 $val += $fibm[$s] if ($left & (1 << $c));
307             #my $adder = ($left & (1 << $c)) ? $fibm[$s] : 0;
308             #print "s = $s val = $val (added $adder)\n";
309 61951         91578 $s++;
310             }
311 23991         37648 $fullcode &= $term; # Done with them
312             }
313             #print "s = $s val = ", $val+$fsums[$s-1], " (added $fsums[$s-1])\n";
314 3286         10880 push @vals, $val + $fsums[$s-1];
315             }
316 837         2495 $self->code_pos_end;
317 837 100       39658 wantarray ? @vals : $vals[-1];
318             }
319              
320              
321             # TODO:
322             # Consider Sayood's NF3 code, described on pages 67-70 of his
323             # Lossless Compression Handbook
324             #
325             # If F(N) ends with ....01, add the terminator 110. Final is ...01110
326             # If F(N) ends with ...011, add the terminator 11. Final is ...01111
327              
328              
329              
330             # String functions
331              
332             sub _encode_fib_c1 {
333 0     0     my $d = shift;
334 0 0 0       return unless $d >= 1 and $d <= ~0;
335 0 0         _calc_fibs_for_order_m(2) unless defined $fibs_order[2];
336 0           my @fibs = @{$fibs_order[2]};
  0            
337 0 0         my $s = ($d < $fibs[20]) ? 0 : ($d < $fibs[40]) ? 21 : 41;
    0          
338 0           $s++ while ($d >= $fibs[$s]);
339 0           my $r = '1';
340 0           while ($s-- > 0) {
341 0 0         if ($d >= $fibs[$s]) {
342 0           $d -= $fibs[$s];
343 0           $r .= "1";
344             } else {
345 0           $r .= "0";
346             }
347             }
348 0           scalar reverse $r;
349             }
350              
351             sub _decode_fib_c1 {
352 0     0     my $str = shift;
353 0 0         return unless $str =~ /^[01]*11$/;
354 0 0         _calc_fibs_for_order_m(2) unless defined $fibs_order[2];
355 0           my @fibs = @{$fibs_order[2]};
  0            
356 0           my $val = 0;
357 0           foreach my $b (0 .. length($str)-2) {
358 0 0         $val += $fibs[$b] if substr($str, $b, 1) eq '1';
359             }
360 0           $val;
361             }
362              
363             sub _encode_fib_c2 {
364 0     0     my $d = shift;
365 0 0 0       return unless $d >= 1 and $d <= ~0;
366 0 0         return '1' if $d == 1;
367 0           my $str = _encode_fib_c1($d-1);
368 0 0         return unless defined $str;
369 0           substr($str, -1, 1) = '';
370 0           substr($str, 0, 0) = '10';
371 0           $str;
372             }
373              
374             sub _decode_fib_c2 {
375 0     0     my $str = shift;
376 0 0         return 1 if $str eq '1';
377 0 0         return unless $str =~ /^10[01]*1$/;
378 0           $str =~ s/^10//;
379 0           my $val = _decode_fib_c1($str . '1');
380 0 0         return unless defined $val;
381 0           $val+1;
382             }
383              
384             sub put_fib_c2 {
385 0     0 1   my $self = shift;
386              
387 0           foreach my $val (@_) {
388 0 0 0       $self->error_code('zeroval') unless defined $val and $val >= 0;
389 0           my $c2_string = _encode_fib_c2($val+1);
390 0 0         $self->error_code('value', $val) unless defined $c2_string;
391 0           $self->put_string($c2_string);
392             }
393 0           1;
394             }
395             sub get_fib_c2 {
396 0     0 1   my $self = shift;
397 0 0         $self->error_stream_mode('read') if $self->writing;
398 0           my $count = shift;
399 0 0         if (!defined $count) { $count = 1; }
  0 0          
    0          
400 0           elsif ($count < 0) { $count = ~0; } # Get everything
401 0           elsif ($count == 0) { return; }
402              
403 0           my @vals;
404 0           $self->code_pos_start('Fibonacci C2');
405 0           while ($count-- > 0) {
406 0           $self->code_pos_set;
407 0           my $str = '';
408 0           if (0) {
409             my $look = $self->read(8, 'readahead');
410             last unless defined $look;
411             if (($look & 0xC0) == 0xC0) { $self->skip(1); return 0; }
412             if (($look & 0xF0) == 0xB0) { $self->skip(3); return 1; }
413             if (($look & 0xF8) == 0x98) { $self->skip(4); return 2; }
414             if (($look & 0xFC) == 0x8C) { $self->skip(5); return 3; }
415             if (($look & 0xFC) == 0xAC) { $self->skip(5); return 4; }
416             if (($look & 0xFE) == 0x86) { $self->skip(6); return 5; }
417             if (($look & 0xFE) == 0xA6) { $self->skip(6); return 6; }
418             if (($look & 0xFE) == 0x96) { $self->skip(6); return 7; }
419             }
420 0           my $b = $self->read(1);
421 0 0         last unless defined $b;
422 0           $str .= $b;
423 0           my $b2 = $self->read(1, 'readahead');
424 0   0       while ( (defined $b2) && ($b2 != 1) ) {
425 0           my $skip = $self->get_unary;
426 0 0         $self->error_off_stream unless defined $skip;
427 0           $str .= '0' x $skip . '1';
428 0           $b2 = $self->read(1, 'readahead');
429             }
430 0           my $val = _decode_fib_c2($str);
431 0 0         $self->error_code('string', "Not a Fibonacci C2 code") unless defined $val;
432 0           push @vals, $val-1;
433             }
434 0           $self->code_pos_end;
435 0 0         wantarray ? @vals : $vals[-1];
436             }
437 28     28   109268 no Moo::Role;
  28         79  
  28         476  
438             1;
439              
440             # ABSTRACT: A Role implementing Fibonacci codes
441              
442             =pod
443              
444             =head1 NAME
445              
446             Data::BitStream::Code::Fibonacci - A Role implementing Fibonacci codes
447              
448             =head1 VERSION
449              
450             version 0.08
451              
452             =head1 DESCRIPTION
453              
454             A role written for L that provides get and set methods for
455             the Fibonacci codes. The role applies to a stream object.
456              
457             =head1 METHODS
458              
459             =head2 Provided Object Methods
460              
461             =over 4
462              
463             =item B< put_fib($value) >
464              
465             =item B< put_fib(@values) >
466              
467             Insert one or more values as Fibonacci C1 codes. Returns 1.
468              
469             =item B< get_fib() >
470              
471             =item B< get_fib($count) >
472              
473             Decode one or more Fibonacci C1 codes from the stream. If count is omitted,
474             one value will be read. If count is negative, values will be read until
475             the end of the stream is reached. In scalar context it returns the last
476             code read; in array context it returns an array of all codes read.
477              
478             =item B< put_fibgen($m, @values) >
479              
480             Insert one or more values as generalized Fibonacci C1 codes with order C.
481             Returns 1.
482              
483             =item B< get_fibgen($m) >
484              
485             =item B< get_fib($m, $count) >
486              
487             Decode one or more generalized Fibonacci C1 codes with order C from the
488             stream. If count is omitted, one value will be read. If count is negative,
489             values will be read until the end of the stream is reached. In scalar context
490             it returns the last code read; in array context it returns an array of all
491             codes read.
492              
493             =item B< put_fib_c2(@values) >
494              
495             Insert one or more values as Fibonacci C2 codes. Returns 1.
496              
497             Note that the C2 codes are not prefix-free codes, so will not work well with
498             other codes. That is, these codes rely on the bit _after_ the code to be a 1
499             (or the end of the stream). Other codes may not meet this requirement.
500              
501             =item B< get_fib_c2() >
502              
503             =item B< get_fib_c2($count) >
504              
505             Decode one or more Fibonacci C2 codes from the stream.
506              
507             =back
508              
509             =head2 Required Methods
510              
511             =over 4
512              
513             =item B< read >
514              
515             =item B< write >
516              
517             =item B< get_unary >
518              
519             =item B< put_string >
520              
521             These methods are required for the role.
522              
523             =back
524              
525             =head1 SEE ALSO
526              
527             =over 4
528              
529             =item Alberto Apostolico and Aviezri S. Fraenkel, "Robust Transmission of Unbounded Strings Using Fibonacci Representations", Computer Science Technical Reports, Paper 464, Purdue University, 14 October 1985. L
530              
531             =item A.S. Fraenkel and S.T. Klein, "Robust Universal Complete Codes for Transmission and Compression", Discrete Applied Mathematics, Vol 64, pp 31-55, 1996. L
532              
533             These papers introduce and describe the order C=2> Fibonacci codes C1, C2, and C3. The C C1 codes are what most people call Fibonacci codes.
534              
535             =item L
536              
537             A description of the C C1 code.
538              
539             =item Shmuel T. Klein and Miri Kopel Ben-Nissan, "On the Usefulness of Fibonacci Compression Codes", The Computer Journal, Vol 53, pp 701-716, 2010. L
540              
541             More information on Fibonacci codes, including C2> codes.
542              
543             =back
544              
545             =head1 AUTHORS
546              
547             Dana Jacobsen
548              
549             =head1 COPYRIGHT
550              
551             Copyright 2011-2012 by Dana Jacobsen
552              
553             This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
554              
555             =cut