Skip to content

Instantly share code, notes, and snippets.

@ggilder
Created June 8, 2012 05:14
Show Gist options
  • Save ggilder/2893724 to your computer and use it in GitHub Desktop.
Save ggilder/2893724 to your computer and use it in GitHub Desktop.
Fizzbuzz bitwise math
messages = [nil, "Fizz", "Buzz", "FizzBuzz"]
acc = 810092048
(1..100).each do |i|
c = acc & 3
puts messages[c] || i
acc = acc >> 2 | c << 28
end
# Explanation
#
# "810092048" appears to be a magic number, but if we display it as binary
# pairs it starts to make sense:
pairs = 810092048.to_s(2).scan(/.{2}/)
#=> ["11", "00", "00", "01", "00", "10", "01", "00", "00", "01", "10", "00", "01", "00", "00"]
# Now display each pair of binary digits as decimal:
pairs = pairs.map {|x| x.to_i(2)}
#=> [3, 0, 0, 1, 0, 2, 1, 0, 0, 1, 2, 0, 1, 0, 0]
# Now map those to messages:
pairs.map {|x| messages[x]}
#=> ["FizzBuzz", nil, nil, "Fizz", nil, "Buzz", "Fizz", nil, nil, "Fizz", "Buzz", nil, "Fizz", nil, nil]
# Notice that this is a reversed version of the first 15 messages (where nil
# stands for "pass through the integer") in the FizzBuzz sequence.
# Now for the binary math. `acc & 3` is the same as taking the last binary pair
# of the number, because 3 == "11" in binary. So `c = acc & 3` just grabs the
# last binary pair of acc. `c` must be 0, 1, 2, or 3, so we print messages[c]
# or pass through the integer if the message is nil (c == 0). Then we perform
# some shifting on acc. Notice that acc is 30 binary digits long:
810092048.to_s(2).length
#=> 30
# So if we shift acc to the right 2 digits, we "pop off" the last (rightmost)
# binary pair. That's `acc >> 2`. However, if we want to keep the sequence
# going past the first 15 integers, we really need to push the binary pair we
# just used back onto the end of the stack. So, we use a binary OR of the
# shifted acc with c (the last index we used) shifted to the right 28 binary
# digits. That way the sequence can go on forever!
# Let's see what that looks like for one of the iterations, to make it more
# clear. First let's make a helper function to print out these binary numbers.
def pp_binary(n)
# pad up to 30 binary digits for consistency
(("0" * 30) + n.to_s(2))[-30..-1].scan(/.{2}/).join(' ')
end
# Here's our magic number...
acc = 810092048
# But let's start by shifting off the first two pairs (4 digits), since we know
# those are both zero, so those iterations don't show us much.
acc = acc >> 4
#=> 50630753
# Let's see that in binary for reference:
pp_binary(acc)
#=> "00 00 11 00 00 01 00 10 01 00 00 01 10 00 01"
# Now let's see what our index is. We're on the iteration for 3, so we should
# have the index for "Fizz", which is 1.
c = acc & 3
pp_binary(c)
#=> "00 00 00 00 00 00 00 00 00 00 00 00 00 00 01"
# Looks good. Now let's see what acc looks like when we shift it over 2
# digits...
pp_binary(acc >> 2)
#=> "00 00 00 11 00 00 01 00 10 01 00 00 01 10 00"
# Notice that the last pair has been shifted off, and the leftmost pair is now
# zero.
# To get that last pair back, we take c and shift it back 28 digits:
pp_binary(c << 28)
#=> "01 00 00 00 00 00 00 00 00 00 00 00 00 00 00"
# Great! Now we just combine those with a binary OR.
acc = acc >> 2 | c << 28
# The binary OR sets each digit to 1 if it is 1 in either of the operands.
pp_binary(acc)
#=> "01 00 00 11 00 00 01 00 10 01 00 00 01 10 00"
# So now we've pushed our last pair back onto acc, and we're ready to run the
# process again! Whew!
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment