1

I came across an algorithm to tell if a given number is perfect square or not in O(logN) time.

Here is the implementation(JAVA) of the idea.

public boolean isPerfectSquare(long x) {
        if (x <= 1)
            return true;
        
        long low = 1;
        long high = x;
        long mid = 0;
        while (low <= high) {
            mid = low + (high - low) / 2l;
            if (mid * mid == x)
                return true;
            else if (mid * mid < x)
                low = mid + 1;
            else
                high = mid - 1;
        }
        
        return false;
    }

This works fine for numbers like 256, 808201 , etc But fails for numbers like 999966000289.

I cannot figure out why?

Piyush Keshari
  • 170
  • 2
  • 10
  • 3
    Maybe because mid*mid overflows. – luk2302 Feb 04 '22 at 18:32
  • 2
    Because numerical types in C have their limits. You should ask yourself what will be `mid * mid` when feeding it `999966000289` and would it fit into the `long` type you are working with. There is also a chance that `long` in your system can't even fit `999966000289` itself. – Eugene Sh. Feb 04 '22 at 18:32
  • Makes sense now. Is there a way to check for square root property for large numbers? – Piyush Keshari Feb 04 '22 at 18:41
  • @Insane_banda consider making `mid` a `long long int`. And while you're at it, you can also use unsigned types for `low`, `high`, `mid`, to gain another factor of 2. – Dillon Davis Feb 04 '22 at 19:10
  • `999966000289` is a 40-bit number. Try `unsigned long long` for a limit of `18446744073709551615`. – pmg Feb 04 '22 at 19:37
  • you may want to experiment with [Newton's method](https://en.wikipedia.org/wiki/Newton%27s_method). – pmg Feb 04 '22 at 19:39
  • 2
    @pmg: Newton's method is efficient when you are close to the root. For initialization, it is better to find the closest power of 4 (easily done by shifts) and compute the same power of 2. –  Feb 04 '22 at 19:47
  • @DillonDavis this is a JAVA implementation – Piyush Keshari Feb 05 '22 at 07:51

1 Answers1

1

As mentioned in the comments, the problem is that the intermediate mid*mid may overflow. It will help to use an unsigned type and a "long" or "long long" variant.

However, with the initial values of low and high, the first value of mid is close to x/4. If x is large, this is a great overshoot of the square root.

Hence, we can improve the range of manageable numbers by improving the initial low and high limit estimates.

Disclaimer: The Stack Overflow format is not suitable for long analysis. I have a good argument that the following works, part of which I have included below, but the full analysis is too lengthy to include here.

bool isPerfectSquare(unsigned long x) {
    if (x <= 1)
        return true;
        
    unsigned long low = 1;
    unsigned long high = x;

    // Improve the low/high limits
    while((low<<1) < (high>>1))
    {
        low <<= 1;
        high >>= 1;
    }

    unsigned long mid = 0;
    while (low <= high) {
        mid = low + (high - low) / 2l;
        if (mid * mid == x)
            return true;
        else if (mid * mid < x)
            low = mid + 1;
        else
            high = mid - 1;
    }
    return false;
}

With this modification, the initial value of mid is much smaller for large values of x and thus larger values of x can be handled without overflow.

It is not so difficult to show that the lower limit will not exceed the square root and doing that illustrates the intuition behind this method:

For some t, where 1<=t<2, x=t*2^r for some integer, r. Thus:

    sqrt(x) = sqrt(t) * 2^(r/2)

which implies that

    2^(r/2) <= sqrt(x) < 2^(r/2+1)

Thus a lower limit is a binary 1 shifted until it gets halfway (when r is even) or as close as possible (when r is odd) to the leftmost 1-bit in the binary representation of x. This is exactly what happens in the while-loop.

Showing that high is indeed an upper bound of the square root after the while-loop requires a longer analysis.

nielsen
  • 5,641
  • 10
  • 27