Imprecise results of logarithm and power functions in Python
<p>I am trying to complete the following exercise:
<a href="https://www.codewars.com/kata/whats-a-perfect-power-anyway/train/python" rel="nofollow">https://www.codewars.com/kata/whats-a-perfect-power-anyway/train/python</a></p>

<p>I tried multiple variations, but my code breaks down when big numbers are involved (I tried multiple variations with solutions involving log and power functions):</p>

<p><strong>Exercise:</strong><br>
Your task is to check wheter a given integer is a perfect power. If it is a perfect power, return a pair m and k with m^k = n as a proof. Otherwise return Nothing, Nil, null, None or your language's equivalent.</p>

<p>Note: For a perfect power, there might be several pairs. For example 81 = 3^4 = 9^2, so (3,4) and (9,2) are valid solutions. However, the tests take care of this, so if a number is a perfect power, return any pair that proves it.</p>

<p>The exercise uses Python 3.4.3</p>

<p><strong>My code:</strong></p>

<pre><code>import math
def isPP(n):
    for i in range(2 +n%2,n,2):
            a = math.log(n,i)
            if int(a) == round(a, 1):
                if pow(i, int(a)) == n:
                    return [i, int(a)]
    return None
</code></pre>

<p><strong>Question:</strong>
How is it possible that I keep getting incorrect answers for bigger numbers? I read that in Python 3, all ints are treated as "long" from Python 2, i.e. they can be very large and still represented accurately. Thus, since i and int(a) are both ints, shouldn't the pow(i, int(a)) == n be assessed correctly? I'm actually baffled.</p>

<p>(edit note: also added integer nth root bellow)</p>

<p>you are in the right track with logarithm but you are doing the math wrong, also you are skipping number you should not and only testing all the even number or all the odd number without considering that a number can be even with a odd power or vice-versa</p>

<p>check this</p>

<pre><code>&gt;&gt;&gt; math.log(170**3,3)
14.02441559235585
&gt;&gt;&gt; 
</code></pre>

<p>not even close, the correct method is described here <a href="https://en.wikipedia.org/wiki/Nth_root#Logarithmic_computation" rel="nofollow">Nth root</a></p>

<p>which is: </p>

<p>let x be the number to calculate the Nth root, n said root and r the result, then we get</p>

<p>r<sup>n</sup> = x</p>

<p>take the log in any base from both sides, and solve for r</p>

<p>log<sub>b</sub>( r<sup>n</sup> ) = log<sub>b</sub>( x )</p>

<p>n * log<sub>b</sub>( r ) = log<sub>b</sub>( x )</p>

<p>log<sub>b</sub>( r ) = log<sub>b</sub>( x ) / n</p>

<p>b<sup>log<sub>b</sub>( r ) </sup> = b<sup>log<sub>b</sub>( x ) / n </sup></p>

<p>r = b<sup>log<sub>b</sub>( x ) / n </sup></p>

<p>so for instance with log in base 10 we get</p>

<pre><code>&gt;&gt;&gt; pow(10, math.log10(170**3)/3 )
169.9999999999999
&gt;&gt;&gt; 
</code></pre>

<p>that is much more closer, and with just rounding it we get the answer</p>

<pre><code>&gt;&gt;&gt; round(169.9999999999999)
170
&gt;&gt;&gt; 
</code></pre>

<p>therefore the function should be something like this</p>

<pre><code>import math

def isPP(x):
    for n in range(2, 1+round(math.log2(x)) ):
        root   = pow( 10, math.log10(x)/n )
        result = round(root)
        if result**n == x:
            return result,n
</code></pre>

<p>the upper limit in range is to avoid testing numbers that will certainly fail</p>

<p>test </p>

<pre><code>&gt;&gt;&gt; isPP(170**3)
(170, 3)
&gt;&gt;&gt; isPP(6434856)
(186, 3)
&gt;&gt;&gt; isPP(9**2)
(9, 2)
&gt;&gt;&gt; isPP(23**8)
(279841, 2)
&gt;&gt;&gt; isPP(279841)
(529, 2)
&gt;&gt;&gt; isPP(529)
(23, 2)
&gt;&gt;&gt; 
</code></pre>

<p><strong>EDIT</strong></p>

<p>or as <strong>Tin Peters</strong> point out you can use <code>pow(x,1./n)</code> as the nth root of a number is also expressed as x<sup>1/n</sup></p>

<p>for example</p>

<pre><code>&gt;&gt;&gt; pow(170**3, 1./3)
169.99999999999994
&gt;&gt;&gt; round(_)
170
&gt;&gt;&gt; 
</code></pre>

<p>but keep in mind that that will fail for extremely large numbers like for example </p>

<pre><code>&gt;&gt;&gt; pow(8191**107,1./107)
Traceback (most recent call last):
  File "&lt;pyshell#90&gt;", line 1, in &lt;module&gt;
    pow(8191**107,1./107)
OverflowError: int too large to convert to float
&gt;&gt;&gt; 
</code></pre>

<p>while the logarithmic approach will success</p>

<pre><code>&gt;&gt;&gt; pow(10, math.log10(8191**107)/107)
8190.999999999999
&gt;&gt;&gt; 
</code></pre>

<p>the reason is that 8191<sup>107</sup> is simple too big, it have 419 digits which is greater that the maximum float representable, but reducing it with a log produce a more reasonable number</p>

<p><strong>EDIT 2</strong> </p>

<p>now if you want to work with numbers ridiculously big, or just plain don't want to use floating point arithmetic altogether and use only integer arithmetic, then the best course of action is to use the <a href="https://en.wikipedia.org/wiki/Nth_root_algorithm" rel="nofollow">method of Newton</a>, that the helpful <a href="http://stackoverflow.com/questions/35254566/wrong-answer-in-spoj-cubert/35276426#35276426">link</a> provided by <strong>Tin Peters</strong> for the particular case for cube root, show us the way to do it in general alongside the wikipedia article </p>

<pre><code>def inthroot(A,n):
    if A&lt;0:
        if n%2 == 0:
            raise ValueError
        return - inthroot(-A,n)
    if A==0:
        return 0
    n1 = n-1
    if A.bit_length() &lt; 1024: # float(n) safe from overflow
        xk = int( round( pow(A,1/n) ) )
        xk = ( n1*xk + A//pow(xk,n1) )//n  # Ensure xk &gt;= floor(nthroot(A)).
    else:
        xk = 1 &lt;&lt; -(-A.bit_length()//n)  # power of 2 closer but greater than the nth root of A
    while True:
        sig = A // pow(xk,n1)
        if xk &lt;= sig:
            return xk
        xk = ( n1*xk + sig )//n
</code></pre>

<p>check the explanation by <strong><a href="http://stackoverflow.com/questions/35254566/wrong-answer-in-spoj-cubert/35276426#35276426">Mark Dickinson</a></strong> to understand the working of the algorithm for the case of cube root, which is basically the same for this </p>

<p>now lets compare this with the other one</p>

<pre><code>&gt;&gt;&gt; def nthroot(x,n):
        return pow(10, math.log10(x)/n )

&gt;&gt;&gt; n = 2**(2**12) + 1  # a ridiculously big number
&gt;&gt;&gt; r = nthroot(n**2,2)
Traceback (most recent call last):
  File "&lt;pyshell#48&gt;", line 1, in &lt;module&gt;
    nthroot(n**2,2)
  File "&lt;pyshell#47&gt;", line 2, in nthroot
    return pow(10, math.log10(x)/n )
OverflowError: (34, 'Result too large')
&gt;&gt;&gt; r = inthroot(n**2,2)
&gt;&gt;&gt; r == n
True
&gt;&gt;&gt; 
</code></pre>

<p>then the function is now</p>

<pre><code>import math

def isPPv2(x):
    for n in range(2,1+round(math.log2(x))):
        root = inthroot(x,n)
        if root**n == x:
            return root,n
</code></pre>

<p>test</p>

<pre><code>&gt;&gt;&gt; n = 2**(2**12) + 1  # a ridiculously big number
&gt;&gt;&gt; r,p = isPPv2(n**23)
&gt;&gt;&gt; p
23
&gt;&gt;&gt; r == n
True
&gt;&gt;&gt; isPPv2(170**3)
(170, 3)
&gt;&gt;&gt; isPPv2(8191**107)
(8191, 107)
&gt;&gt;&gt; isPPv2(6434856)
(186, 3)
&gt;&gt;&gt;
</code></pre>

<p>now lets check isPP vs isPPv2</p>

<pre><code>&gt;&gt;&gt; x = (1 &lt;&lt; 53) + 1
&gt;&gt;&gt; x
9007199254740993
&gt;&gt;&gt; isPP(x**2)
&gt;&gt;&gt; isPPv2(x**2)
(9007199254740993, 2)
&gt;&gt;&gt;     
</code></pre>

<p>clearly, avoiding floating point is the best choice  </p>

python