为什么 math.sqrt() 对于大数不正确?

Why is math.sqrt() incorrect for large numbers?

为什么 math 模块 return 结果错误?

第一次测试

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

此处结果不正确

为什么会这样?

因为math.sqrt(..) first casts the number to a floating point and floating points have a limited mantissa:只能正确表示部分数字。所以 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 的维基百科文章中阅读有关浮点数如何工作以及为什么它们是近似值的更多信息,这是关于浮点数如何工作的正式定义。

documentation for the math module 声明 "It provides access to the mathematical functions defined by the C standard." 它还声明 "Except when explicitly noted otherwise, all return values are floats."

它们一起意味着平方根函数的参数是一个浮点值。在大多数系统中,这意味着一个适合 8 个字节的浮点值,在 C 语言中称为 "double"。您的代码在计算平方根之前将您的整数值转换为这样的值,然后 returns 这样的值。

但是,8字节的浮点值可以存储at most 15 to 17 significant decimal digits。这就是您在结果中得到的。

如果您希望平方根的精度更高,请使用保证为整数参数提供完整精度的函数。只要做一个网络搜索,你就会找到几个。这些通常会采用 Newton-Raphson 方法的变体来迭代并最终以正确答案结束。请注意,这比数学模块的 sqrt 函数要慢得多。

这是我从网上修改的套路。我现在不能引用消息来源。此版本也适用于非整数参数,但仅适用于 returns 平方根的整数部分。

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

如果你想计算非常大的数字的平方根并且你需要精确的结果,你可以使用sympy:

import sympy

num = sympy.Integer(123456758365483459347856)

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

浮点数在内存中的存储方式使得使用它们进行的计算容易出现轻微错误,但在需要精确结果时可能会很严重。正如其中一条评论所述,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 都非常有用。