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