File Coverage

blib/lib/Math/NumSeq/Fibbinary.pm
Criterion Covered Total %
statement 131 135 97.0
branch 28 32 87.5
condition 6 8 75.0
subroutine 25 25 100.0
pod 11 11 100.0
total 201 211 95.2


line stmt bran cond sub pod time code
1             # Copyright 2011, 2012, 2013, 2014 Kevin Ryde
2              
3             # This file is part of Math-NumSeq.
4             #
5             # Math-NumSeq is free software; you can redistribute it and/or modify
6             # it under the terms of the GNU General Public License as published by the
7             # Free Software Foundation; either version 3, or (at your option) any later
8             # version.
9             #
10             # Math-NumSeq is distributed in the hope that it will be useful, but
11             # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12             # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13             # for more details.
14             #
15             # You should have received a copy of the GNU General Public License along
16             # with Math-NumSeq. If not, see .
17              
18              
19             # ZOrderCurve, ImaginaryBase tree shape
20             # DragonCurve repeating runs
21             #
22             # cf fxtbook ch38 p756
23             #
24             # cf visualizing
25             # http://cs-people.bu.edu/ilir/zecko/
26              
27              
28             package Math::NumSeq::Fibbinary;
29 4     4   26162 use 5.004;
  4         18  
  4         169  
30 4     4   23 use strict;
  4         9  
  4         147  
31 4     4   24 use Carp;
  4         9  
  4         460  
32              
33 4     4   30 use vars '$VERSION', '@ISA';
  4         7  
  4         277  
34             $VERSION = 71;
35 4     4   595 use Math::NumSeq;
  4         10  
  4         324  
36             @ISA = ('Math::NumSeq');
37             *_is_infinite = \&Math::NumSeq::_is_infinite;
38             *_to_bigint = \&Math::NumSeq::_to_bigint;
39              
40 4     4   1955 use Math::NumSeq::Fibonacci;
  4         18  
  4         302  
41             *_bit_split_hightolow = \&Math::NumSeq::Fibonacci::_bit_split_hightolow;
42             *_blog2_estimate = \&Math::NumSeq::Fibonacci::_blog2_estimate;
43              
44             # uncomment this to run the ### lines
45             # use Smart::Comments;
46              
47              
48             # use constant name => Math::NumSeq::__('Fibbinary Numbers');
49 4     4   27 use constant description => Math::NumSeq::__('Fibbinary numbers 0,1,2,4,5,8,9,etc, integers without adjacent 1-bits.');
  4         7  
  4         23  
50 4     4   117 use constant default_i_start => 0;
  4         9  
  4         188  
51 4     4   22 use constant characteristic_increasing => 1;
  4         9  
  4         184  
52 4     4   21 use constant characteristic_integer => 1;
  4         6  
  4         400  
53              
54             sub values_min {
55 1     1 1 12 my ($self) = @_;
56 1         8 return $self->ith($self->i_start);
57             }
58              
59             #------------------------------------------------------------------------------
60             # cf A000119 - number of fibonacci sums forms
61             # A003622 - n with odd Zeckendorf, cf golden seq
62             # A037011 - baum-sweet cubic, might be 1 iff i is in the fibbinary seq
63             # A014417 - n in fibonacci base, the fibbinaries written out in binary
64             # A139764 - smallest Zeckendorf term
65             # A054204 - using only even Fibs
66             #
67 4     4   21 use constant oeis_anum => 'A003714'; # Fibbinary, OFFSET=0 start value=0
  4         7  
  4         4511  
68              
69             #------------------------------------------------------------------------------
70             # $self->{'i'}, $self->{'value'} are the next $i,$value to return.
71             # next() increments 'i' and steps 'value'.
72             # So the next value is calculated ahead of its actually being needed,
73             # but doing so
74              
75             sub rewind {
76 9     9 1 616 my ($self) = @_;
77 9         46 $self->{'i'} = $self->i_start;
78 9         30 $self->{'value'} = 0;
79             }
80             sub seek_to_i {
81 41     41 1 5559 my ($self, $i) = @_;
82 41 50       85 if ($i < 0) {
83 0         0 croak "Cannot seek to ",$i,", sequence begins at i=0";
84             }
85 41         62 $self->{'i'} = $i;
86 41         79 $self->{'value'} = $self->ith($i);
87             }
88             sub seek_to_value {
89 14     14 1 114 my ($self, $value) = @_;
90 14         30 $self->seek_to_i($self->value_to_i_ceil($value));
91             }
92              
93             sub next {
94 712     712 1 4079 my ($self) = @_;
95             ### Fibbinary next() ...
96              
97 712         1192 my $v = $self->{'value'};
98 712         1205 $self->{'value'} = _value_next($self,$v);
99 712         1718 return ($self->{'i'}++, $v);
100             }
101              
102             sub _value_next {
103 712     712   832 my ($self, $value) = @_;
104 712         976 my $filled = ($value >> 1) | $value;
105 712         924 my $mask = (($filled+1) ^ $filled) >> 1;
106              
107             ### value : sprintf('0b %6b',$value)
108             ### filled: sprintf('0b %6b',$filled)
109             ### mask : sprintf('0b %6b',$mask)
110             ### bit : sprintf('0b %6b',$mask+1)
111             ### newv : sprintf('0b %6b',($value | $mask))
112              
113 712         1517 return ($value | $mask) + 1;
114             }
115              
116             sub ith {
117 1524     1524 1 7547 my ($self, $i) = @_;
118             ### Fibbinary ith(): $i
119              
120 1524 50       4157 if (_is_infinite($i)) {
121 0         0 return $i;
122             }
123              
124             # f1+f0 > i
125             # f0 > i-f1
126             # check i-f1 as the stopping point, so that if i=UV_MAX then won't
127             # overflow a UV trying to get to f1>=i
128             #
129 1524         2275 my @fibs;
130             {
131 1524         2410 my $f0 = ($i * 0); # inherit bignum 0
  1524         2053  
132 1524         1834 my $f1 = $f0 + 1; # inherit bignum 1
133 1524         2470 @fibs = ($f0);
134 1524         3413 while ($f0 <= $i-$f1) {
135 16536         24829 ($f1,$f0) = ($f1+$f0,$f1);
136 16536         34381 push @fibs, $f1;
137             }
138             }
139             ### @fibs
140              
141 1524         1850 my $value = 0;
142 1524         3796 while (my $f = pop @fibs) {
143             ### at: "$f i=$i value=$value"
144 11776         19743 $value *= 2;
145 11776 100       31518 if ($i >= $f) {
146 5337         5877 $value += 1;
147 5337         5187 $i -= $f;
148             ### sub: "$f to i=$i value=$value"
149              
150             # never consecutive fibs, so pop without comparing to i
151 5337 100       10363 pop @fibs || last;
152 4760         12547 $value *= 2;
153             }
154             }
155 1524         4544 return $value;
156             }
157              
158             sub pred {
159 85     85 1 565 my ($self, $value) = @_;
160             ### Fibbinary pred(): $value
161              
162 85         124 my $int;
163 85 100 66     534 unless ($value >= 0
164             && $value == ($int = int($value))) {
165 32         135 return 0;
166             }
167              
168             # go to BigInt if NV floating point integer bigger than UV, since "&"
169             # operator will cast to a UV and lose bits
170 53 100 66     166 if ($int > ~0 && ! ref $int) {
171 1         28 $int = _to_bigint(sprintf('%.0f',$int));
172             ### use BigInt: $int
173             ### str: sprintf('%.0f',$int)
174             }
175              
176             ### and: ($int & ($int >> 1)).''
177 53         384 return ! ($int & ($int >> 1));
178             }
179              
180             #------------------------------------------------------------------------------
181              
182             sub value_to_i_floor {
183 219     219 1 973 my ($self, $value) = @_;
184             ### Fibbinary value_to_i_floor(): $value
185 219 100       452 if ($value < 0) { return 0; }
  5         12  
186 214         1949 my ($i) = _value_to_i_and_floor($value);
187 214         505 return $i;
188             }
189             sub value_to_i_ceil {
190 64     64 1 252 my ($self, $value) = @_;
191             ### Fibbinary value_to_i_ceil(): $value
192 64 50       136 if ($value < 0) { return 0; }
  0         0  
193 64         1648 my ($i,$floor) = _value_to_i_and_floor($value);
194 64         197 return $i + $floor;
195             }
196             sub value_to_i {
197 49     49 1 228 my ($self, $value) = @_;
198             ### Fibbinary value_to_i(): $value
199 49 100       125 if ($value < 0) { return undef; }
  4         15  
200 45         1837 my ($i,$floor) = _value_to_i_and_floor($value);
201 45 100       198 return ($floor ? undef : $i);
202             }
203              
204             # return ($i, $floor)
205             sub _value_to_i_and_floor {
206 323     323   368 my ($value) = @_;
207              
208 323 50       712 if (_is_infinite($value)) {
209 0         0 return ($value,
210             0); # reckon infinite as not rounded
211             }
212              
213 323         11856 my $floor;
214             {
215 323         347 my $int = int($value);
  323         424  
216 323 100       1214 $floor = ($value == $int ? 0 : 1);
217 323   100     1627 $value = $int
218             || return (0, $floor); # i=0 not handled below
219             }
220              
221 311         1771 my @bits = _bit_split_hightolow($value);
222 311         1380 my @fibs;
223             {
224 311         308 my $f0 = ($value * 0); # inherit bignum 0
  311         426  
225 311         8295 my $f1 = $f0 + 1; # inherit bignum 1
226 311         4631 foreach (@bits) {
227 1652         2345 ($f1,$f0) = ($f1+$f0,$f1);
228 1652         11435 push @fibs, $f1;
229             }
230             }
231             ### @fibs
232              
233 311         472 my $prev_bit = shift @bits; # high 1-bit
234 311         383 my $i = pop @fibs;
235              
236             ### initial i: $i
237              
238 311         438 foreach my $bit (@bits) { # high to low
239 965         1087 my $fib = pop @fibs;
240             ### $bit
241             ### $fib
242              
243 965 100       2781 if ($bit) {
244 355 100       644 if ($prev_bit) {
245             ### consecutive bits 11xxx, round down to 10xxx with xxx=1010 ...
246 146         292 while (@fibs) {
247 230         262 $i += pop @fibs;
248 230         455 pop @fibs;
249             }
250 146         369 return ($i,
251             1); # rounded down
252             }
253 209         288 $i += $fib;
254             ### add i to: $i
255             }
256 819         2374 $prev_bit = $bit;
257             }
258             ### exact i: "$i"
259 165         480 return ($i,
260             $floor); # not rounded, unless $value was fractional
261             }
262              
263             #------------------------------------------------------------------------------
264             # value_to_i_estimate()
265              
266 4     4   26 use constant 1.02 _PHI => (1 + sqrt(5)) / 2;
  4         87  
  4         1064  
267              
268             # (phi-beta) = phi+1/phi = 2phi-1
269             #
270             # value=2^k
271             # log(value) = k*log(2)
272             # k = log(value)/log(2)
273             # i = F(k)
274             # = phi^(k+1) / (phi-beta)
275             # = phi^k * C where C=phi/(phi-beta) ~= 0.72
276             # log(i/C) = k*log(phi)
277             # k = log(i/C)/log(phi)
278             #
279             # log(i/C)/log(phi) = log(value)/log(2)
280             # log(i/C) = log(value) * log(phi)/log(2)
281             # i/C = e^ (log(value) * log(phi)/log(2))
282             # i/C = (e^log(value)) ^ (log(phi)/log(2)))
283             # i = C * value ^ (log(phi)/log(2)))
284             #
285             # log(phi)/log(2) ~= 0.694
286             #
287              
288             sub value_to_i_estimate {
289 23     23 1 608 my ($self, $value) = @_;
290              
291 23 100       104 if ($value <= 0) {
292 8         19 return 0;
293             }
294              
295 15         144 $value = int($value);
296 15 100       60 if (my $blog2 = Math::NumSeq::Fibonacci::_blog2_estimate($value)) {
297 1         302 my $shift = int ((1 - log(_PHI)/log(2))
298             * Math::NumSeq::Fibonacci::_blog2_estimate($value));
299 1         226 return $value >> $shift;
300             }
301              
302 14         2393 return int ((((_PHI + 1/_PHI)/_PHI))
303             * $value ** (log(_PHI)/log(2)));
304             }
305              
306             # Can get close taking bits low to high and tweaking for consecutive 1s.
307             # But the high to low of the full value_to_i_floor() is only a little extra
308             # work.
309             #
310             # sub value_to_i_estimate {
311             # my ($self, $value) = @_;
312             # ### Fibbinary value_to_i_estimate(): $value
313             #
314             # if (_is_infinite($value)) {
315             # return $value;
316             # }
317             #
318             # my $f0 = my $f1 = ($value * 0)+1; # inherit bignum 1
319             # my $i = 0;
320             #
321             # my $prev_bit = 0;
322             # while ($value) {
323             # my $bit = $value % 2;
324             # if ($bit) {
325             # if ($prev_bit) {
326             # $i += $f0;
327             # } else {
328             # $i += $f1;
329             # }
330             # }
331             # $prev_bit = $bit;
332             # ($f1,$f0) = ($f1+$f0,$f1);
333             # $value = int($value/2);
334             # }
335             # return $i;
336             # }
337              
338             1;
339             __END__