Быстрый обратный квадратный корень![]() Бы́стрый обра́тный квадра́тный ко́рень (также быстрый InvSqrt() или 0x5F3759DF по используемой «магической» константе) — это приближённый алгоритм вычисления обратного квадратного корня для положительных 32-битных чисел с плавающей запятой. Алгоритм использует целочисленные операции «вычесть» и «битовый сдвиг», а также дробные «вычесть» и «умножить» — без медленных операций «разделить» и «квадратный корень». Несмотря на «хакерство» на битовом уровне, приближение монотонно и непрерывно: близкие аргументы дают близкий результат. Точности (менее 0,2 % в меньшую сторону и никогда — в большую)[1][2] не хватает для настоящих численных расчётов и даже для нормирования матриц поворота в трёхмерной графике[3], однако вполне достаточно для маловажных эффектов вроде освещения и теней. Алгоритм стал широко известен благодаря реализации в компьютерной игре Quake III Arena, производящая сложные вычисления для отрисовки трёхмерной графики. Мотивация![]() «Прямое» наложение освещения на трёхмерную модель, даже высокополигональную, даже с учётом закона Ламберта и других сложных формул отражения и рассеивания, сразу же выдаст полигональный вид — зритель увидит разницу в освещении по рёбрам многогранника[4]. Иногда так и нужно — если предмет действительно угловатый. А для криволинейных предметов поступают так: в трёхмерной программе указывают, острое ребро или сглаженное[4]. В зависимости от этого ещё при экспорте модели по углам треугольников вычисляют нормаль единичной длины к криволинейной поверхности. При анимации и поворотах игра преобразует эти нормали вместе с остальными трёхмерными данными; при наложении освещения — интерполирует по всему треугольнику и нормализует (доводит до единичной длины). Чтобы нормализовать вектор, надо разделить все три его компонента на длину. Или, что лучше, умножить их на величину, обратную длине: . За секунду для отрисовки кадра в реальном времени должны проводиться миллионы таких вычислений. До того, как было создано специальное аппаратное обеспечение для обработки трансформаций и освещения, программное обеспечение вычислений могло быть медленным. В частности, в начале 1990-х большинство вычислений с плавающей запятой отставало по производительности от операций с целыми числами. АлгоритмАлгоритм принимает 32-битное число с плавающей запятой (одинарной точности в формате IEEE 754) в качестве исходных данных и производит над ним следующие операции:
Реализация алгоритма в том виде, в котором он был впервые опубликован в исходном коде Quake III[5]: float Q_rsqrt( float number )
{
long i;
float x2, y;
const float threehalfs = 1.5F;
x2 = number * 0.5F;
y = number;
i = * ( long * ) &y; // evil floating point bit level hacking
i = 0x5f3759df - ( i >> 1 ); // what the fuck?
y = * ( float * ) &i;
y = y * ( threehalfs - ( x2 * y * y ) ); // 1st iteration
// y = y * ( threehalfs - ( x2 * y * y ) ); // 2nd iteration, this can be removed
return y;
}
Эта реализация ориентирована на 32-битную архитектуру процессоров x86, в которой размеры #include <stdint.h>
float Q_rsqrt( float number )
{
const float x2 = number * 0.5F;
const float threehalfs = 1.5F;
union {
float f;
uint32_t i;
} conv = {number}; // такая инициализация присвоит поле «f»
conv.i = 0x5f3759df - ( conv.i >> 1 );
conv.f *= threehalfs - x2 * conv.f * conv.f;
return conv.f;
}
В C++ для устранения неопределенного поведения при приведении числа с плавающей точкой в целочисленное алгоритм можно реализовать следующим образом (используются возможности стандартов C++20 и C++23): import std;
constexpr std::float32_t Q_rsqrt(std::float32_t number) noexcept
{
const auto y = std::bit_cast<std::float32_t>(
0x5f3759df - (std::bit_cast<std::uint32_t>(number) >> 1));
return y * (1.5f32 - (number * 0.5f32 * y * y));
}
ИсторияСаму идею приближения дробного числа целым для вычисления придумали Уильям Кэхэн и К. Ын в 1986[6]. До этой идеи добрались Грег Уолш, Клив Моулер и Гэри Таролли, работавшие тогда в Ardent Computer[7][8]. Грегу Уолшу и приписывается знаменитая константа 0x5F3759DF. Таролли, перешедший в 3dfx, принёс алгоритм туда, где он и применялся для расчёта углов падения и отражения света в трёхмерной графике. Джим Блинн, специалист по 3D-графике, переизобрёл метод в 1997 году с более простой константой 1,5[9]. Более сложный табличный метод, который считает до 4 знаков (0,01 %), найден при дизассемблировании игры Interstate ’76 (1997)[10]. Однако данный метод не появлялся на общедоступных форумах, таких как Usenet, до 2002—2003-х годов. Метод обнаружили в Quake III: Arena, опубликованном в 2005, и приписали авторство Джону Кармаку. Тот предположил, что его в id Software принёс Майкл Абраш, специалист по графике, или Терье Матисен, специалист по ассемблеру; другие ссылались на Брайана Хука, выходца из 3dfx[11]. Изучение вопроса показало, что код имел более глубокие корни как в аппаратной, так и в программной сферах компьютерной графики. Исправления и изменения производились как Silicon Graphics, так и 3dfx Interactive, при этом самая ранняя известная версия написана Гэри Таролли для SGI Indigo. Приближённый обратный корень не раз реализовывали аппаратно: Анализ и погрешностьПреобразование «дробное ↔ целое»Битовое представление 4-байтового дробного числа в формате IEEE 754 выглядит так:
Имеем дело только с положительными числами (знаковый бит равен нулю), не денормализованными, не ∞ и не NaN. Такие числа в стандартном виде записываются как 1,mmmm2·2e. Часть 1,mmmm называется мантиссой, e — порядком. Головную единицу не хранят (неявная единица), так что величину 0,mmmm назовём явной частью мантиссы. Кроме того, у машинных дробных чисел смещённый порядок: 20 записывается как 011.1111.12[a]. На положительных числах биекция «дробное ↔ целое» (ниже обозначенная как ) непрерывна как кусочно-линейная функция и монотонна. Отсюда сразу же можно заявить, что быстрый обратный корень, как комбинация непрерывных функций, непрерывен. А первая его часть — сдвиг-вычитание — к тому же монотонна и кусочно-линейна. Биекция сложна, но почти «бесплатна»: в зависимости от архитектуры процессора и соглашений вызова, нужно или ничего не делать, или переместить число из дробного регистра в целочисленный. В примере выше целочисленное представление равняется 0x3E20.0000, и оно раскладывается так: знаковое поле 0, смещённый порядок[a] 011.1110.02=124, несмещённый 124−127=−3, мантисса (вместе с неявной единицей) 1,012=1,25, и дробное значение 1,25·2−3=0,15625. Обозначим явную часть мантиссы числа , — несмещённый порядок, — разрядность мантиссы, — смещение порядка. Число[b] будет иметь целочисленное представление . Можно выписать и обратное преобразование: , . Первое приближение![]() Поскольку и , нелинейную функцию «логарифм» можно приблизить линейной , где — параметр, используемый для настройки точности приближения. Этот параметр варьируется от 0 (формула точна при и ) до 0,086 (точна в одной точке, ). Аргумент , записанный в линейно-логарифмической разрядной сетке компьютерных дробных, можно[5][14] приблизить логарифмической сеткой как . Перегруппируем , тогда целочисленное представление числа можно приблизить как Соответственно, . 1️⃣ Проделаем это же[5] для (соответственно )
Соединив 1️⃣ и 2️⃣, получаем[5] Это и есть формула первого приближения. Магическая константа QМагическая константа находится в пространстве компьютерных целых, но её дробное представление также важно для исследователей. Несмещённый порядок при B=127 и имеющихся ограничениях на σ:
Смещение порядка B нечётное, и полная мантисса числа равняется
а в двоичной записи[a] — 0|101.1111.0|011… (1 — неявная единица; 0,5 пришли из порядка; маленькая единица соответствует диапазону [1,375; 1,5) и потому крайне вероятна, но не гарантирована нашими прикидочными расчётами.) ![]() Через константу c можно вычислить, чему равняется первое кусочно-линейное приближение[15] (в источнике используется не сама мантисса, а её явная часть ):
На бо́льших или меньших результат пропорционально меняется: при учетверении результат уменьшается ровно вдвое. Метод НьютонаМетод Ньютона даёт[15] , , и . Функция убывает и выпукла вниз, на таких функциях метод Ньютона подбирается к истинному значению слева — потому алгоритм всегда занижает ответ. После одного шага метода Ньютона результат получается довольно точный (+0 % −0,18 %)[1][2], что для целей компьютерной графики более чем подходит (1⁄256 ≈ 0,39 %). Такая погрешность сохраняется на всём диапазоне нормированных дробных чисел. Два шага дают точность в 5 цифр[1], после четырёх достигается погрешность double. Метод Ньютона может испортить монотонность. Однако как компьютерный перебор, так и аналитические выкладки говорят, что монотонность остаётся. Существуют аналогичные алгоритмы для других степеней, например, квадратного или кубического корня[5]. Дальнейшие улучшенияПри желании можно перебалансировать погрешность, умножив коэффициенты 1,5 и 0,5 на 1,0009, чтобы метод давал симметрично ±0,09 % — так поступили[10] в игре Interstate ’76, которая также делает итерацию метода Ньютона. Константа Уолша 0x5F3759DF ↔[c] 1,4324301·263 оказалась очень хорошей. Крис Ломонт и Мэттью Робертсон незначительно уменьшили[1][2] предельную относительную погрешность, отыскав перебором константу[d] 0x5F375A86 ↔ 1,4324500·263, а для double — 0x5FE6EB50C7B537A9. Правда, для double алгоритм бессмысленный (не даёт выигрыша в точности по сравнению с float)[2]. Константу Ломонта удалось получить и аналитически (c = 1,432450084790142642179)[d], но расчёты довольно сложны[2][15]. Крайний случай — константа Блинна 1,5 — даёт без перебалансировок и улучшений около −0,6 %[9]. Чех Ян Ка́длец двоичным поиском, а затем перебором в окрестности найденного улучшил алгоритм[16]. Его метод даёт в 1,3 раза меньшую симметричную погрешность — не ±0,09 %, а ±0,065. float inv_sqrt(float x)
{ union { float f; uint32 u; } y = {x};
y.u = 0x5F1FFFF9ul - (y.u >> 1);
return 0.703952253f * y.f * (2.38924456f - x * y.f * y.f);
}
Вместо метода Ньютона можно использовать метод Галлея, в данной задаче эквивалентный методу Ньютона для уравнения . Он точнее одного шага метода Ньютона, но не дотягивает до двух и требует деление[16]: где нужно рассчитать всего один раз и сохранить во временной переменой. Предложено необычное улучшение нулевого (без метода Ньютона) приближения: вычислить два обратных корня четвёртой степени с разными константами и перемножить их как дробные[3]. Комментарии
Примечания
Ссылки
|
Portal di Ensiklopedia Dunia