Я вычисляю fixedpoint обратные величины в Q22.10 с подразделением Goldschmidt для использования в моем растрирующем процессоре программного обеспечения на ARM.
Это сделано, просто установив числитель на 1, т.е. числитель становится скаляром на первом повторении. Честно говоря, я отчасти следую алгоритму Википедии вслепую здесь. В статье говорится что, если знаменатель масштабируется в полуоткрытом диапазоне (0.5, 1.0], хорошая первая оценка может быть основана на одном только знаменателе: Позвольте F быть предполагаемым скаляром и D быть знаменателем, затем F = 2 - D.
Но при выполнении этого, я теряю большую точность. Скажите, хочу ли я найти обратную величину 512.00002f. Для уменьшения масштаба числа я теряю 10 битов точности в дробной части, которая переключается на верхний регистр. Так, мои вопросы:
Вот мой тестовый сценарий.Примечание: Реализация программного обеспечения clz
на строке 13 из моего сообщения здесь. Можно заменить его внутренним, если Вы хотите. clz
должен возвратить количество начальных нулей, и 32 для значения 0.
#include
#include
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;
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<
Я не мог не потратить час на вашу проблему ...
Этот алгоритм описан в разделе 5.5.2 книги «Arithmetique des ordinateurs» Жана-Мишеля Мюллера ( На французском). На самом деле это частный случай итераций Ньютона с 1 в качестве отправной точки. В книге дается простая формулировка алгоритма вычисления N / D с нормализацией D в диапазоне [1 / 2,1 [:
e = 1 - D
Q = N
repeat K times:
Q = Q * (1+e)
e = e*e
Число правильных битов удваивается на каждой итерации. В случае 32 бита будет достаточно 4 итераций. Вы также можете выполнять итерацию до тех пор, пока e
не станет слишком маленьким для изменения Q
.
Нормализация используется, поскольку она обеспечивает максимальное количество значащих битов в результате. Также легче вычислить ошибку и количество необходимых итераций, когда входные данные находятся в известном диапазоне.
После того, как ваше входное значение нормализовано, вам не нужно беспокоиться о значении BASE, пока вы не получите обратное.У вас просто есть 32-битное число X, нормализованное в диапазоне от 0x80000000 до 0xFFFFFFFF, и вычисляется приближение Y = 2 ^ 64 / X (Y не более 2 ^ 33).
Этот упрощенный алгоритм может быть реализован для вашего представления Q22.10 следующим образом:
// Fixed point inversion
// EB Apr 2010
#include <math.h>
#include <stdio.h>
// Number X is represented by integer I: X = I/2^BASE.
// We have (32-BASE) bits in integral part, and BASE bits in fractional part
#define BASE 22
typedef unsigned int uint32;
typedef unsigned long long int uint64;
// Convert FP to/from double (debug)
double toDouble(uint32 fp) { return fp/(double)(1<<BASE); }
uint32 toFP(double x) { return (int)floor(0.5+x*(1<<BASE)); }
// Return inverse of FP
uint32 inverse(uint32 fp)
{
if (fp == 0) return (uint32)-1; // invalid
// Shift FP to have the most significant bit set
int shl = 0; // normalization shift
uint32 nfp = fp; // normalized FP
while ( (nfp & 0x80000000) == 0 ) { nfp <<= 1; shl++; } // use "clz" instead
uint64 q = 0x100000000ULL; // 2^32
uint64 e = 0x100000000ULL - (uint64)nfp; // 2^32-NFP
int i;
for (i=0;i<4;i++) // iterate
{
// Both multiplications are actually
// 32x32 bits truncated to the 32 high bits
q += (q*e)>>(uint64)32;
e = (e*e)>>(uint64)32;
printf("Q=0x%llx E=0x%llx\n",q,e);
}
// Here, (Q/2^32) is the inverse of (NFP/2^32).
// We have 2^31<=NFP<2^32 and 2^32<Q<=2^33
return (uint32)(q>>(64-2*BASE-shl));
}
int main()
{
double x = 1.234567;
uint32 xx = toFP(x);
uint32 yy = inverse(xx);
double y = toDouble(yy);
printf("X=%f Y=%f X*Y=%f\n",x,y,x*y);
printf("XX=0x%08x YY=0x%08x XX*YY=0x%016llx\n",xx,yy,(uint64)xx*(uint64)yy);
}
Как отмечено в коде, операции умножения не являются полными 32x32-> 64 бит. E будет становиться все меньше и меньше и изначально умещается на 32 бита. Q всегда будет на 34 бита. Мы берем только старшие 32 бита продуктов.
Вывод 64-2 * BASE-shl
оставлен в качестве упражнения для читателя :-). Если он становится 0 или отрицательным, результат не представляется возможным (введенное значение слишком мало).
РЕДАКТИРОВАТЬ. В продолжение моего комментария вот вторая версия с неявным 32-м битом на Q. И E, и Q теперь хранятся в 32 битах:
uint32 inverse2(uint32 fp)
{
if (fp == 0) return (uint32)-1; // invalid
// Shift FP to have the most significant bit set
int shl = 0; // normalization shift for FP
uint32 nfp = fp; // normalized FP
while ( (nfp & 0x80000000) == 0 ) { nfp <<= 1; shl++; } // use "clz" instead
int shr = 64-2*BASE-shl; // normalization shift for Q
if (shr <= 0) return (uint32)-1; // overflow
uint64 e = 1 + (0xFFFFFFFF ^ nfp); // 2^32-NFP, max value is 2^31
uint64 q = e; // 2^32 implicit bit, and implicit first iteration
int i;
for (i=0;i<3;i++) // iterate
{
e = (e*e)>>(uint64)32;
q += e + ((q*e)>>(uint64)32);
}
return (uint32)(q>>shr) + (1<<(32-shr)); // insert implicit bit
}
Пара идей для вас, но ни одна из них не решает вашу проблему напрямую как указано.
adcs hi, den, hi, lsl # 1
subcc hi, hi, den
adcs lo, lo , lo
повторяется n бит раз с двоичным поиском вне clz, чтобы определить, с чего начать. Это чертовски быстро.
Опять же, не прямые ответы для вас, но, возможно, несколько идей для дальнейшего развития. Просмотр реального кода ARM, вероятно, тоже мне немного поможет.
Мэдс, вы совсем не теряете точности. Когда вы делите 512.00002f на 2 ^ 10, вы просто уменьшаете показатель степени вашего числа с плавающей запятой на 10. Мантисса остается прежней. Конечно, если показатель степени не достигает минимального значения, но этого не должно происходить, поскольку вы масштабируете до (0,5, 1].
РЕДАКТИРОВАТЬ: Хорошо, поэтому вы используете фиксированную десятичную точку. В этом случае вы должны разрешить другое представление знаменателя в вашем алгоритме. Значение D находится из (0,5, 1] не только в начале, но и на протяжении всего вычисления (легко доказать, что x * (2-x) <1 для x <1) . Таким образом, вы должны представить знаменатель с десятичной точкой в базе = 32. Таким образом, у вас будет 32 бита точности все время.
РЕДАКТИРОВАТЬ: Чтобы реализовать это, вам нужно будет изменить следующие строки вашего кода:
//bitpos = 31 - clz(val) - BASE;
bitpos = 31 - clz(val) - 31;
...
//F = (2ULL<<BASE) - D;
//N = F;
//D = ((unsigned long long)D*F)>>BASE;
F = -D;
N = F >> (31 - BASE);
D = ((unsigned long long)D*F)>>31;
...
//F = (2<<(BASE)) - D;
//D = ((unsigned long long)D*F)>>BASE;
F = -D;
D = ((unsigned long long)D*F)>>31;
...
//N = ((unsigned long long)N*F)>>BASE;
N = ((unsigned long long)N*F)>>31;
Также в конце вам придется сдвигать N не на битовые позиции, а на какое-то другое значение, которое мне сейчас лень вычислять :).