0

I want to check if a number x is an exponential power of another number, y. I understand mathematics: use logarithms.

However, when I did this approach in Python3, using log() function, I got weird results. I wanted to test whether 243 is a power of 3, which it is, my code returned False. My code is as follows:

power = log(n, 3)
if (int(power)) == power:
    return True

I get 4.999999999999999 as the result in power. I read about the precision and other tactical details pertaining to floating points and logarithms in Python3 and I tried those solutions but none of them gave me the answer that I know is correct as far as basic maths is concerned.

I tried this:

from math import log
from decimal import Decimal, Context

class Power:
    def power_func(self, n: int) -> bool:
        if n == 0:
            return False
        ctx = Context(prec=20)
        power = log(n, Decimal(3))
        if (int(power)) == power:
            return True
        return False

I think I am missing some basics of Python here but not sure how to proceed further. I know other solutions to do the task but I wanted to achieve this using logarithms in Python3.

Aviral Srivastava
  • 4,058
  • 8
  • 29
  • 81
  • Check out [solution](https://www.geeksforgeeks.org/check-if-a-number-is-power-of-another-number/). Does it without using log function. – DarrylG Oct 17 '19 at 15:23
  • @DarrylG thanks! But as I have mentioned, I am willing to get the results using log() function. – Aviral Srivastava Oct 17 '19 at 15:34
  • 1
    Logarithms only work in general if you are doing real arithmetic, which you cannot do in Python. (`Decimal` is a better approximation of a real number than `float`, but it is still just an approximation.) Your best bet is to stick with repeated multiplication or division. – chepner Oct 17 '19 at 15:45
  • Thank you! I think I should accept your comment as the answer. Can you answer with your comment? – Aviral Srivastava Oct 17 '19 at 15:54
  • @AviralSrivastava--Actually, question previously asked with [answer](https://stackoverflow.com/questions/39281632/check-if-a-number-is-a-perfect-power-of-another-number) – DarrylG Oct 17 '19 at 15:57
  • @DarrylG hey! thanks! – Aviral Srivastava Oct 17 '19 at 18:10

4 Answers4

2

Don't use logarithms; they rely on real arithmetic to work correctly, which Python cannot do.

Instead, use repeated squaring to approach n from the base.

def is_power_of(n, b):
    y = b
    # Compute y = b**2, b**4, ..., b**2**i until y reaches or exceeds n
    # i = 1
    while y < n:
        y = y * y
        # i *= 2

    # If we overshoot, divide by b until we either
    # reach n or get a non-zero remainder
    while y > n:
        y, r = divmod(y, b)
        # i -= 1
        if r:
            return False
    else:
        return y == n

Note that if the function returns true, i will be the value such that b**i == n.

chepner
  • 497,756
  • 71
  • 530
  • 681
  • +1, this is a nice answer, but I think it can be made better by using bisection to zero in on the power. I wonder if a variation on Newton's method would have even faster convergence. You might need to save the powers of b as you are iterating to avoid recomputing them during bisection. – President James K. Polk Oct 17 '19 at 16:11
  • I think bisection would still be logarithmic. The constants might be nicer, but I think the implementation would be a little more complex. – chepner Oct 17 '19 at 17:14
2

Using log from Previous Post

from math import log

def is_power(a, b):
  " check if a is a power of b (i.e. b is base)"
  if b == 1 or a == 0: return False
  return return b ** int(round(log(a, b))) == a


print(is_power(243, 3))  # returns True

This should work for practical numbers

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 Dinkinson

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

Performance

Compared to powers methods such as is_power_of routine used in @chepner solution

for k in range(1, 300):
    num = 3**k
    assert is_power_of(num, 3) == is_power(num, 3)

There are no asserts so the two methods equal at least up till 3^300 =136891479058588375991326027382088315966463695625337436471480190078368997177499076593800206155688941388250484440597994042813512732765695774566001 (144 digits)

num = 3**300
%timeit is_power_of(num, 3)--uses powers
100000 loops, best of 3: 136 µs per loop

%timeit is_power(num, 3)--uses log
100000 loops, best of 3: 2.71 µs per loop

Thus, log method is over 50X faster for large numbers However, the two methods are comparable for smaller numbers

Cireo
  • 4,197
  • 1
  • 19
  • 24
DarrylG
  • 16,732
  • 2
  • 17
  • 23
1
return 3**math.ceil(power) == n or 3**math.floor(power) == n
chepner
  • 497,756
  • 71
  • 530
  • 681
Hamed
  • 315
  • 2
  • 13
  • Thank you for your answer but I need to know how can I handle this using log() function/property in Python3. – Aviral Srivastava Oct 17 '19 at 15:34
  • `power = log(n, 3)` , You are using logarithm. I just round the result up/down and recalculate the `n`. – Hamed Oct 17 '19 at 15:35
  • 2
    This will return `True` even if `log(n, 3)` is *not* an close-enough-integer; consider `3**math.ceil(4.5) == 243`. You would want to use `math.ceil(3**power)` and `math.floor(3**power)`, but even then I suspect that for some very large numbers this will fail. – chepner Oct 17 '19 at 15:37
  • You are right but if you think about it, if using `**` operator was my intention, I would have never used log() at all. Although, I am going to upvote because I got a new direction. Again, thank you for your effort. – Aviral Srivastava Oct 17 '19 at 15:40
  • @chepner you're right, but you won't get 4.5 when `power = log(n,3)`. – Hamed Oct 17 '19 at 15:41
0

It seems like the question insists to use and handle log(), but naively it's impossible to achieve perfect precision for computer like @DarryIG implied.

Without log(), a better approach is probably not using multiplication from base number since it will take a lot of repeat calculation. Instead, go with division and check remainder for quicker converge. As long as y%b is not integer, we can immediately terminate the computation.

def isPower(n,b):
  y = n
  while y % b == 0: y /= b
  return y == 1
Yunhai
  • 1,283
  • 1
  • 11
  • 26
  • It doesn't really bear any relationship to the randomness question. Also, the "/" operator always performs floating point division in python 3. You should use the "//" operator. – President James K. Polk Oct 17 '19 at 16:54
  • @JamesKPolk I agree with you that randomness thing is off-topic. actually, we have to use "/" operator for this one, we want floating point here for while condition to work. "//" will round the y to integer which is not our intend. Consider the case isPower(8.1, 2) – Yunhai Oct 17 '19 at 17:00
  • It isn't explicitly stated in the question, but it seems to be implied that all values must be integers. – President James K. Polk Oct 17 '19 at 17:05
  • I just realized that since y % b always execute before the division, there is not a situation where y/b will generate a decimal.....Then the consent of using "//" might have to do with how python handle (-0==0) or (0.0==0). But it seems like it doesn't matter for this case. Thx anyway, I almost forgot the floor division – Yunhai Oct 17 '19 at 17:17
  • For the general problem there is no limit on the size of the integers, but floating point types have limited precision. Thus, if y/b is greater than about 2\**53 the division result will not be exact. – President James K. Polk Oct 17 '19 at 17:32