Самый быстрый способ определить, является ли целочисленный квадратный корень целым числом - PullRequest
1355 голосов
/ 17 ноября 2008

Я ищу самый быстрый способ определить, является ли значение long идеальным квадратом (то есть его квадратный корень является другим целым числом):

  1. Я сделал это простым способом, используя встроенный Math.sqrt() функции, но мне интересно, если есть способ сделать это быстрее, ограничить себя только целочисленным доменом.
  2. Ведение справочной таблицы нецелесообразно (поскольку существует около 2 31,5 целые числа, площадь которых меньше 2 63 ).

Вот очень простой и понятный способ, которым я делаю это сейчас:

public final static boolean isPerfectSquare(long n)
{
  if (n < 0)
    return false;

  long tst = (long)(Math.sqrt(n) + 0.5);
  return tst*tst == n;
}

Примечание: я использую эту функцию во многих Project Euler задачах. Так что больше никому не придется поддерживать этот код. И этот вид микрооптимизации может реально изменить ситуацию, поскольку одна из задач состоит в том, чтобы выполнить каждый алгоритм менее чем за минуту, и в некоторых задачах эту функцию нужно будет вызывать миллионы раз.


Я пробовал разные варианты решения проблемы:

  • После исчерпывающего тестирования я обнаружил, что добавление 0.5 к результату Math.sqrt () необязательно, по крайней мере, на моей машине.
  • Быстрый обратный квадратный корень был быстрее, но он дал неверные результаты для n> = 410881. Однако, как подсказывает BobbyShaftoe , мы можем использовать хак FISR для n < 410881.
  • Метод Ньютона был немного медленнее, чем Math.sqrt(). Вероятно, это связано с тем, что Math.sqrt() использует что-то похожее на метод Ньютона, но реализовано в аппаратном обеспечении, поэтому оно намного быстрее, чем в Java. Кроме того, метод Ньютона все еще требовал использования двойных чисел.
  • Модифицированный метод Ньютона, который использовал несколько трюков, так что использовалась только целочисленная математика, требовал некоторых хаков, чтобы избежать переполнения (я хочу, чтобы эта функция работала со всеми положительными 64-битными целыми числами со знаком), и он все еще был медленнее Math.sqrt().
  • Бинарная отбивная была еще медленнее. Это имеет смысл, потому что двоичной отбивке в среднем потребуется 16 проходов, чтобы найти квадратный корень 64-битного числа.
  • Согласно тестам Джона, использование or операторов в C ++ быстрее, чем использование switch, но в Java и C #, похоже, нет никакой разницы между or и switch.
  • Я также попытался создать таблицу поиска (в качестве частного статического массива из 64 логических значений). Тогда вместо оператора switch или or я бы просто сказал if(lookup[(int)(n&0x3F)]) { test } else return false;. К моему удивлению, это было (немного) медленнее. Это связано с тем, что границы массивов проверяются в Java .

Ответы [ 35 ]

5 голосов
/ 04 декабря 2011

Я проверил все возможные результаты, когда наблюдаются последние n бит квадрата. Последовательно исследуя больше битов, можно исключить до 5/6 входных данных. Я действительно разработал это для реализации алгоритма факторизации Ферма, и он там очень быстрый.

public static boolean isSquare(final long val) {
   if ((val & 2) == 2 || (val & 7) == 5) {
     return false;
   }
   if ((val & 11) == 8 || (val & 31) == 20) {
     return false;
   }

   if ((val & 47) == 32 || (val & 127) == 80) {
     return false;
   }

   if ((val & 191) == 128 || (val & 511) == 320) {
     return false;
   }

   // if((val & a == b) || (val & c == d){
   //   return false;
   // }

   if (!modSq[(int) (val % modSq.length)]) {
        return false;
   }

   final long root = (long) Math.sqrt(val);
   return root * root == val;
}

Последний бит псевдокода можно использовать для расширения тестов, чтобы исключить больше значений. Вышеприведенные тесты для k = 0, 1, 2, 3

a имеет вид (3 << 2k) - 1 <li> b имеет вид (2 << 2k) <li> c имеет вид (2 << 2k + 2) - 1 <li> d имеет вид (2 << 2k - 1) * 10 </p>

Сначала он проверяет наличие квадратного остатка с модулями степени два, затем тестирует на основе окончательного модуля, а затем использует Math.sqrt для окончательного тестирования. Я придумал идею из верхнего поста и попытался ее расширить. Я ценю любые комментарии или предложения.

Обновление: Используя тест по модулю (modSq) и базе модулей 44352, мой тест выполняется в 96% времени по сравнению с тестом в обновлении ОП для чисел до 1 000 000 000.

5 голосов
/ 04 декабря 2010

Учитывая общую длину в битах (хотя я здесь использовал определенный тип), я попытался разработать упрощенный алгоритм, как показано ниже. Первоначально требуется простая и очевидная проверка для 0,1,2 или <0. Следующее просто в том смысле, что оно не пытается использовать любые существующие математические функции. Большая часть операторов может быть заменена побитовыми операторами. Я не проверял ни с какими контрольными данными все же. Я не являюсь экспертом в математике или компьютерном алгоритме, в частности, я хотел бы, чтобы вы указали на проблему Я знаю, что есть много шансов на улучшение. </p>

int main()
{
    unsigned int c1=0 ,c2 = 0;  
    unsigned int x = 0;  
    unsigned int p = 0;  
    int k1 = 0;  
    scanf("%d",&p);  
    if(p % 2 == 0) {  
        x = p/2; 
    }  
    else {  
        x = (p/2) +1;  
    }  
    while(x) 
    {
        if((x*x) > p) {  
            c1 = x;  
            x = x/2; 
        }else {  
            c2 = x;  
            break;  
        }  
    }  
    if((p%2) != 0)  
        c2++;

    while(c2 < c1) 
    {  
        if((c2 * c2 ) == p) {  
            k1 = 1;  
            break;  
        }  
        c2++; 
    }  
    if(k1)  
        printf("\n Perfect square for %d", c2);  
    else  
        printf("\n Not perfect but nearest to :%d :", c2);  
    return 0;  
}  
2 голосов
/ 01 ноября 2018

Я не знаю, упоминалось ли это ранее. Но я нашел решение здесь :

int result = (int)(floor(sqrt(b)) - ceil(sqrt(a)) + 1);
1 голос
/ 18 ноября 2008

Если скорость имеет значение, почему бы не выделить наиболее часто используемый набор входов и их значений в таблицу поиска, а затем выполнить любой оптимизированный магический алгоритм, который вы придумали для исключительных случаев?

1 голос
/ 22 февраля 2009

Должна быть возможность упаковать 'не может быть идеальным квадратом, если последние X цифр N' гораздо эффективнее, чем это! Я буду использовать 32-битные числа Java и получу достаточно данных для проверки последних 16 битов числа - это 2048 шестнадцатеричных значений типа int.

...

Ok. Либо я столкнулся с некоторой теорией чисел, которая немного выше меня, либо в моем коде есть ошибка. В любом случае вот код:

public static void main(String[] args) {
    final int BITS = 16;

    BitSet foo = new BitSet();

    for(int i = 0; i< (1<<BITS); i++) {
        int sq = (i*i);
        sq = sq & ((1<<BITS)-1);
        foo.set(sq);
    }

    System.out.println("int[] mayBeASquare = {");

    for(int i = 0; i< 1<<(BITS-5); i++) {
        int kk = 0;
        for(int j = 0; j<32; j++) {
            if(foo.get((i << 5) | j)) {
                kk |= 1<<j;
            }
        }
        System.out.print("0x" + Integer.toHexString(kk) + ", ");
        if(i%8 == 7) System.out.println();
    }
    System.out.println("};");
}

и вот результаты:

(изд: исключено из-за низкой производительности в prettify.js; просмотреть историю изменений, чтобы увидеть.)

1 голос
/ 13 октября 2017

Вот самый простой и краткий способ, хотя я не знаю, как он сравнивается с точки зрения циклов ЦП. Это прекрасно работает, если вы хотите знать, является ли корень целым числом. Если вам действительно важно, является ли оно целым числом, вы также можете понять это. Вот простая (и чистая) функция:

public static boolean isRootWhole(double number) {
    return Math.sqrt(number) % 1 == 0;
}

Если вам не нужна микрооптимизация, этот ответ лучше с точки зрения простоты и удобства обслуживания. Если вы будете получать отрицательные числа, возможно, вы захотите использовать Math.abs () для аргумента числа в качестве аргумента Math.sqrt ().

На моем 3,6 ГГц процессоре Intel i7-4790 запуск этого алгоритма на 0 - 10000000 занял в среднем 35 - 37 наносекунд на расчёт. Я сделал 10 последовательных прогонов, напечатав среднее время, затраченное на каждый из десяти миллионов квадратных расчетов. Каждый полный прогон занимал чуть более 600 мсек.

Если вы выполняете меньшее количество вычислений, более ранние вычисления занимают немного больше времени.

0 голосов
/ 18 октября 2018

метод Ньютона с целочисленной арифметикой

Если вы хотите избежать нецелочисленных операций, вы можете использовать метод ниже. В основном он использует метод Ньютона, модифицированный для целочисленной арифметики.

/**
 * Test if the given number is a perfect square.
 * @param n Must be greater than 0 and less
 *    than Long.MAX_VALUE.
 * @return <code>true</code> if n is a perfect
 *    square, or <code>false</code> otherwise.
 */
public static boolean isSquare(long n)
{
    long x1 = n;
    long x2 = 1L;

    while (x1 > x2)
    {
        x1 = (x1 + x2) / 2L;
        x2 = n / x1;
    }

    return x1 == x2 && n % x1 == 0L;
}

Эта реализация не может конкурировать с решениями, использующими Math.sqrt. Однако его производительность можно улучшить, используя механизмы фильтрации, описанные в некоторых других публикациях.

0 голосов
/ 26 сентября 2018

Не уверен, что это самый быстрый способ, но на это я наткнулся (давным-давно в старшей школе), когда мне было скучно и я играл с калькулятором во время урока математики. В то время я был очень удивлен, что это сработало ...

public static boolean isIntRoot(int number) {
    return isIntRootHelper(number, 1);
}

private static boolean isIntRootHelper(int number, int index) {
    if (number == index) {
        return true;
    }
    if (number < index) {
        return false;
    }
    else {
        return isIntRootHelper(number - 2 * index, index + 1);
    }
}
0 голосов
/ 17 ноября 2008

Не знаю о самом быстром, но самое простое - взять квадратный корень обычным способом, умножить результат на себя и посмотреть, соответствует ли оно исходному значению.

Поскольку мы говорим здесь целые числа, пост может включать коллекцию, в которой вы можете просто найти.

0 голосов
/ 27 декабря 2018

Вот решение «разделяй и властвуй».

Если квадратный корень из натурального числа (number) является натуральным числом (solution), вы можете легко определить диапазон для solution на основе количества цифр number:

  • number имеет 1 цифру: solution в диапазоне = 1 - 4
  • number имеет 2 цифры: solution в диапазоне = 3 - 10
  • number имеет 3 цифры: solution в диапазоне = 10 - 40
  • number имеет 4 цифры: solution в диапазоне = 30 - 100
  • number имеет 5 цифр: solution в диапазоне = 100 - 400

Заметили повторение?

Вы можете использовать этот диапазон в подходе двоичного поиска, чтобы увидеть, существует ли solution, для которого:

number == solution * solution

Вот код

Вот мой класс 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;
    }

}

А вот пример того, как его использовать.

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"
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...