Как работает этот алгоритм аппроксимации деления?

Я работаю над игрой с программным рендерером, чтобы получить наиболее точный вид PS1. Когда я занимался исследованиями того, как работала система графики/рендеринга PS1, причина шатких вершин и т. д., я наткнулся на некоторую документацию о том, как они делали свое разделение. Вот ссылка на него:http://problemkaputt.de/psx-spx.htm#gteoverview (см. раздел "неточность деления GTE")

соответствующий код:

  if (H < SZ3*2) then                            ;check if overflow
    z = count_leading_zeroes(SZ3)                ;z=0..0Fh (for 16bit SZ3)
    n = (H SHL z)                                ;n=0..7FFF8000h
    d = (SZ3 SHL z)                              ;d=8000h..FFFFh
    u = unr_table[(d-7FC0h) SHR 7] + 101h        ;u=200h..101h
    d = ((2000080h - (d * u)) SHR 8)             ;d=10000h..0FF01h
    d = ((0000080h + (d * u)) SHR 8)             ;d=20000h..10000h
    n = min(1FFFFh, (((n*d) + 8000h) SHR 16))    ;n=0..1FFFFh
  else n = 1FFFFh, FLAG.Bit17=1, FLAG.Bit31=1    ;n=1FFFFh plus overflow flag

Я с трудом понимая, как это работает, что такое таблица "unr"? почему мы все меняем? Если бы кто-то мог дать более подробное объяснение того, как эта вещь на самом деле достигает разрыва, это было бы оценено.

1 ответов


этот алгоритм представляет собой фиксированное деление двух беззнаковых 16-битных дробных значений в [0,1). Сначала он вычисляет начальное 9-битное приближение к реципрокному делителю через поиск таблицы, уточняет это с помощью одной итерации Ньютона-Рафсона для реципрокного, xi+1: = xя * (2-d * xя), в результате чего взаимная точность около 16 бит, наконец, умножает это на дивиденд, давая 17-битный коэффициент в [0,2).

для поиска таблицы делитель сначала нормализуется в [0.5, 1), применяя масштабный коэффициент 2z, очевидно, дивиденды затем должны быть скорректированы на тот же масштабный коэффициент. Поскольку реципрокность операндов в [0.5, 1) будет [1,2], целочисленный бит реципрокности, как известно, равен 1, поэтому можно использовать 8-битные записи таблицы для получения 1,8 фиксированной точки реципрокности путем добавления 0x100 (= 1). Причина 0x101 используется здесь не ясно; это может быть из-за требования, что этот шаг всегда обеспечивает завышение истинного реципрокного.

следующие два шага являются дословными переводами итерации Ньютона-Рафсона для взаимного учета коэффициентов шкалы с фиксированной точкой. Так что 0x2000000 представляет 2.0. Код использует 0x2000080 так как он включает в себя константу округления 0x80 (=128)для следующего деления на 256, используемого для масштабирования результата. Следующий шаг также добавляет 0x00000080 как константа округления для a перемасштабирование деления на 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.89 e-3. Улучшенный поиск таблицы уменьшает максимальную относительную ошибку в u до 2.85 e-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).