Created
June 8, 2012 05:14
-
-
Save ggilder/2893724 to your computer and use it in GitHub Desktop.
Fizzbuzz bitwise math
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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