Search code examples
rubymethodsfixnum

Create a method to find if a number is a power of 2?


I have this code to return true if num is a power of 2.

def is_power_of_two?(num)
  result = num.inject(0) {|n1, n2| n2 ** n1}
  if result == num
    true
  else
    false
  end
end

p is_power_of_two?(16)

I keep getting an error though. How could I fix and simplify this code?


Solution

  • Clearly, n is a non-negative integer.

    Code

    def po2?(n)
      n.to_s(2).count('1') == 1
    end
    

    Examples

    po2?  0     #=> false
    po2?  1     #=> true
    po2? 32     #=> true
    po2? 33     #=> false
    

    Explanation

    Fixnum#to_s provides the string representation of an integer (the receiver) for a given base. The method's argument, which defaults to 10, is the base. For example:

    16.to_s     #=> "16" 
    16.to_s(8)  #=> "20" 
    16.to_s(16) #=> "10"
    15.to_s(16) #=>  "f"
    

    It's base 2 we're interested in. For powers of 2:

     1.to_s(2)  #=>      "1" 
     2.to_s(2)  #=>     "10" 
     4.to_s(2)  #=>    "100" 
     8.to_s(2)  #=>   "1000"
    16.to_s(2)  #=>  "10000"
    

    For a few natural numbers that are are not powers of 2:

     3.to_s(2)  #=>    "11" 
     5.to_s(2)  #=>   "101" 
    11.to_s(2)  #=>  "1011" 
    

    We therefore wish to match binary strings that contain one 1.

    Another Way

    R = /
        \A      # match beginning of string ("anchor")
        10*     # match 1 followed by zero or more zeroes
        \z      # match end of string ("anchor")
        /x      # free-spacing regex definition mode
    
    def po2?(n)
      (n.to_s(2) =~ R) ? true : false
    end
    
    po2?(4)  #=> true
    po2?(5)  #=> false
    

    One more

    This uses Fixnum#bit_length and Fixnum#[]:

    def po2?(n)
      m = n.bit_length-1
      n[m] == 1 and m.times.all? { |i| n[i].zero? }
    end
    
    po2?  0     #=> false
    po2?  1     #=> true
    po2? 32     #=> true
    po2? 33     #=> false
    

    And a fourth, perhaps the best

    def po2?(n)
      return false if n <= 0
      while n.even?
        n /= 2
      end
      n == 1
    end
    
    po2?  0     #=> false
    po2?  1     #=> true
    po2? 32     #=> true
    po2? 33     #=> false