1

I want to avoid my code mistaking a near integer for an integer. For example, 58106601358565889 has as its square root 241,053,109.00000001659385359763188, but when I used the following boolean test, 58106601358565889 fooled me into thinking it was a perfect square:

a = 58106601358565889
b = math.sqrt(a)
print(b == int(b))

The precision isn't necessarily the problem, because if I re-check, I get the proper (False) conclusion:

print(a == b**2)

What would be a better way to test for a true versus a near integer? The math.sqrt is buried in another definition in my code, and I would like to avoid having to insert a check of a squared square root, if possible. I apologize if this is not a good question; I'm new to python.

Jeptha
  • 65
  • 8

4 Answers4

1

This isn't a matter of distinguishing integers from non-integers, because b really is an integer*. The precision of a Python float isn't enough to represent the square root of a to enough digits to get any of its fractional component. The second check you did:

print(a == b**2)

only prints False because while b is an integer, b**2 still isn't a.

If you want to test whether very large integers are exact squares, consider implementing a square root algorithm yourself.

*as in 0 fractional part, not as in isinstance(b, int).

user2357112
  • 260,549
  • 28
  • 431
  • 505
1
import numpy as np
import math
from decimal import *

a = 58106601358565889
b = np.sqrt(a)
c = math.sqrt(a)
d = Decimal(58106601358565889).sqrt()


print(d)
print(int(d))

print(c)
print(int(c))

print(b)
print(int(b))

o/p

241053109.0000000165938535976
241053109
241053109.0
241053109
241053109.0
241053109

I would say use decimal.

Expected code :

from decimal import *
d = Decimal(58106601358565889).sqrt()
print(d == int(d))

o/p

False
backtrack
  • 7,996
  • 5
  • 52
  • 99
  • `Decimal` still does calculations to limited (though configurable) precision. Doing all your math in `Decimal` just postpones the problem to even larger input sizes unless you perform additional configuration and secondary checks. – user2357112 Apr 12 '16 at 05:15
  • Thank you, Decimal did the trick. I hadn't known about that option. – Jeptha Apr 12 '16 at 05:16
  • Thanks user2357112, I will have to look into the limits of precision for `Decimal`. – Jeptha Apr 12 '16 at 05:20
  • @Jeptha: As an example of this solution not working, try it with `a = 10000000000000200000000000002`. That's not a square, but you'll get `True` anyway. – user2357112 Apr 12 '16 at 05:25
  • You're right, [user2357112](http://stackoverflow.com/users/2357112/user2357112), I still need to double check that one with: `int(math.sqrt(a)**2) == a`, which correctly gives `False` for the `a` you mention. – Jeptha Apr 12 '16 at 05:33
  • `Decimal` did work for the particular variable value I initially ran into, and I'll have to keep an eye out for the limits of `Decimal` precision now, too. – Jeptha Apr 12 '16 at 05:36
  • @Jeptha: But the double-check still isn't good enough. Try it with `a = (10**28+1)**2`, which *is* a square, and the double-check will say it's not. – user2357112 Apr 12 '16 at 06:03
0

It's not the precision of the int that is the problem - it's the limited precision of floats

>>> import math
>>> math.sqrt(58106601358565889)
241053109.0
>>> math.sqrt(58106601358565889) - 241053109
0.0

I think the double check would be the obvious solution

John La Rooy
  • 295,403
  • 53
  • 369
  • 502
0

You could also look at the gmpy2 library. It has a function for calculating the integer square root and also the integer square root plus remainder. There are no precision constraints.

>>> import gmpy2
>>> gmpy2.isqrt(58106601358565889)
mpz(241053109)
>>> gmpy2.isqrt_rem(58106601358565889)
(mpz(241053109), mpz(8))
>>> 

Disclaimer: I maintain gmpy2.

casevh
  • 11,093
  • 1
  • 24
  • 35