1

I'm trying to write a program to look for a number, n, between 0 and 100 such that n! + 1 is a perfect square. I'm trying to do this because I know there are only three so it was meant as a test of my Python ability.

Refer to Brocard's problem.

Asclepius
  • 57,944
  • 17
  • 167
  • 143
PythonNewbie
  • 43
  • 1
  • 4
  • Why have you failed? What error(s) or problem(s) have you encountered? – James Mills May 26 '15 at 01:14
  • This is a very interesting open problem in number theory: http://en.wikipedia.org/wiki/Brocard%27s_problem It doesn't look like there are any elegant shortcuts, so a brute force search for n in [0,100] is probably the best you can hope for. But it's important to use arbitrary precision integer math, rather than floating point, as several of the answers point out. – Jim Lewis May 26 '15 at 05:35

4 Answers4

5

math.sqrt always returns a float, even if that float happens to be, say, 4.0. As the docs say, "Except when explicitly noted otherwise, all return values are floats."

So, your test for type(math.sqrt(x)) == int will never be true.

You could try to work around that by checking whether the float represents an integer, like this:

sx = math.sqrt(x)
if round(sx) == sx:

There's even a built-in method that does this as well as possible:

if sx.is_integer():

But keep in mind that float values are not a perfect representation of real numbers, and there are always rounding issues. For example, for a too-large number, the sqrt might round to an integer, even though it really wasn't a perfect square. For example, if math.sqrt(10000000000**2 + 1).is_integer() is True, even though obviously the number is not a perfect square.

I could tell you whether this is safe within your range of values, but can you convince yourself? If not, you shouldn't just assume that it is.

So, is there a way we can check that isn't affected by float roading issues? Sure, we can use integer arithmetic to check:

sx = int(round(math.sqrt(x)))
if sx*sx == x:

But, as Stefan Pochmann points out, even if this check is safe, does that mean the whole algorithm is? No; sqrt itself could have already been rounded to the point where you've lost integer precision.

So, you need an exact sqrt. You could do this by using decimal.Decimal with a huge configured precision. This will take a bit of work, and a lot of memory, but it's doable. Like this:

decimal.getcontext().prec = ENOUGH_DIGITS
sx = decimal.Decimal(x).sqrt()

But how many digits is ENOUGH_DIGITS? Well, how many digits do you need to represent 100!+1 exactly?

So:

decimal.getcontext().prec = 156
while n <= 100:
    x = math.factorial(n) + 1
    sx = decimal.Decimal(x).sqrt()
    if int(sx) ** 2 == x:
        print(sx)
    n = n + 1

If you think about it, there's a way to reduce the needed precision to 79 digits, but I'll leave that as an exercise for the reader.


The way you're presumably supposed to solve this is by using purely integer math. For example, you can find out whether an integer is a square in logarithmic time just by using Newton's method until your approximation error is small enough to just check the two bordering integers.

abarnert
  • 354,177
  • 51
  • 601
  • 671
  • 1
    I'm playing with `decimal`'s `sqrt` and trying to solve your exercise, but have a problem. Why does it tell me that floor(sqrt(8)) is 3? `from decimal import *; getcontext().prec = 1; getcontext().rounding = ROUND_FLOOR; Decimal(8).sqrt()` – Stefan Pochmann May 26 '15 at 20:06
  • @StefanPochmann: Interesting. You'd have to look at how it's implemented (and/or run some other tests—does it give you the sqrt of `1.20` as `1.10` with `prec=3`?), and then look at how IEEE 854 actually defines `sqrt` to make sure it doesn't require using this method or something equivalent. But it definitely smells like a bug. (Of course that won't affect this case, because `int(3) ** 2 == 8` will be false.) – abarnert May 26 '15 at 20:43
  • @StefanPochmann: P.S., do you know where to find the last draft (free) standard of IEEE 854 since 754 and 854 were merged in 2008? (I can find a copy of the final standard, but that's presumably a copyright violation and I wouldn't want to link to it…) – abarnert May 26 '15 at 20:48
  • @StefanPochmann: Got it. [#1388949](http://bugs.python.org/issue1388949) was closed because `sqrt` is explicitly defined to round to half-even, as required by IBM's [General Decimal Arithmetic Specification](http://speleotrove.com/decimal/daops.html#refsqrt). Apparently the `decimal` module follows this as its primary definition, not IEEE 854-1987 (which says in section 4 "… every operation specified in Section 5. shall be performed as if it first produced an intermediate result correct to infinite precision and with unbounded range, and then that result rounded…"). – abarnert May 26 '15 at 20:53
  • Yes, I just saw #1388949 and some others as well, thanks. I'm just still displeased that [the documentation for sqrt](https://docs.python.org/3.4/library/decimal.html#decimal.Decimal.sqrt) doesn't mention that it ignores the rounding setting (it just says "Return the square root of the argument to full precision."). – Stefan Pochmann May 26 '15 at 20:58
  • @StefanPochmann: Yeah, if [`exp`](https://docs.python.org/3.4/library/decimal.html#decimal.Decimal.exp) explicitly mentions `ROUND_HALF_EVEN`, why shouldn't `sqrt` as well? Or maybe there should be an explanation in the notes at the end. You might want to raise a new issue on that. (This is why I try to come up with things like checking the square instead of checking that the value is exact when truncated; then I don't have to look up the detailed rules most of the time… but when I do, it would be nice if they were complete…) – abarnert May 26 '15 at 21:28
3

For very large numbers it's better to avoid using floating point square roots altogether because you will run into too many precision issues and you can't even guarantee that you will be within 1 integer value of the correct answer. Fortunately Python natively supports integers of arbitrary size, so you can write an integer square root checking function, like this:

def isSquare(x):
    if x == 1:
        return True
    low = 0
    high = x // 2
    root = high
    while root * root != x:
       root = (low + high) // 2
       if low + 1 >= high:
          return False
       if root * root > x:
          high = root
       else:
          low = root
    return True

Then you can run through the integers from 0 to 100 like this:

n = 0
while n <= 100:
    x = math.factorial(n) + 1
    if isSquare(x):
        print n
    n = n + 1
samgak
  • 23,944
  • 4
  • 60
  • 82
1

Here's another version working only with integers, computing the square root by adding decreasing powers of 2, for example intsqrt(24680) will be computed as 128+16+8+4+1.

 def intsqrt(n):
    pow2 = 1
    while pow2 < n:
        pow2 *= 2
    sqrt = 0
    while pow2:
        if (sqrt + pow2) ** 2 <= n:
            sqrt += pow2
        pow2 //= 2
    return sqrt

factorial = 1
for n in range(1, 101):
    factorial *= n
    if intsqrt(factorial + 1) ** 2 == factorial + 1:
        print(n)
Stefan Pochmann
  • 27,593
  • 8
  • 44
  • 107
0

The number math.sqrt returns is never an int, even if it's an integer.How to check if a float value is a whole number

Community
  • 1
  • 1
jwilner
  • 6,348
  • 6
  • 35
  • 47
  • Why doesn't it return as an integer type when it is an integer? – PythonNewbie May 26 '15 at 01:20
  • @PythonNewbie, an int and an integer aren't the same thing. The former is just one way of representing the latter. Floats can also accurately represent integers, and it's simpler and more consistent for the square root function to always return the same type. – jwilner May 26 '15 at 01:33