Я вычисляю встречные обратные точки в Q22.10 с раздел Goldschmidt для использования в моем растеризаторе программного обеспечения на ARM.
Это делается путем простой установки числителя в 1, т.е. числитель становится скаляром на первой итерации. Честно говоря, я слепо слежу за алгоритмом Википедии. В статье говорится, что если знаменатель масштабируется в полуоткрытом диапазоне (0,5, 1,0), хорошая первая оценка может основываться только на знаменателе: пусть F - оценочный скаляр, а D - знаменатель, то F = 2 - Д.
Но при этом я теряю много точности. Скажите, хочу ли я найти обратную ссылку 512.00002f. Чтобы уменьшить число вниз, я теряю 10 бит точности во фракционной части, которая сдвинута. Итак, мои вопросы:
- Есть ли способ выбрать лучшую оценку, которая не требует нормализации? Зачем? Почему нет? Математическое доказательство того, почему это или невозможно, было бы здорово.
- Кроме того, можно предварительно вычислить первые оценки, чтобы ряд сходился быстрее? Сейчас он сходится после 4-й итерации в среднем. В ARM это примерно в ~ 50 циклов наихудшего случая и не учитывает эмуляцию clz/bsr, а также поиск в памяти. Если это возможно, я хотел бы знать, увеличивает ли это количество ошибок и насколько.
Вот мой тестовый файл. Примечание. Реализация программного обеспечения clz
в строке 13 из моего сообщения здесь. Вы можете заменить его внутренним, если хотите. clz
должен возвращать число начальных нулей и 32 для значения 0.
#include <stdio.h>
#include <stdint.h>
const unsigned int BASE = 22ULL;
static unsigned int divfp(unsigned int val, int* iter)
{
/* Numerator, denominator, estimate scalar and previous denominator */
unsigned long long N,D,F, DPREV;
int bitpos;
*iter = 1;
D = val;
/* Get the shift amount + is right-shift, - is left-shift. */
bitpos = 31 - clz(val) - BASE;
/* Normalize into the half-range (0.5, 1.0] */
if(0 < bitpos)
D >>= bitpos;
else
D <<= (-bitpos);
/* (FNi / FDi) == (FN(i+1) / FD(i+1)) */
/* F = 2 - D */
F = (2ULL<<BASE) - D;
/* N = F for the first iteration, because the numerator is simply 1.
So don't waste a 64-bit UMULL on a multiply with 1 */
N = F;
D = ((unsigned long long)D*F)>>BASE;
while(1){
DPREV = D;
F = (2<<(BASE)) - D;
D = ((unsigned long long)D*F)>>BASE;
/* Bail when we get the same value for two denominators in a row.
This means that the error is too small to make any further progress. */
if(D == DPREV)
break;
N = ((unsigned long long)N*F)>>BASE;
*iter = *iter + 1;
}
if(0 < bitpos)
N >>= bitpos;
else
N <<= (-bitpos);
return N;
}
int main(int argc, char* argv[])
{
double fv, fa;
int iter;
unsigned int D, result;
sscanf(argv[1], "%lf", &fv);
D = fv*(double)(1<<BASE);
result = divfp(D, &iter);
fa = (double)result / (double)(1UL << BASE);
printf("Value: %8.8lf 1/value: %8.8lf FP value: 0x%.8X\n", fv, fa, result);
printf("iteration: %d\n",iter);
return 0;
}