Based on this answer, I’m implementing a simple algorithm in Python 3.x to determine whether or not a integer n is a power of another integer base. However, the algorithm doesn’t return the correct result. The code in the linked answer is:
while (n % 3 == 0) {
n /= 3;
}
return n == 1;
(A comment indicates that checking n == 0 is also necessary, logically). This is my code:
def is_power(n: int, base: int) -> bool:
if n == 0:
return false
while (n % base == 0):
n /= base
return n == 1
I wrote simple testing code that tests a certain range of bases and exponents, but the results it returns are incorrect. Testing code:
for base in range(3, 10):
print("Base: {0}".format(base))
for exp in range(30, 40):
b = is_power(pow(base, exp), base)
if not(b):
print("{: <3}: {: <5}".format(exp, str(b)))
I tested this with a much larger range, but for the sake of output, I limited it in this example. This outputs:
Base: 3
35 : False
36 : False
37 : False
38 : False
39 : False
Base: 4
Base: 5
30 : False
31 : False
32 : False
33 : False
34 : False
35 : False
36 : False
37 : False
38 : False
39 : False
Base: 6
35 : False
36 : False
37 : False
38 : False
39 : False
Base: 7
30 : False
31 : False
32 : False
33 : False
34 : False
35 : False
36 : False
37 : False
38 : False
39 : False
Base: 8
Base: 9
30 : False
31 : False
32 : False
33 : False
34 : False
35 : False
36 : False
37 : False
38 : False
39 : False
which is clearly incorrect. I’ve tried debugging a small example, where n = pow(3, 35) and base = 3 yields these values of n in the loop:
50031545098999707
1.6677181699666568e+16
and the loop ends, because 50031545098999707 / 3 == 1.667718169966656 9 e+16 (note that the last digit is different). Is this the problem? Are Python’s calculations failing? If not, what is the problem with this algorithm?
The algorithm also fails even more if I use math.pow instead, but I’m not necessarily surprised by that, since checking one example demonstrates that pow and math.pow don’t always return the same value.
import math
pow(3, 35) == math.pow(3, 35) # 50031545098999707 != 5.0031545098999704e+16
Since you are using Python 3, you should use
In Python 3.x, the
/operation always perform “real division” and return a floating point number, but in the algorithm you’ve linked to, it expects the/to do integer division, which is done with the//(“floor division”) operator instead.And the test fails really because of the lack of precision in floating-point arithmetic, as you have expected. The algorithm is still correct when
/returns an infinite-precision real number. Take 3.034 as an example,The default floating-point format only supports 53 bits of precision, but the above number requires 54 bits to represent, so we have to round off the last bit (the 1), resulting in
which the modulus to 3 will return 2, which will break the loop and return false.
(I haven’t checked why it rounds down instead of up, but even if it rounds up the modulus is still not zero, so it will still return false.)