【问题标题】:Imprecise results of logarithm and power functions in PythonPython中对数和幂函数的不精确结果
【发布时间】:2016-09-18 17:59:39
【问题描述】:

我正在尝试完成以下练习: https://www.codewars.com/kata/whats-a-perfect-power-anyway/train/python

我尝试了多种变体,但是当涉及到大数字时我的代码会崩溃(我尝试了多种变体以及涉及对数和幂函数的解决方案):

锻炼:
你的任务是检查给定整数是否是完美幂。如果它是完美幂,则返回一对 m 和 k,其中 m^k = n 作为证明。否则返回 Nothing、Nil、null、None 或您的语言等效项。

注意:对于完美的力量,可能有几对。例如 81 = 3^4 = 9^2,所以 (3,4) 和 (9,2) 是有效解。但是,测试会处理这一点,因此如果一个数字是完美幂,则返回任何证明它的对。

练习使用 Python 3.4.3

我的代码:

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

问题: 对于更大的数字,我怎么可能一直得到错误的答案?我读到在 Python 3 中,所有整数都被视为 Python 2 中的“长”,即它们可以非常大并且仍然可以准确表示。因此,由于 i 和 int(a) 都是整数,不应该正确评估 pow(i, int(a)) == n 吗?我真的很困惑。

【问题讨论】:

  • 你能提供更大数字的错误答案的例子吗?
  • int(a)int,但 a 不是而且已经不准确。无论如何,证明问题。仅仅“我的代码崩溃”“得到不正确的答案”并没有多大帮助。
  • 分解是多余的,二进制搜索似乎可以工作:cstheory.stackexchange.com/q/2077
  • 你应该使用an IDE,除非你已经使用了一个。然后在调试模式下运行您的代码。例如,如果您使用 PyCharm,请关注 these instructions
  • 扔掉两个if 语句,并用if pow(i, round(a)) == n: 替换它们。在 Python 3(但不是 Python 2)中,1-argument round() 返回一个整数,因此幂是精确的。照原样,您的 round(a, 1) 返回一个浮点数,根本没有理由想象代码会起作用。例如,如果由于浮点变幻莫测,数学(无限精确)对数为 3,但计算为 2.999999999999,int() 将是 2 但round(that, 1) 将是 3.0。 round(that) 将返回正确的 3。

标签: python


【解决方案1】:

(编辑说明:还添加了整数 nth root 波纹管)

你在对数的正确轨道上,但你做错了数学,你也跳过了你不应该的数字,只测试所有偶数或所有奇数,而不考虑一个数字可以是偶数权力,反之亦然

检查一下

>>> math.log(170**3,3)
14.02441559235585
>>> 

甚至没有接近,正确的方法在这里描述Nth root

即:

设x为计算第N个根的数,n表示根,r为结果,那么我们得到

rn = x

从两边取任意基数,求解r

logb(rn) = logb(x)

n * logb( r ) = logb( x )

logb( r ) = logb( x ) / n

blogb( r ) = blogb( x ) / n

r = blogb( x ) / n

例如,我们以 10 为基数登录

>>> pow(10, math.log10(170**3)/3 )
169.9999999999999
>>> 

这更接近,只需四舍五入就能得到答案

>>> round(169.9999999999999)
170
>>> 

所以函数应该是这样的

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

范围的上限是为了避免测试肯定会失败的数字

测试

>>> isPP(170**3)
(170, 3)
>>> isPP(6434856)
(186, 3)
>>> isPP(9**2)
(9, 2)
>>> isPP(23**8)
(279841, 2)
>>> isPP(279841)
(529, 2)
>>> isPP(529)
(23, 2)
>>> 

编辑

或者正如 Tin Peters 指出的那样,您可以使用 pow(x,1./n) 作为数字的第 n 根也表示为 x1/n

例如

>>> pow(170**3, 1./3)
169.99999999999994
>>> round(_)
170
>>> 

但请记住,对于非常大的数字,例如

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

虽然对数方法会成功

>>> pow(10, math.log10(8191**107)/107)
8190.999999999999
>>> 

原因是 8191107 太简单了,它有 419 位,大于可表示的最大浮点数,但用 log 减少它会产生更合理的数字

编辑 2

现在,如果您想处理大得离谱的数字,或者只是不想完全使用浮点运算而只使用整数运算,那么最好的做法是使用method of Newton,这很有帮助linkTin Peters 提供,用于立方根的特殊情况,在维基百科文章旁边向我们展示一般的方法

def inthroot(A,n):
    if A<0:
        if n%2 == 0:
            raise ValueError
        return - inthroot(-A,n)
    if A==0:
        return 0
    n1 = n-1
    if A.bit_length() < 1024: # float(n) safe from overflow
        xk = int( round( pow(A,1/n) ) )
        xk = ( n1*xk + A//pow(xk,n1) )//n  # Ensure xk >= floor(nthroot(A)).
    else:
        xk = 1 << -(-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 <= sig:
            return xk
        xk = ( n1*xk + sig )//n

查看Mark Dickinson的解释,了解立方根情况下算法的工作原理,与此基本相同

现在让我们将其与另一个进行比较

>>> def nthroot(x,n):
        return pow(10, math.log10(x)/n )

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

那么函数就是现在

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

测试

>>> n = 2**(2**12) + 1  # a ridiculously big number
>>> r,p = isPPv2(n**23)
>>> p
23
>>> r == n
True
>>> isPPv2(170**3)
(170, 3)
>>> isPPv2(8191**107)
(8191, 107)
>>> isPPv2(6434856)
(186, 3)
>>>

现在让我们检查 isPP 与 isPPv2

>>> x = (1 << 53) + 1
>>> x
9007199254740993
>>> isPP(x**2)
>>> isPPv2(x**2)
(9007199254740993, 2)
>>>     

显然,避免浮点是最好的选择

【讨论】:

  • 嘿,感谢您提供的有用答案,它有效,您对对数的观点非常好!我使用了第 2 步,因为我很确定如果 n = p^k 是偶数,那么 p 也必须是偶数,因为如果你不断将奇数相乘,你将只会得到奇数。
  • 如果 n=p^k,是的,如果 n 是偶数,p 必须是偶数,但这并不能告诉您关于 k 的任何信息,而这正是您要搜索的。因此,当 n 为一个或另一个时跳过所有奇数/偶数忽略当 k 是相反时的情况,如 170^3 = 4913000 所示,这是一个偶数,并且通过跳过所有奇数显然你永远不会得到 k 的正确答案
  • 只是注意到pow( 10, math.log10(x)/n ) 更简单(更准确)表示为x**(1./n)pow(x, 1./n)(同样的事情在封面下)。特别是在极端情况下,库pow() 比使用不同的log 精确得多,并且以本机浮点精度逐个取幂;但是对于非常大的整数,浮点数可能甚至无法保持对根的近似值——在这种情况下,您需要一个全整数根函数。
  • @JohnnyQ 我还添加了一个替代版本
  • 好!请注意,全整数版本只是使我们免于溢出问题 - 如果根具有超过 53 位(浮点数只有 53 位精度),它也是必要的。例如,说x = (1 &lt;&lt; 53) + 1。然后整数版本应该正确地确定x**2x 的平方,但没有浮点版本可能可以,因为x 不能完全表示为浮点数。尽管x**2 只有 107 位宽,并且在大约 8.1e31 处几乎没有溢出。干得好:-)
猜你喜欢
  • 2020-04-04
  • 2020-01-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-06-16
相关资源
最近更新 更多