3

For example, 243 is a perfect power of 3 because 243=3^5.

I've previously been using (math.log(a) / math.log(b)).is_integer(), which I thought worked fine, but then I tried it with the example above and it actually returns 4.999999999999999 due to floating point arithmetic. So it's only reliable for very small numbers, less than around 100 I've found.

I suppose I could use a loop to do repeated multiplication... i.e. set i to 3, then 9, then 27, then 81, then 243, which equals the target, so we know it's a perfect power. If it reaches a point where it's bigger than 243 then we know it's not a perfect power. But I'm running this check within a loop as it is, so this seems like it'd be very inefficient.

So is there any other way of reliably checking if a number is a perfect power of another?

clb
  • 655
  • 8
  • 20
  • 3
    See http://cstheory.stackexchange.com/questions/2077/how-to-check-if-a-number-is-a-perfect-power-in-polynomial-time – Sven Marnach Sep 01 '16 at 22:13
  • 1
    Regarding Y in your XY problem: don't test floats with equality, test for *closeness* (as in checking that `math.log(a) / math.log(b)` is *close* to its integer part) – Andras Deak Sep 01 '16 at 22:14
  • Note that the link I posted covers the case that you want to detect whether a number is a non-trivial perfect power of _any_ other number, e.g. you don't know b. – Sven Marnach Sep 01 '16 at 22:28
  • Related: http://stackoverflow.com/q/15390807/674039 – wim Sep 01 '16 at 23:11
  • Do you already know that `b=3`, or are you trying to find that as well? – Teepeemm Sep 02 '16 at 00:59

7 Answers7

6

Try:

b ** int(round(math.log(a, b))) == a

That is, only use log() (note there is a 2-argument form!) to get a guess at an integer power, then verify whether "that works".

Note that math.log() returns a sensible result even for integer arguments much too large to represent as a float. Also note that integer ** in Python is exact, and uses an efficient algorithm internally (doing a number of multiplications proportional to the number of bits in the exponent).

This is straightforward and much more efficient (in general) than, say, repeated division.

But then I'm answering the question you asked ;-) If you had some other question in mind, some of the other answers may be more appropriate.

Tim Peters
  • 55,793
  • 10
  • 105
  • 118
  • 1
    It may be worth noting that while this solution isn't totally infallible for large values, depending as it does on floating-point accuracy, `a` would have to be spectacularly large for that to be a problem. E.g., with `b = 2`, `a = 2**(2**53 + 1)` and IEEE 754 floats, this can't possibly work, but you'd need approximately 1.2 PB of RAM and a machine with an address space bigger than 2**48 to be able to represent that value of `a` in the first place. – Mark Dickinson Sep 02 '16 at 16:00
  • Yes, there's no computer in existence with enough memory to hold integers large enough for this to plausibly fail. But I didn't mention that, because if the OP was surprised that `log()` may not be exact in all cases, they're not quite ready to appreciate an argument about the far-flung limits of theoretical possibilities ;-) – Tim Peters Sep 02 '16 at 16:08
  • Thank you! It's helpful to know that you can pass in the base as the second argument too. :) Is the int() necessary though when round() returns an integer? – clb Sep 02 '16 at 20:47
  • Necessary, yes! The `int()` is needed so that the exponentiation raises an integer to an integer power, which is computed exactly. Without the `int()`, we'd be raising an integer to a float power, which is only an approximation. By the way, the behavior of 1-argument `round()` differs between Python 2 and 3. `int()` isn't needed in Python 3, but doesn't hurt, and is necessary in Python 2. – Tim Peters Sep 02 '16 at 20:51
  • Ah okay, I've only ever used Python 3 so had no idea that round() actually returned a float in Python 2. – clb Sep 03 '16 at 13:33
  • Py3 can be confusing here; e.g., `round(12.3)` is the int `12`, but `round(12.3, -1)` is the float `10.0`. In Py2 `round()` always returns a float. In any case, if your question has been answered, you should accept the answer: http://meta.stackexchange.com/questions/5234/how-does-accepting-an-answer-work – Tim Peters Sep 03 '16 at 17:12
2

maybe something like:

def is_perfect_power(a, b):
  while a % b == 0:
    a = a / b
  if a == 1:
    return True
  return False

print is_perfect_power(8,2)
Abd Azrad
  • 271
  • 2
  • 10
1

If you will be working with large numbers, you may want to look at gmpy2. gmpy2 provides access to the GMP multiple-precision library. One of the functions provided is is_power(). It will return True if the argument is a perfect power of some base number. It won't provide the power or the base number but it will quickly filter out numbers that can not be perfect powers.

>>> import gmpy2
>>> [n for n in range(1,1000) if gmpy2.is_power(n)]
[1, 4, 8, 9, 16, 25, 27, 32, 36, 49, 64, 81, 100, 121, 125, 128, 144, 169, 196, 216, 225, 243, 256, 289, 324, 343, 361, 400, 441, 484, 512, 529, 576, 625, 676, 729, 784, 841, 900, 961]

Once you've identified possible powers, then you can use iroot_rem(x,n) to find the nth root and remainder of x. Then once you find a valid exponent, you can find the base number. Here is an example that searches through a range for all possible perfect powers.

>>> for x in range(1,1000):
...   if gmpy2.is_power(x):
...     for e in range(x.bit_length(), 1, -1):
...       temp_root, temp_rem = gmpy2.iroot_rem(x, e)
...       if not temp_rem:
...         print x, temp_root, e
... 
4 2 2
8 2 3
9 3 2
16 2 4
16 4 2
25 5 2
27 3 3
32 2 5
36 6 2
49 7 2
64 2 6
64 4 3
64 8 2
81 3 4
81 9 2
<< remainder clipped>>

Disclaimer: I maintain gmpy2.

casevh
  • 10,348
  • 1
  • 19
  • 30
1

If you want a speed up over repeated division for large numbers, you can make a list of all the exponents of the base where the exponent is a power of 2, and test those only.

For example, take 39. 9 is 101 in binary, which is 23 + 21 = 8 + 1.

So 39 = 38+1 = 323+21 = 323 x 321

You only need to trial divide twice, instead of 9 times.

def is_power(a, b):
  if b == 0 or b == 1:
    return a == b
  if a < b:
    return False
  c = []
  # make a list of the power of 2 exponents less than or equal to a:
  while b * b <= a:
    c.append(b) 
    b = b * b
  # test against each of them where b <= remaining a:
  while True: 
    if a % b != 0:
       return False
    a //= b
    while b > a:
      if len(c) == 0:
        return a == 1
      b = c.pop()
  return True

Works for positive integers, e.g:

print is_power(3**554756,3)
print is_power((3**554756)-1,3)

Output:

True
False
samgak
  • 22,290
  • 4
  • 50
  • 73
1

You can also use sympy's perfect_power:

>>> from sympy import perfect_power
>>> perfect_power(243)
(3, 5)
>>> perfect_power(244)
False

Please check the comment by @smichr below if you merely want to check whether the given number is a perfect power of some given base.

M.S. Dousti
  • 2,951
  • 5
  • 31
  • 45
  • You would use this if you don't know the base. In this case, it seems like the user knows, for example, that the base is 3. See also `sympy.core.power.integer_log(243, 3) -> (5, True)` which uses a method as suggested by @samgak. – smichr Jan 18 '18 at 04:50
  • @smichr: Fair enough. I modify my answer to point to your comment. – M.S. Dousti Jan 18 '18 at 21:32
0

If both the base and exponent are wildcards here and you are starting with only the "big number" result, then you're going to face a speed-memory trade-off. Since you seem to have implied that nested loops would be "inefficient", I assume that CPU cycle efficiency is important to you. In this case, may I suggest pre-computing a bunch of base-exponent pairs: a kind of rainbow table if you will. This will consume more memory, but much less CPU cycles. Then you can simply iterate over your table and see if you find a match. Or, if you're very concerned about speed, you can even sort the table and do a a binary search. You can even store the pre-computed table in a file, if you like.

Also, you didn't specify whether you just want a yes/no answer, or whether you are trying to find the corresponding base and exponent of the power. So I'll assume you want to find a base-exponent pair (some perfect powers will have multiple base-exponent solutions).

Try this on for size (I'm using Python 3):

#Setup the table as a list
min_base = 2 #Smallest possible base
max_base = 100 #Largest possible base
min_exp = 2 #Smallest possible exponent
max_exp = 10 #Largest possible exponent
powers = []

#Pre-compute the table - this takes time, but only needs to be done once
for i in range(min_base, max_base+1):
    for j in range(min_exp, max_exp+1):
        powers.append([i, j, i ** j])

#Now sort the table by the 3'rd element - again this is done only once
powers.sort(key=lambda x: x[2])


#Binary search the table to check if a number is a perfect power
def is_perfect_power(a, powers_arr):
    lower = 0
    upper = len(powers_arr)
    while lower < upper:
        x = lower + (upper - lower) // 2
        val = powers_arr[x][2] #[2] for the pre-computed power
        if a == val:
            return powers_arr[x][0:2]
        elif a > val:
            if lower == x:
                break
            lower = x
        elif a < val:
            upper = x
    return False #Number supplied is not a perfect power

#A simple demonstration
print(is_perfect_power(243, powers)) #Output is [3, 5]
print(is_perfect_power(105413504, powers)) #Output is [14, 7]
print(is_perfect_power(468209, powers)) #Output is False - not a perfect power

Someone more mathematically inclined may have a more efficient answer, but this should get you started. On my machine this runs pretty fast.

Ben Hershey
  • 310
  • 3
  • 7
  • I think the OP is looking for a known base. – Sven Marnach Sep 01 '16 at 23:09
  • Maybe, but given that he said he's already checking these in a loop I assumed he is looping over bases. I took a step back and looked at the bigger problem. Maybe won't help him, but hopefully will help someone. – Ben Hershey Sep 03 '16 at 15:46
0
def checkifanumberisapowerofanothernumber(x,y):
    if x==1:
        return y==1
    p=1
    while p<y:
        p=p*x
    return p==y

this repeatedly computes the power of x till the number becomes close to y. and then checks if the number becomes equal to y or not .

ravi tanwar
  • 456
  • 4
  • 13