Ruby of course supports your run-of-the-mill binary math stuff. In case you’ve never come across this stuff before, here’s a quick intro.
NOTE: This is theory and doesn’t take account of realities like endianess, signedness and so forth. I’ll just assume LSB (‘least significant bit’ in terms of the full number) == bit 0, and will also consider Fixnum as 8-bit for the purposes of this.
NOTE ALSO: You probably won’t need to use this stuff in Ruby all that much, but it is right at the heart of programming so if you’re a ‘got to know’ type like me, you’ll want to read it. Otherwise, probably best to forget all about it because it could confuse matters.
Download this source
# Imagine we have some bitmaps:
# 1 2 4 8 16 32 64 128
# 1 0 0 1 0 1 0 0 = 41
# 1 0 0 0 0 0 0 1 = 129
# 0 0 0 0 0 1 0 0 = 32
# 1 1 1 1 1 1 1 1 = 255
#
# And we wonder about bit 5 (v=32). Well, we can easily find out
# using bitwise and (&), comparing the mask for the bit we need
# to check.
bmps = [41, 129, 8]
bmps.each do |it|
if it & 32 > 0
puts "#{it} - Bit 5 is set"
else
puts "#{it} - Bit 5 isn't set"
end
end
# It should be obvious why this works - 'and' returns a number with
# any bits *that were set in both original numbers* set.
# The following illustrates:
#
# 1 2 4 8 16 32 64 128
# 0 1 0 1 0 0 0 0 = 9
# 0 0 0 1 0 0 0 0 = 8
# AND 0 0 0 1 0 0 0 0 = 8
#
# So to figure out if bit 'n' is set, we just need to figure out
# the mask for that bit. Did you notice they're all powers of
# two? Well, that's no coincidence...
class Fixnum
def bit_set(n)
self & (2 ** n) > 0 # or, self & (2**n) == (2**n)
end
# handy for examples
def show_bits
print "#{self.to_s}:\t"
(0..7).each { |n| print "#{self.bit_set(n) ? 1 : 0}\t" }
print "\n"
end
end
bmps.each do |bmp|
bmp.inspect
bmp.show_bits
end
print "\n"
# Okay. So now we can check bitmaps we're passed in. But what about
# making up out own bit-sets? Lets' look at 'or', which as you might
# expect, returns a number with any bits *that were set in either
# original number* set:
#
# 1 2 4 8 16 32 64 128
# 0 1 0 1 0 0 0 0 = 10
# 0 0 0 1 0 0 1 0 = 72
# OR 0 1 0 1 0 0 1 0 = 74
#
# Notice the subtlety? 'or' actually means 'set in either or both'.
# We can use this - if we supply a bitmask and an original number,
# then we'll get back a number with all the original bits set,
# as well as the mask ones. Let's set bit 4 (v == 16) in 72.
#
72.show_bits
16.show_bits
puts "OR"
(72 | 16).show_bits # becomes 88
# Okay, so that's all good. But what about switching them off?
# For this we're best to go to a relative or 'or' - 'xor'.
# As the name utterly fails to suggest, this is 'Exclusive' or,
# i.e. one or the other, but not both.
puts "XOR"
(88 ^ 16).show_bits # becomes 72 again
# There's also a smooth effect here - XOR doesn't actually
# 'switch bits off', but instead it toggles them to their
# opposite state.
(72 ^ 16).show_bits # becomes 88 again
# So the obvious way to control bits is:
class Fixnum
def flip_bit(n)
self ^ (2**n)
end
def set_bit(n,t)
bit_set(n) == t ? self : flip_bit(n)
end
end
# We can now do flipping and setting willy-nilly.
print "\n\n=================================================================\n"
print "72.flip_bit 4 = "
72.flip_bit(4).show_bits
print "88.flip_bit 4 = "
88.flip_bit(4).show_bits
print "72.set_bit 4, true = "
72.set_bit(4,true).show_bits
print "72.set_bit 4, false= "
72.set_bit(4,false).show_bits
print "88.set_bit 4, true = "
88.set_bit(4,true).show_bits
print "88.set_bit 4, false= "
88.set_bit(4,false).show_bits
print "\n\n"
# For completeness, we should also take a look at bitwise negation, with
# the unary ~ operator. This is pretty self-evident I think.
#
# NOTE: The fact that Fixnum is signed slightly messes this up, but the
# effect on the bits is still the same - ignore the actual number, the
# demo is in the bit table ;)
72.show_bits
(~72).show_bits
print "\n\n"
# =========================================================================
# This is only the very basics - you're encouraged to go read up on this
# stuff, because understanding the concepts on which computers are built
# will help you be a better programmer. We now return to you'r regularly
# scheduled Ruby-specific wonderment.
# Just one more thing to say really, which is that Ruby supports shortcut
# assignment operators for bitwise operations:
a = 72
a |= 16
puts "OR: #{a}"
a = 88
a ^= 16
puts "XOR: #{a}"
a = 88
a &= 16
puts "AND: #{a}"
# Obviously, negation isn't supported in this context since it is a
# unary operator anyway. It's important not to confuse these with the
# 'improvement' operators, (see oper.rb) which are actually conditional
# assignment operators and do something very different.
Running this outputs:
41 - Bit 5 is set
129 - Bit 5 isn't set
8 - Bit 5 isn't set
41: 1 0 0 1 0 1 0 0
129: 1 0 0 0 0 0 0 1
8: 0 0 0 1 0 0 0 0
72: 0 0 0 1 0 0 1 0
16: 0 0 0 0 1 0 0 0
OR
88: 0 0 0 1 1 0 1 0
XOR
72: 0 0 0 1 0 0 1 0
88: 0 0 0 1 1 0 1 0
=================================================================
72.flip_bit 4 = 88: 0 0 0 1 1 0 1 0
88.flip_bit 4 = 72: 0 0 0 1 0 0 1 0
72.set_bit 4, true = 88: 0 0 0 1 1 0 1 0
72.set_bit 4, false= 72: 0 0 0 1 0 0 1 0
88.set_bit 4, true = 88: 0 0 0 1 1 0 1 0
88.set_bit 4, false= 72: 0 0 0 1 0 0 1 0
72: 0 0 0 1 0 0 1 0
-73: 1 1 1 0 1 1 0 1
OR: 88
XOR: 72
AND: 16

