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