Math::N...ibbinary(3)

πŸ“¦
version 75 Β· 2 versions
, in fedora 43

πŸ’– Math::NumSeq::Fibbinary πŸ’–

okay bestie, so this is a page from the User Contributed Perl Documentation for the module Math::NumSeq::Fibbinary β€” and no, the name isn't actually spelled with dots in the middle, that's just the header being extra and truncating on us. the real name is right here, it's giving very much "no adjacent 1-bits" energy and we're gonna learn it together 🫢


✨ NAME ✨

Math::NumSeq::Fibbinary β€” without consecutive 1-bits. that's the whole vibe, that's the brand πŸ’…


🌸 SYNOPSIS 🌸

use Math::NumSeq::Fibbinary;
my $seq = Math::NumSeq::Fibbinary->new;
my ($i, $value) = $seq->next;

that's it. that's the whole thing. three lines and you're in your Fibbinary era πŸ¦‹


πŸŽ€ DESCRIPTION πŸŽ€

okay so this sequence is the Fibbinary numbers and they go a little something like:

0, 1, 2, 4, 5, 8, 9, 10, 16, 17, 18, 20, 21, 32, 33, 34, ...
starting i=0       (A003714)

and the whole point of them?? they have no adjacent 1-bits when you write them in binary. like literally none. zero. not one pair. iconic behaviour fr fr ✨

here's the receipts, bestie:

i     Fibbinary    Fibbinary
      (decimal)    (binary)
---    ---------    --------
 0         0             0
 1         1             1
 2         2            10
 3         4           100
 4         5           101
 5         8          1000
 6         9          1001
 7        10          1010
 8        16         10000
 9        17         10001

so for example at i=4 the Fibbinary is 5. and the next Fibbinary is NOT 6 or 7 because those two are out here with adjacent 1-bits (110 and 111, embarrassing for them honestly), so the next one that's actually allowed is 8 (100). slay πŸ’…

the two highest bits must be "10...", they cannot be "11...". so there's effectively a block of 2^k values (not all used, mind you) followed by a gap of 2^k values, etc. we love a structured queen πŸ‘‘

alsooo the least significant bit of each Fibbinary is the Fibonacci word sequence, per Math::NumSeq::FibonacciWord. cute crossover moment 😌

and ngl, all numbers without adjacent 1-bits can also be generated super simply by taking the binary expansion and changing each "1" to "01" β€” but that doesn't give them in ascending order the way the Fibbinary here does. so like, valid technique, wrong order, respect though 🌈

πŸ’œ Zeckendorf Base πŸ’œ

okay this part is the main character moment so pay attention bestie. the bits of the Fibbinary values encode Fibonacci numbers used to represent i in Zeckendorf style Fibonacci base. in the Zeckendorf base system, an integer i is a sum of Fibonacci numbers,

i = F[k1] + F[k2] + ... F[kn]         k1 > k2 > ... > kn

and each k gets chosen as the highest Fibonacci less than the remainder at that point. for example, reckoning the Fibonaccis as F[0]=1, F[2]=2, etc

19 = 13+5+1 = F[5] + F[3] + F[0]

then those k's get assembled as 1-bits in binary to encode the sum in an integer,

Fibbinary(19) = 2^5 + 2^3 + 2^0 = 41

the gaps between Fibonacci numbers are exactly why after subtracting F(k) from i, the next one cannot be F(k-1) β€” it must be F(k-2) or less. and THAT is the reason there's no adjacent 1-bits in the Fibbinary numbers. mystery solved, case closed, no cap πŸ”₯

the connection between no adjacent 1s and the Fibonacci sequence can be seen by considering values with high bit 2^k. the further bits in it can't have 2^(k-1) but only 2^(k-2), so effectively the number of new values isn't from the previous k-1 but the second previous k-2 β€” the same way as the Fibonacci sequence adds not the previous term (which would be double) but the one before instead. math is literally serving the same bit twice and I'm obsessed 🧚


⭐ FUNCTIONS ⭐

see "FUNCTIONS" in Math::NumSeq for behaviour common to all sequence classes. shared lore, iykyk πŸ’–

  • $seq = Math::NumSeq::Fibbinary->new () β€” create and return a new sequence object. brand new you, brand new sequence ✨

πŸ¦‹ Iterating πŸ¦‹

  • $seq->seek_to_i($i)
  • $seq->seek_to_value($value) β€” move the current i so that next() will return $i or $value on the next call. and heads up: if $value is not in the sequence then it moves so as to return the next higher value which is. no half measures, only real Fibbinary values allowed here πŸ’…

πŸ‘‘ Random Access πŸ‘‘

  • $value = $seq->ith($i) β€” return the $i'th Fibbinary number. give it an index, get the value, simple as that πŸŽ€
  • $bool = $seq->pred($value) β€” return true if $value is a Fibbinary number, which means that in binary it doesn't have any consecutive 1-bits. vibe check: passed or failed, no in between ⭐
  • $i = $seq->value_to_i($value)
  • $i = $seq->value_to_i_ceil($value)
  • $i = $seq->value_to_i_floor($value) β€” return the index i of $value. but if $value is not in the sequence then value_to_i() returns "undef" (the ghosting option 😭), or value_to_i_ceil() returns the i of the next higher value which is, or value_to_i_floor() the i of the next lower value. choose your fighter 🫢
  • $i = $seq->value_to_i_estimate($value) β€” return an estimate of the i corresponding to $value. not exact, just vibes-adjacent math πŸ’œ

🌈 FORMULAS 🌈

πŸ”₯ Next Value πŸ”₯

for a given Fibbinary number, the next Fibbinary is +1 if the lowest bit is 2^2=4 or more. but if the low bit is 2^1=2 or 2^0=1 then the run of low alternating ...101 or ...1010 must be cleared and the bit above set. for example 1001010 becomes 1010000. all cases can be handled by some bit twiddling:

# value=Fibbinary
filled = (value >> 1) | value
mask = ((filled+1) ^ filled) >> 1
next value = (value | mask) + 1

for example:

value  = 1001010
filled = 1101111
mask   =    1111
next   = 1010000

"filled" means trailing ...01010 has the zeros filled in to ...01111. then those low ones can be extracted with +1 and XOR (the usual trick for getting low ones, classic move 😌). +1 means the bit above the filled part is included so 11111, but a shift drops back to "mask" just 01111. OR-ing and incrementing then clears those low bits and sets the next higher bit to make ...10000. chef's kiss πŸ’…

this works for any Fibbinary input, both odd "...10101" and even "...1010" endings and also zeros "...0000". in the zeros case the result is just a +1 for "...0001", and that includes input value=0 giving next=1. inclusivity queen πŸ‘‘

⭐ Ith Value ⭐

the i'th Fibbinary number can be calculated as per "Zeckendorf Base" above. reckoning the Fibonacci numbers as F(0)=1, F(1)=2, F(2)=3, F(3)=5, etc,

find the biggest F(k) <= i
subtract i -= F(k)
Fibbinary result += 2^k
repeat until i=0

to find each F(k)<=i you can either just work downwards through the Fibonacci numbers, or β€” get this β€” the Fibonaccis grow as (phi^k)/sqrt(5) with phi=(sqrt(5)+1)/2 the golden ratio 🌟 so an F(k) could be found by a log base phi of i. or taking log2 of i (the bit length of i) might give 2 or 3 candidates for k. calculating log base phi is unlikely to be faster, but log 2 high bit should quickly go to a nearly-correct place in a table. basically: shortcut options exist, pick your fave πŸ’–

🧚 Predicate 🧚

testing for a Fibbinary value can be done by a shift and AND,

is_Fibbinary = ((value & (value >> 1)) == 0)

any adjacent 1-bits overlap in the shift+AND and come through as non-zero. caught in 4k, no escape πŸ•΅οΈ

now important tech tea β˜•: Perl "&" operator converts NV float to UV integer. if an NV value is an integer but bigger than a UV then bits will be lost to the "&". conversion to "Math::BigInt" or similar is necessary to preserve the full value. do not skip this part, bestie, you will lose bits and you will be sad 😭

floats which are integers but bigger than an UV might be of interest, or it might be thought any float means rounded-off and therefore inaccurate and not of interest. the current code has some experimental automatic BigInt conversion which works for floats and for BigFloat or BigRat integers too, but don't rely on this quite yet. (a BigInt input directly is fine of course, no stress there πŸ’…)

πŸ’œ Value to i Floor πŸ’œ

in a Fibbinary value each bit becomes a Fibonacci F[i] to add to make i, as per "Zeckendorf Base" above.

if a number is not a Fibbinary then the next lower Fibbinary can be had by finding the highest 11 pair and changing it and all the bits below to 101010... etc. for example 10011001 is not a Fibbinary and must change down to 10010101, ie. the 11001 reduces to 10101, that being the biggest Fibbinary no-adjacent-1s which is 10xxx. glow down, not glow up 🫢

bits 2^k from high to low
  if bit set
    if prev bit set too
    then treat remainder as 010101...
    else i += F[k]

if working downwards adding F[k] values then it's easy enough to notice an adjacent 11 pair. an alternative would be to find all 11 pairs by bit-twiddling per "Predicate" above and the highest 1-bit (if any) of those is the place to mangle. two paths, same destination, pick whichever speaks to you ✨

🌸 Value to i Estimate 🌸

in general i grows as a power of phi=1.618 and the values grow as a power of 2. so an estimate can be had from

value = 2^k
i = F[k+1]
  ~= phi^(k+1) / (phi + 1/phi)
  ~= C * phi^k
where C=phi/(phi + 1/phi)

log(i/C)/log(phi) ~= log(value)/log(2)

i_estimate = C * value^(log(phi)/log(2))

the power log(phi)/log(2)=0.694 reduces the value to give an i approximation. that power can also be approximated by shifting off the least significant 1-0.694=0.306 of the bits of the value. this is fast and may be enough accuracy for a bigint. fast and loose but make it math πŸ’…

highbitpos of value
i_estimate = value >> floor(highbitpos * 0.306)

🫢 SEE ALSO 🫢

  • Math::NumSeq
  • Math::NumSeq::Fibonacci
  • Math::NumSeq::FibonacciWord
  • Math::NumSeq::GolayRudinShapiro
  • Math::NumSeq::BaumSweet
  • Math::Fibonacci decompose()

the whole friend group, go say hi to all of them πŸŽ€


🌐 HOME PAGE 🌐

<http://user42.tuxfamily.org/math-numseq/index.html>


πŸ“œ LICENSE πŸ“œ

copyright 2011, 2012, 2013, 2014, 2015, 2016, 2019, 2020, 2021, 2022 Kevin Ryde

Math-NumSeq is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version.

Math-NumSeq is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. see the GNU General Public License for more details.

you should have received a copy of the GNU General Public License along with Math-NumSeq. if not, see <http://www.gnu.org/licenses/>.

ok bestie quick legal moment: that warranty disclaimer is doing a LOT of heavy lifting and it means exactly what it says. no promises, no guarantees, read the license, be free πŸ•ŠοΈ

and that's the whole page, you ate it, you understood it, you're a Fibbinary girlie now πŸ’…βœ¨ this was Math::NumSeq::Fibbinary, brought to you by perl v5.42.0 on 2025-07-25 β€” date stamped, version locked, slay, periodt πŸŽ€πŸ’–

πŸ“¦ where this page comes from