5

In python, how can you check if a number n is an exact power of base b?

Note: it needs to be generalized to any base which is given as a parameter.

Here is what I got:

Assume n and base are integers > 0.

import math
def is_power(n,base):
    return math.log(n,base) == base**n
omega
  • 40,311
  • 81
  • 251
  • 474
  • 3
    Shouldn't you be checking if the math.log(n, base) is *any* integer? –  Mar 12 '13 at 03:12
  • 4
    Why would `math.log(n,base)` ever equal `base**n`? 128 is a power of 2, but `2**128` is definitely not `7`. – chrisaycock Mar 12 '13 at 03:12

6 Answers6

10

First, assuming you have a specific logarithm operator (many languages provide logarithms to base 10 or base e only), logab can be calculated as logxb / logxa (where x is obviously a base that your language provides).

Python goes one better since it can work out the logarithm for an arbitrary base without that tricky equality above.

So one way or another, you have a way to get logarithm to a specific base. From there, if the log of b in base a is an integer(note 1), then b is a power of a.

So I'd start with the following code, now with added edge-case detection:

# Don't even think about using this for negative powers :-)

def isPower (num, base):
    if base in {0, 1}:
        return num == base
    power = int (math.log (num, base) + 0.5)
    return base ** power == num

See for example the following complete program which shows this in action:

import math

def isPower (num, base):
    if base in {0, 1}:
        return num == base
    power = int (math.log (num, base) + 0.5)
    return base ** power == num

print isPower (127,2)       # false
print isPower (128,2)       # true
print isPower (129,2)       # false
print

print isPower (26,3)        # false
print isPower (27,3)        # true
print isPower (28,3)        # false
print isPower (3**10,3)     # true
print isPower (3**129,3)    # true
print

print isPower (5,5)         # true
print isPower (1,1)         # true
print isPower (10,1)        # false

If you're the sort that's worried about floating point operations, you can do it with repeated multiplications but you should test the performance of such a solution since it's likely to be substantially slower in software than it is in hardware. That won't matter greatly for things like isPower(128,2) but it may become a concern for isPower(verybignum,2).

For a non-floating point variant of the above code:

def isPower (num, base):
    if base in {0, 1}:
        return num == base
    testnum = base
    while testnum < num:
        testnum = testnum * base
    return testnum == num

But make sure it's tested against your largest number and smallest base to ensure you don't get any performance shocks.


(Note 1) Keep in mind here the possibility that floating point imprecision may mean it's not exactly an integer. You may well have to use a "close enough" comparison.

paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
  • 1
    1) `math.exp` only takes 1 argument. If you fix that, then 2) You'll get the wrong answer for `isPower(3**10, 3)`. – Robᵩ Mar 12 '13 at 03:21
  • Fails for boundary cases like `isPower(1, 1)` – wim Mar 12 '13 at 03:22
  • @Robᵩ, `exp` was the wrong function, it's been changed into `pow`. – paxdiablo Mar 12 '13 at 03:22
  • @wim, that's why I said "start with", I'll update the code to fix that edge case. – paxdiablo Mar 12 '13 at 03:24
  • and 3**10 isn't even a very big number (59049) – John La Rooy Mar 12 '13 at 03:25
  • That's solved by using round instead of int to take care of things like introduced FP inaccuracy. – paxdiablo Mar 12 '13 at 03:27
  • Now it fails for `isPower(3**129, 3)` – Robᵩ Mar 12 '13 at 03:27
  • -1 just avoid FP altogether, there is no need to introduce these complications. – wim Mar 12 '13 at 03:37
  • 1
    @wim, that's another possibility but it introduced other problems such as `isPower(big_honkin_number,2)` which is likely to be substantially slower. For things like `ispower(128,2)`, that probably won't matter but it may become a concern as the number becomes large - this is, of course, not something that's been specified in the question specifically,. – paxdiablo Mar 12 '13 at 03:49
5

A very simple solution could go like this:

def ispower(n, base): 

    if n == base:
        return True

    if base == 1:
        return False

    temp = base

    while (temp <= n):
        if temp == n:
            return True
        temp *= base

    return False

Result:

>>> ispower(32, 2)
True
>>> ispower(81, 3)
True
>>> ispower(625, 5)
True
>>> ispower(50, 5)
False
>>> ispower(32, 4)
False
>>> ispower(2,1)
False
>>> ispower(1,1)
True
Akavall
  • 82,592
  • 51
  • 207
  • 251
  • +1 very good. you might want to fix the infinite loop case with something like `ispower(2, 1)` though. – wim Mar 12 '13 at 03:34
  • You fixed the infinite loop, but you wrote a bug. `ispower(2, 1)` should be false. – wim Mar 12 '13 at 03:43
  • You probably want to look at the other edge cases as well. `ispower(1,1)` should be true, as should `ispower(1,0)`. Technically you may also want to handle negative powers but I've just stated my answer does not allow that :-) – paxdiablo Mar 12 '13 at 03:51
  • The question stated "Assume n and base are integers > 0". – wim Mar 12 '13 at 03:54
  • Oh, yeah, you're right. That means you only _need_ to cover `ispower(1,1)`. – paxdiablo Mar 12 '13 at 03:55
  • @paxdiablo, I fixed the `ispower(1,1)` case, but would `ispower(1,0)` be certainly true? This would be true if 0 ** 0 = 1, but that's not very clear. – Akavall Mar 12 '13 at 04:05
  • `0**0 == 1` in python, although perhaps it should have been left undefined. It's a moot point anyway, given the question. – wim Mar 12 '13 at 04:06
0
>>> def isPower(n, b):
...   return b**int(math.log(n, b)+.5)==n
... 
>>> isPower(128, 2)
True
>>> isPower(129, 2)
False
>>> isPower(3**10, 3)
True
>>> isPower(3**129, 3)
True
>>> isPower(10**500, 10)
True
>>> isPower(10**(10**6), 10)
True


EDIT: This code does fail for 1,1:
>>> isPower(1,1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in isPower
ZeroDivisionError: float division by zero

I'll leave it to the OP to decide if he wants to apply the trivial fix or rewrite his requirements.

Robᵩ
  • 163,533
  • 20
  • 239
  • 308
0

Here's a constant-time solution that handles every special case mentioned in the comments so far:

import math

def is_power(a, b, precision=14):
    if a == 1 or a == b:
        return True
    if b in (0, 1):
        return False
    return round(math.log(a, b), precision).is_integer()

Behavior for each special case:

a b is_power(a, b)
3^10 3 True ✔️
3^129 3 True ✔️
2 1 False ✔️
1 1 True ✔️
1 0 True ✔️
17^3 17 True ✔️
Quint
  • 1,146
  • 12
  • 12
0

I think I have a nice simple way of doing it (without calling a library) recursively:

def is_power_of(number, base):
  if number < base:
    return number == 1
  return is_power_of(number/base, base)
Agahowa
  • 1
  • 1
-1

>>>(math.log(int(num),int(base))).is_integer()

This will return a boolean value either true or false. This should work fine. Hope it helps

  • Nope, try `(math.log(4913, 17)).is_integer()` for example. – Eugene Yarmash Apr 29 '17 at 22:10
  • @EugeneYarmash, let me know what is the difference between the solution I gave and the correction you proposed. Can you specify scenario in which solution fails or the mistake in the solution. – Sachin Kumar Aug 02 '18 at 09:48
  • Well, your 'solution' apparently doesn't work for the example I posted 1+ year ago. log17(4913) == 3, which **is** an integer. – Eugene Yarmash Aug 02 '18 at 09:55