为什么在处理大数时math.sqrt()方法会出现错误?

4

math 模块为什么会返回错误的结果?

第一个测试

A = 12345678917
print 'A =',A
B = sqrt(A**2)
print 'B =',int(B)

结果

A = 12345678917
B = 12345678917

在这里,结果是正确的。

第二个测试

A = 123456758365483459347856
print 'A =',A
B = sqrt(A**2)
print 'B =',int(B)

结果

A = 123456758365483459347856
B = 123456758365483467538432

这里的结果是不正确的。

为什么会这样呢?


9
浮点数精度。 - MaxNoe
2
int(float(123456758365483459347856)) == 123456758365483459347856 -> False - MaxNoe
2
我自己没有尝试过,但也许decimal库会有用? - Tagc
@DenisLeonov 修复这个问题的一种可靠方法是使用Fraction来表示你的数字。请参考我在这里的回答(链接:https://stackoverflow.com/a/77284628/2599133),展示了如何以正确的舍入方式,对任意分数或整数进行任意精度的平方根计算。 - undefined
显示剩余3条评论
4个回答

7
因为 math.sqrt(..) 首先将数字转换为浮点数,而浮点数具有有限的尾数:它只能正确表示数字的一部分。因此float(A**2)不等于A**2。接下来计算math.sqrt,这也是大致正确的。

大多数使用浮点数的函数与其整数对应函数永远无法完全准确。浮点数运算几乎天生就是近似的。

如果计算A**2,则得到:

>>> 12345678917**2
152415787921658292889L

现在如果将其转换为float(..),将得到:
>>> float(12345678917**2)
1.5241578792165828e+20

但如果你现在问它们是否相等:
>>> float(12345678917**2) == 12345678917**2
False

在转换为浮点数时,部分信息已丢失。

您可以阅读有关浮点数工作原理以及为什么这些是近似值的更多信息,可以查看维基百科关于IEEE-754的文章,这是浮点数工作的正式定义。


6

math模块的文档指出:“它提供了C标准定义的数学函数的访问”。它还指出:“除非另有说明,否则所有返回值都是浮点数。”

这两者意味着平方根函数的参数是浮点值。在大多数系统中,这意味着适合8字节的浮点值,在C语言中称为“double”。您的代码将整数值转换为此类值,然后计算平方根并返回此类值。

但是,8字节浮点值最多只能存储15到17个有效数字。这就是您得到的结果。

如果您想要更好的平方根精度,请使用保证为整数参数提供完全精度的函数。只需进行网络搜索,您就会找到几个这样的函数。这些通常使用牛顿-拉弗森方法的变体进行迭代,并最终得出正确答案。请注意,这比math模块的sqrt函数慢得多。

这是我从互联网上修改的例程,目前无法引用来源。这个版本也适用于非整数参数,但只返回平方根的整数部分。

def isqrt(x):
    """Return the integer part of the square root of x, even for very
    large values."""
    if x < 0:
        raise ValueError('square root not defined for negative numbers')
    n = int(x)
    if n == 0:
        return 0
    a, b = divmod(n.bit_length(), 2)
    x = (1 << (a+b)) - 1
    while True:
        y = (x + n//x) // 2
        if y >= x:
            return x
        x = y

3
如果您想计算非常大的数的平方根并需要准确的结果,可以使用sympy
import sympy

num = sympy.Integer(123456758365483459347856)

print(int(num) == int(sympy.sqrt(num**2)))

0

浮点数在内存中的存储方式使得与它们的计算容易出现轻微的误差,这些误差在需要精确结果时可能是显著的。正如评论中提到的那样,decimal 库可以帮助您解决这个问题:

>>> A = Decimal(12345678917)
>>> A
Decimal('123456758365483459347856')
>>> B = A.sqrt()**2
>>> B
Decimal('123456758365483459347856.0000')
>>> A == B
True
>>> int(B)
123456758365483459347856

我使用的是版本3.6,它没有整数大小的硬编码限制。我不知道在2.7中将B强制转换为int是否会导致溢出,但无论如何,decimal都非常有用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接