Лучшие вопросы
Таймлайн
Чат
Перспективы
Быстрый обратный квадратный корень
быстрый приближённый алгоритм вычисления обратного квадратного корня Из Википедии, свободной энциклопедии
Remove ads
Бы́стрый обра́тный квадра́тный ко́рень (также быстрый InvSqrt() или 0x5F3759DF по используемой «магической» константе) — приближённый алгоритм вычисления обратного квадратного корня для положительных 32-битных чисел с плавающей запятой. Алгоритм использует целочисленные операции «вычесть» и «битовый сдвиг», а также дробные «вычесть» и «умножить» — без медленных операций «разделить» и «квадратный корень». Несмотря на «хакерство» на битовом уровне, приближение монотонно и непрерывно: близкие аргументы дают близкий результат. Точности (менее 0,2 % в меньшую сторону и никогда — в большую)[1][2] не хватает для настоящих численных расчётов и даже для нормирования матриц поворота в трёхмерной графике[3], однако вполне достаточно для маловажных эффектов вроде освещения и теней.

Алгоритм стал широко известен благодаря реализации в компьютерной игре Quake III Arena, в своё время налаживавшей красивое освещение даже полупрограммно, без использования Transform & Lighting.
Remove ads
Мотивация
Суммиров вкратце
Перспектива

«Прямое» наложение освещения на трёхмерную модель, даже высокополигональную, даже с учётом закона Ламберта и других сложных формул отражения и рассеивания, сразу же выдаст полигональный вид — зритель увидит разницу в освещении по рёбрам многогранника[4]. Иногда так и нужно — если предмет действительно угловатый. А для криволинейных предметов поступают так: в трёхмерной программе указывают, острое ребро или сглаженное[4]. В зависимости от этого ещё при экспорте модели по углам треугольников вычисляют нормаль единичной длины к криволинейной поверхности. При анимации и поворотах игра преобразует эти нормали вместе с остальными трёхмерными данными; при наложении освещения — интерполирует по всему треугольнику и нормализует (доводит до единичной длины).
Чтобы нормализовать вектор, надо разделить все три его компонента на длину. Или, что лучше, умножить их на величину, обратную длине: . За секунду для отрисовки кадра в реальном времени должны проводиться миллионы таких вычислений. До того, как было создано специальное аппаратное обеспечение для обработки трансформаций и освещения, программное обеспечение вычислений могло быть медленным. В частности, в начале 1990-х большинство вычислений с плавающей запятой отставало по производительности от операций с целыми числами.
Remove ads
Алгоритм
Суммиров вкратце
Перспектива
Алгоритм принимает 32-битное число с плавающей запятой (одинарной точности в формате IEEE 754) в качестве исходных данных и производит над ним следующие операции:
- Трактуя 32-битное дробное число как целое, провести операцию y0 = 5F3759DF16 − (x >> 1), где >> — битовый сдвиг вправо. Результат снова трактуется как 32-битное дробное число.
- Для уточнения можно провести одну итерацию метода Ньютона: y₁ = y₀(1,5 − 0,5xy₀²).
Реализация алгоритма в том виде, в котором он был впервые опубликован в исходном коде 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; // страшное дробное хакерство на битовом уровне
i = 0x5f3759df - ( i >> 1 ); // что за чёрт?
y = * ( float * ) &i;
y = y * ( threehalfs - ( x2 * y * y ) ); // 1-я итерация
// y = y * ( threehalfs - ( x2 * y * y ) ); // 2-я итерация, можно убрать
return y;
}
Эта реализация ориентирована на 32-битную архитектуру процессоров x86, в которой размеры float и long одинаковы и равны 4 байтам. Корректная по меркам современного Си реализация, с учётом возможных оптимизаций и кроссплатформенности:
#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));
}
Remove ads
История
Суммиров вкратце
Перспектива
Саму идею приближения дробного числа целым для вычисления придумали Уильям Кэхэн и К. Ын в 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.
Приближённый обратный корень не раз реализовывали аппаратно: pfrsqrt (AMD 3DNow!, 1998), rsqrtss (Intel SSE, 1999), последний значительно точнее, 0,01 %[12]. А функция Transform & Lighting (на ПК — nVidia GeForce 256, 1999) перенесла расчёт освещения на видеоадаптер. Так что программистам на ПК уже с 2000-х годов алгоритм не нужен. Тем не менее, он остаётся полезным в проектах для встраиваемых систем или систем с ограниченными ресурсами[13].
Анализ и погрешность
Суммиров вкратце
Перспектива
Преобразование «дробное ↔ целое»
Битовое представление 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] для (соответственно )
- 2️⃣
Соединив 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].
Remove ads
Дальнейшие улучшения
Суммиров вкратце
Перспектива
При желании можно перебалансировать погрешность, умножив коэффициенты 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].
Remove ads
Комментарии
- Здесь и далее точки — границы полубайтов, вертикальные линии — границы полей компьютерного дробного.
- Здесь и далее ≡ означает «равно по определению».
- Неинтуитивное округление теоретического c=1,432450084… до 1,4324500 на самом деле двойное: сначала к ближайшему двоичному (0x3FB75A86 ≈ 1,432450056), а потом к самому круглому десятичному — единица младшего разряда равняется 1,19·10−7, и 1,19⁄2 > 0,56, так что 1,43245 — самое круглое, преобразующееся в 0x3FB75A86.
Remove ads
Примечания
Ссылки
Wikiwand - on
Seamless Wikipedia browsing. On steroids.
Remove ads
