Этот алгоритм представляет собой разделение с фиксированной точкой двух беззнаковых 16-битовых дробных значений в [0,1]. Сначала он вычисляет начальное 9-битное приближение к обратному делителю посредством поиска таблицы, уточняет это с помощью одной итерации Newton-Raphson для обратного, x i + 1: = x i * (2 - d * x i), в результате чего получается обратная точность примерно до 16 бит, и, наконец, умножает это на дивиденд, получая 17 битов в [0,2).
Для поиска в таблице делитель сначала нормализуется на [0,5, 1), применяя масштабный коэффициент 2 z, очевидно, что тогда дивиденд должен быть скорректирован одним и тем же масштабным коэффициентом. Поскольку обратные операнды в [0,5, 1], будут [1,2], целочисленный бит взаимности, как известно, равен 1, поэтому можно использовать 8-битные записи таблицы для создания 1,8 фиксированной точки обратным путем добавления 0x100
(= 1). Причина использования 0x101
здесь не ясна; это может быть связано с требованием, чтобы этот шаг всегда обеспечивал завышение истинного взаимного.
Следующие два шага являются дословными переводами итерации Ньютона-Рафсона для взаимного учета с учетом факторов шкалы с фиксированной точкой. Таким образом, 0x2000000
представляет 2.0. Код использует 0x2000080
, поскольку он содержит константу округления 0x80
(= 128) для следующего деления на 256, используемого для масштабирования результата. Следующий шаг также добавляет 0x00000080
в качестве константы округления для деления масштабирования на 256. Без масштабирования это будет чистое умножение.
Окончательное умножение n*d
умножает обратное значение на d
с дивидендом в n
, чтобы получить коэффициент в 33 бит. Опять же, округляющая константа 0x8000 применяется до деления на 65536, чтобы перемасштабировать в соответствующий диапазон, давая коэффициент в 1.16 формате с фиксированной запятой.
Непрерывное масштабирование типично для вычислений с фиксированной точкой, где вы пытаетесь как можно больше сохранить промежуточные результаты, чтобы максимизировать точность конечного результата. Что является немного необычным, так это то, что округление применяется во всей промежуточной арифметике, а не только на последнем шаге. Возможно, необходимо было достичь определенного уровня точности.
Функция не все точная, хотя, вероятно, вызвана неточностями в начальном приближении. Во всех не исключительных случаях 2,424,807,756 соответствуют правильному округленному результату с фиксированной точкой, равному 1,16, 780 692 403 имеют ошибку 1 ulp, 15 606 093 имеют ошибку 2-ulp, а 86,452 имеют ошибку 3-ulp. В ходе быстрого эксперимента максимальная ошибка в начальном приближении u
составляла 3,89e-3. Улучшенный поиск в таблице уменьшает максимальную относительную погрешность в u
до 2.85e-3, уменьшая, но не устраняя ошибки 3-ulp в конечном результате.
Если вы хотите посмотреть на конкретном примере, рассмотрим h
= 0,3 (0x4ccd
), деленное на SZ3
= 0,2 (0x3333
). Затем z
= 2, таким образом d
= 0,2 * 4 = 0,8 (0xcccc
). Это приводит к u
= 1,25 (0x140
). Поскольку оценка достаточно точна, мы ожидаем, что (2 - d * u) будет около 1, и фактически d
= 1.000015 (0x10001
). Утонченный ответный результат равен d
= 1.250015 (0x14001
), и поэтому он n
= 1.500031 (0x18002
).
Попробуйте http://codereview.stackexchange.com/ – OldProgrammer
Он реализует [Отдел Ньютона-Рафсона] (https://en.wikipedia.org/wiki/Division_algorithm#Newton.E2.80.93Raphson_division) (ссылка Википедии). –
@OldProgrammer Этот вопрос не по теме для обзора кода и на его пути к закрытию - Code Review не делает «объяснения кода» или «почему/как это работает». –