Как часть программы, которую я пишу, мне нужно сравнить два значения в форме a + sqrt(b)
где a
и b
- целые числа без знака. Поскольку это часть узкого цикла, я бы хотел, чтобы это сравнение выполнялось как можно быстрее. (Если это имеет значение, я запускаю код на компьютерах с архитектурой x86-64, а целые числа без знака не превышают 10 ^ 6. Кроме того, я точно знаю, что a1<a2
.)
Это отдельная функция, которую я пытаюсь оптимизировать. Мои числа являются достаточно маленькими целыми числами, чтобы double
(или даже с float
) могли точно представлять их, но ошибка округления в результатах sqrt
не должна изменить результат.
// known pre-condition: a1 < a2 in case that helps
bool is_smaller(unsigned a1, unsigned b1, unsigned a2, unsigned b2) {
return a1+sqrt(b1) < a2+sqrt(b2); // computed mathematically exactly
}
Тестовый пример: is_smaller(900000, 1000000, 900001, 998002)
должен возвращать true, но, как показано в комментариях @wim, вычисление его с помощью sqrtf()
вернет false. Так бы (int)sqrt()
усечь обратно до целого числа.
a1+sqrt(b1) = 90100
и a2+sqrt(b2) = 901000.00050050037512481206
. Ближайший к этому поплавок - ровно 90100.
Поскольку функция sqrt()
как правило, довольно дорогая даже на современном x86-64, когда она полностью встроена как инструкция sqrtsd
, я стараюсь по возможности избегать вызова sqrt()
.
Удаление sqrt путем возведения в квадрат потенциально также позволяет избежать опасности ошибок округления, делая все вычисления точными.
Если бы вместо этого функция была что-то вроде этого...
bool is_smaller(unsigned a1, unsigned b1, unsigned x) {
return a1+sqrt(b1) < x;
}
... тогда я мог бы просто сделать return x-a1>=0 && static_cast<uint64_t>(x-a1)*(x-a1)>b1;
Но теперь, так как есть два sqrt(...)
члена, я не могу сделать одно и то же алгебраическое манипулирование.
Я мог бы возвести в квадрат значения дважды, используя эту формулу:
a1 + sqrt(b1) = a2 + sqrt(b2)
<==> a1 - a2 = sqrt(b2) - sqrt(b1)
<==> (a1 - a2) * (a1 - a2) = b1 + b2 - 2 * sqrt(b1) * sqrt(b2)
<==> (a1 - a2) * (a1 - a2) = b1 + b2 - 2 * sqrt(b1 * b2)
<==> (a1 - a2) * (a1 - a2) - (b1 + b2) = - 2 * sqrt(b1 * b2)
<==> ((b1 + b2) - (a1 - a2) * (a1 - a2)) / 2 = sqrt(b1 * b2)
<==> ((b1 + b2) - (a1 - a2) * (a1 - a2)) * ((b1 + b2) - (a1 - a2) * (a1 - a2)) / 4 = b1 * b2
Беззнаковое деление на 4 дешево, потому что это просто битовое смещение, но так как я возводю числа в квадрат дважды, мне нужно будет использовать 128-битные целые числа, и мне нужно будет ввести несколько проверок >=0
(потому что я сравниваю неравенство вместо равенство).
Такое ощущение, что может быть способ сделать это быстрее, применив лучшую алгебру к этой проблеме. Есть ли способ сделать это быстрее?