code review: is it acceptable to induce an infinite loop?

Question:

Stumbled across this coding challenge today and, the goal is to check if n is a power of two. Not all too happy with my solution although it does seem pass all tests.

For one, it doesn’t really seem to match the Pseudo code written before it and when trying to compare n to a number greater than those used in the tests ie: while n < 10: I am hit with an infinite loop.

Having trouble wrapping my head around this one!

I’ve heard of purposefully inducing an indefinite loop; is this some sort of abstract rendition of that concept?

def is_power_of_two(n):
  # Check if the number can be divided by two without a remainder
  while n % 2 != n:
    n = n / 2
  # If after dividing by two the number is 1, it's a power of two
  if n == 1:
    return True
  return False
print(is_power_of_two(0)) # Should be False
print(is_power_of_two(1)) # Should be True
print(is_power_of_two(8)) # Should be True
print(is_power_of_two(9)) # Should be False
Asked By: icecream

||

Answers:

The code seems to work well for many inputs, but it relies on floating point numbers, by applying a (non-integer) division by 2. If in the end this nicely ends up with n being 1, then indeed the original number was a power of 2.

However, because of floating point limitations, this will break far large enough inputs. You’ll get false positives.

For instance:

is_power_of_two((1<<80) + 1)

This should return False, as there are clearly two 1-bits in this number. But your function will return True, as if the input had been 1<<80.

To get a correct implementation, you should use integer division (//) and keep looping as long as the remainder is 0.

And to the topic of infinite loops: it could not loop infinitely for because n becomes smaller by the division, and eventually, it will get below the value of 2, when n % 2 == n.

Even when n is negative… in that case it will remain negative by the division, but again because of floating point limitations, the division will eventually give 0, and at that moment the loop condition is fulfilled.

The integer-based version, could loop forever if the input is 0, and would need protection for that case. We can use the opportunity to also capture negative inputs:

  if n <= 0:
    return False
  while n % 2 == 0:
    n = n // 2

Now the above test case will return the correct result.

Note that you can do this without explicit loop, using some bit wise operators:

  return n > 0 and (n & -n == n)

Or possibly more readable:

  return n > 0 and (1 << (n.bit_length() - 1) == n)
Answered By: trincot

This works for smaller numbers:

def is_power_of_two(n):
    while n > 1:
        n /= 2
    return n == 1

print(is_power_of_two(0)) # False
print(is_power_of_two(1)) # True
print(is_power_of_two(8)) # True
print(is_power_of_two(9)) # False

But for bigger numbers, its accuracy is compromised by Python’s floating point accuracy. So, you can use this:

def is_power_of_two(n):
    return bin(n).count('1') == 1

print(is_power_of_two(0)) # False
print(is_power_of_two(1)) # True
print(is_power_of_two(8)) # True
print(is_power_of_two(9)) # False

print(is_power_of_two(2**54))     # True
print(is_power_of_two(2**54 - 1)) # False
print(is_power_of_two(2**54 + 1)) # False

This works by using the fact that a number is a power of two if in binary it has no other 1s after the leading digit (e.g. 2 = 10, 4
= 100, 8 = 1000, etc.)

Answered By: The Thonnu

This algorithm can actually be solved without a loop at all.

If you choose to use bit shifting, the algorithm can look like:

def is_power_two(n):
    if n < 1:
        return False
    return n == (1 << n.bit_length() - 1)

Give a number n, you can use a bit shift of 1 << number of bits - 1 along with a equality check to n. If the number is a power of two, zero (True) is returned, otherwise a non-zero value (False) is returned.


Example:

The number 8 occupies four bits: 0b1000. A bit left shifted three (1 << 3) with an equality check to 8 (0b1000 == 8) returns True. However, the number 10 also occupies four bits: 0b1010. Yet, a bit left shifted three (0b1000), an equality check with 8 (0b1010 == 8) returns False.


Testing:

for i in range(65):
    print(i, is_power_two(i))

0 False
1 True  # 2**0
2 True
3 False
4 True
5 False
6 False
7 False
8 True
9 False
...
62 False
63 False
64 True
Answered By: S3DEV
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.