rootn (float_t x, int_t n)
- это функция, которая вычисляет n-й корень x 1/n и поддерживается некоторыми языками программирования, такими как OpenCL. Когда используются числа с плавающей запятой IEEE-754, эффективные низкоточные стартовые аппроксимации для любого n
могут быть сгенерированы на основе простой манипуляции базовым шаблоном бит, предполагая, что нужно обрабатывать только нормализованные операнды x
.
Двоичный показатель root (x, n)
будет равен 1/n бинарного показателя x
. Поле экспоненты числа с плавающей запятой IEEE-754 является смещенным. Вместо того, чтобы не смещать экспонента, делить его и повторно смещать результат, мы можем просто делить смещенный показатель на n
, а затем применить смещение, чтобы компенсировать ранее забытое смещение. Кроме того, вместо выделения, разделив поле экспоненты, мы можем просто делить весь операнд x
, повторно интерпретируемый как целое число. Требуемое смещение тривиально, чтобы найти в качестве аргумента значение 1, вернет результат 1 для любого n
.
Если у нас есть две вспомогательные функции, __int_as_float()
, которые переинтерпретируют IEEE-754 binary32
как int32
и __float_as_int()
, которые переинтерпретируют операнд int32
как binary32
, мы приходим к следуя низкоточному приближению к rootn (x, n)
простым образом:
rootn (x, n) ~= __int_as_float((int)(__float_as_int(1.0f)*(1.0-1.0/n)) + __float_as_int(x)/n)
Целочисленное деление __float_as_int (x) / n
может быть сведено к сдвигу или умножению плюс сдвиг известных оптимизаций целочисленного деления на константный делитель. Некоторые примеры:
rootn (x, 2) ~= __int_as_float (0x1fc00000 + __float_as_int (x) / 2) // sqrt (x)
rootn (x, 3) ~= __int_as_float (0x2a555556 + __float_as_int (x) / 3) // cbrt (x)
rootn (x, -1) ~= __int_as_float (0x7f000000 - __float_as_int (x) / 1) // rcp (x)
rootn (x, -2) ~= __int_as_float (0x5f400000 - __float_as_int (x) / 2) // rsqrt (x)
rootn (x, -3) ~= __int_as_float (0x54aaaaaa - __float_as_int (x) / 3) // rcbrt (x)
При всех этих аппроксимациях результат будет точным только тогда, когда x
= 2 n * m для целых m
. В противном случае аппроксимация обеспечит переоценку по сравнению с истинным математическим результатом. Мы можем приблизительно вдвое уменьшить максимальную относительную погрешность, слегка уменьшив смещение, что приведет к сбалансированному сочетанию недооценки и переоценки. Это легко выполнить путем бинарного поиска оптимального смещения, которое использует все числа с плавающей запятой в интервале [1, 2 n) в качестве тестовых примеров. Поступая таким образом, мы находим:
rootn (x, 2) ~= __int_as_float (0x1fbb4f2e + __float_as_int(x)/2) // max rel err = 3.47474e-2
rootn (x, 3) ~= __int_as_float (0x2a51067f + __float_as_int(x)/3) // max rel err = 3.15547e-2
rootn (x,-1) ~= __int_as_float (0x7ef311c2 - __float_as_int(x)/1) // max rel err = 5.05103e-2
rootn (x,-2) ~= __int_as_float (0x5f37642f - __float_as_int(x)/2) // max rel err = 3.42128e-2
rootn (x,-3) ~= __int_as_float (0x54a232a3 - __float_as_int(x)/3) // max rel err = 3.42405e-2
Некоторые могут заметить, что вычисление для rootn (x,-2)
в основном является начальной частью Quake fast inverse square root.
Основываясь на соблюдении различий между исходным исходным смещением и окончательным смещением, оптимизированным для минимизации максимальной относительной ошибки, я мог бы сформулировать эвристику для вторичной коррекции и, таким образом, окончательное, оптимизированное значение смещения.
Однако мне интересно, можно ли определить оптимальное смещение некоторой формулой замкнутой формы, так что максимальная абсолютная величина относительной ошибки max (| (approx (x, n) - x 1/n)/x 1/n |), минимизируется для всех x
в [1,2 n). Для удобства изложения мы можем ограничить номерами binary32
(IEEE-754 с одной точностью).
Я знаю, что в целом для минимаксных приближений нет решения замкнутой формы, однако у меня создается впечатление, что решения замкнутой формы существуют для случая полиномиальных приближений к алгебраическим функциям, таким как n-й корень. В этом случае мы имеем (кусочно) линейную аппроксимацию.