pythonpowsqrt

Where are the inaccuracies in math.sqrt() and math.pow() coming from for large numbers?


If you take a number, take its square root, drop the decimal, and then raise it to the second power, the result should always be less than or equal to the original number.

This seems to hold true in python until you try it on 99999999999999975425 for some reason.

import math

def check(n):
    assert math.pow(math.floor(math.sqrt(n)), 2) <= n

check(99999999999999975424)  # No exception.
check(99999999999999975425)  # Throws AssertionError.

It looks like math.pow(math.floor(math.sqrt(99999999999999975425)), 2) returns 1e+20.

I assume this has something to do with the way we store values in python... something related to floating point arithmetic, but I can't reason about specifically how that affects this case.


Solution

  • The problem is not really about sqrt or pow, the problem is you're using numbers larger than floating point can represent precisely. Standard IEEE 64 bit floating point arithmetic can't represent every integer value beyond 52 bits (plus one sign bit).

    Try just converting your inputs to float and back again:

    >>> int(float(99999999999999975424))
    99999999999999967232
    >>> int(float(99999999999999975425))
    99999999999999983616
    

    As you can see, the representable value skipped by 16384. The first step in math.sqrt is converting to float (C double), and at that moment, your value increased by enough to ruin the end result.

    Short version: float can't represent large integers precisely. Use decimal if you need greater precision. Or if you don't care about the fractional component, as of 3.8, you can use math.isqrt, which works entirely in integer space (so you never experience precision loss, only the round down loss you expect), giving you the guarantee you're looking for, that the result is "the greatest integer a such that a² ≤ n".