Fastest way to determine if an integer's square root is an integer

前端 未结 30 1668
心在旅途
心在旅途 2020-11-22 02:17

I\'m looking for the fastest way to determine if a long value is a perfect square (i.e. its square root is another integer):

  1. I\'ve done it the ea
相关标签:
30条回答
  • 2020-11-22 02:53

    Regarding the Carmac method, it seems like it would be quite easy just to iterate once more, which should double the number of digits of accuracy. It is, after all, an extremely truncated iterative method -- Newton's, with a very good first guess.

    Regarding your current best, I see two micro-optimizations:

    • move the check vs. 0 after the check using mod255
    • rearrange the dividing out powers of four to skip all the checks for the usual (75%) case.

    I.e:

    // Divide out powers of 4 using binary search
    
    if((n & 0x3L) == 0) {
      n >>=2;
    
      if((n & 0xffffffffL) == 0)
        n >>= 32;
      if((n & 0xffffL) == 0)
          n >>= 16;
      if((n & 0xffL) == 0)
          n >>= 8;
      if((n & 0xfL) == 0)
          n >>= 4;
      if((n & 0x3L) == 0)
          n >>= 2;
    }
    

    Even better might be a simple

    while ((n & 0x03L) == 0) n >>= 2;
    

    Obviously, it would be interesting to know how many numbers get culled at each checkpoint -- I rather doubt the checks are truly independent, which makes things tricky.

    0 讨论(0)
  • 2020-11-22 02:54

    If you do a binary chop to try to find the "right" square root, you can fairly easily detect if the value you've got is close enough to tell:

    (n+1)^2 = n^2 + 2n + 1
    (n-1)^2 = n^2 - 2n + 1
    

    So having calculated n^2, the options are:

    • n^2 = target: done, return true
    • n^2 + 2n + 1 > target > n^2 : you're close, but it's not perfect: return false
    • n^2 - 2n + 1 < target < n^2 : ditto
    • target < n^2 - 2n + 1 : binary chop on a lower n
    • target > n^2 + 2n + 1 : binary chop on a higher n

    (Sorry, this uses n as your current guess, and target for the parameter. Apologise for the confusion!)

    I don't know whether this will be faster or not, but it's worth a try.

    EDIT: The binary chop doesn't have to take in the whole range of integers, either (2^x)^2 = 2^(2x), so once you've found the top set bit in your target (which can be done with a bit-twiddling trick; I forget exactly how) you can quickly get a range of potential answers. Mind you, a naive binary chop is still only going to take up to 31 or 32 iterations.

    0 讨论(0)
  • 2020-11-22 02:54

    It's been pointed out that the last d digits of a perfect square can only take on certain values. The last d digits (in base b) of a number n is the same as the remainder when n is divided by bd, ie. in C notation n % pow(b, d).

    This can be generalized to any modulus m, ie. n % m can be used to rule out some percentage of numbers from being perfect squares. The modulus you are currently using is 64, which allows 12, ie. 19% of remainders, as possible squares. With a little coding I found the modulus 110880, which allows only 2016, ie. 1.8% of remainders as possible squares. So depending on the cost of a modulus operation (ie. division) and a table lookup versus a square root on your machine, using this modulus might be faster.

    By the way if Java has a way to store a packed array of bits for the lookup table, don't use it. 110880 32-bit words is not much RAM these days and fetching a machine word is going to be faster than fetching a single bit.

    0 讨论(0)
  • 2020-11-22 02:54

    Here is a divide and conquer solution.

    If the square root of a natural number (number) is a natural number (solution), you can easily determine a range for solution based on the number of digits of number:

    • number has 1 digit: solution in range = 1 - 4
    • number has 2 digits: solution in range = 3 - 10
    • number has 3 digits: solution in range = 10 - 40
    • number has 4 digits: solution in range = 30 - 100
    • number has 5 digits: solution in range = 100 - 400

    Notice the repetition?

    You can use this range in a binary search approach to see if there is a solution for which:

    number == solution * solution
    

    Here is the code

    Here is my class SquareRootChecker

    public class SquareRootChecker {
    
        private long number;
        private long initialLow;
        private long initialHigh;
    
        public SquareRootChecker(long number) {
            this.number = number;
    
            initialLow = 1;
            initialHigh = 4;
            if (Long.toString(number).length() % 2 == 0) {
                initialLow = 3;
                initialHigh = 10;
            }
            for (long i = 0; i < Long.toString(number).length() / 2; i++) {
                initialLow *= 10;
                initialHigh *= 10;
            }
            if (Long.toString(number).length() % 2 == 0) {
                initialLow /= 10;
                initialHigh /=10;
            }
        }
    
        public boolean checkSquareRoot() {
            return findSquareRoot(initialLow, initialHigh, number);
        }
    
        private boolean findSquareRoot(long low, long high, long number) {
            long check = low + (high - low) / 2;
            if (high >= low) {
                if (number == check * check) {
                    return true;
                }
                else if (number < check * check) {
                    high = check - 1;
                    return findSquareRoot(low, high, number);
                }
                else  {
                    low = check + 1;
                    return findSquareRoot(low, high, number);
                }
            }
            return false;
        }
    
    }
    

    And here is an example on how to use it.

    long number =  1234567;
    long square = number * number;
    SquareRootChecker squareRootChecker = new SquareRootChecker(square);
    System.out.println(square + ": " + squareRootChecker.checkSquareRoot()); //Prints "1524155677489: true"
    
    long notSquare = square + 1;
    squareRootChecker = new SquareRootChecker(notSquare);
    System.out.println(notSquare + ": " + squareRootChecker.checkSquareRoot()); //Prints "1524155677490: false"
    
    0 讨论(0)
  • 2020-11-22 02:57

    An integer problem deserves an integer solution. Thus

    Do binary search on the (non-negative) integers to find the greatest integer t such that t**2 <= n. Then test whether r**2 = n exactly. This takes time O(log n).

    If you don't know how to binary search the positive integers because the set is unbounded, it's easy. You starting by computing your increasing function f (above f(t) = t**2 - n) on powers of two. When you see it turn positive, you've found an upper bound. Then you can do standard binary search.

    0 讨论(0)
  • 2020-11-22 02:58

    It should be much faster to use Newton's method to calculate the Integer Square Root, then square this number and check, as you do in your current solution. Newton's method is the basis for the Carmack solution mentioned in some other answers. You should be able to get a faster answer since you're only interested in the integer part of the root, allowing you to stop the approximation algorithm sooner.

    Another optimization that you can try: If the Digital Root of a number doesn't end in 1, 4, 7, or 9 the number is not a perfect square. This can be used as a quick way to eliminate 60% of your inputs before applying the slower square root algorithm.

    0 讨论(0)
提交回复
热议问题