Обратный sqrt для фиксированной точки

Я ищу лучший алгоритм вычисления обратного квадратного корня для чисел с фиксированной запятой 16,16. Код ниже - это то, что у меня есть до сих пор (но в основном он берет квадратный корень и делится на исходное число, и я хотел бы получить обратный квадратный корень без деления). Если он что-то изменит, код будет скомпилирован для armv5te.

uint32_t INVSQRT(uint32_t n)
{
    uint64_t op, res, one;
    op = ((uint64_t)n<<16);
    res = 0;
    one = (uint64_t)1 << 46;
    while (one > op) one >>= 2;
    while (one != 0)
    {
        if (op >= res + one)
        {
            op -= (res + one);
            res +=  (one<<1);
        }
        res >>= 1;
        one >>= 2;
    }
    res<<=16;
    res /= n;
    return(res);
}

person Jonathan    schedule 08.06.2011    source источник
comment
Педантизм: Вы, наверное, имеете в виду обратный квадратный корень?   -  person Oliver Charlesworth    schedule 09.06.2011
comment
en.wikipedia.org/wiki/Fast_inverse_square_root?   -  person Guerrero    schedule 09.06.2011
comment
^ именно я собирался ответить этим   -  person Jonathan    schedule 09.06.2011
comment
@Guerrero, @Jonathan: Да, это название вводит в заблуждение (действительно, в этой статье говорится, что быстрый обратный квадратный корень ... это метод вычисления обратной величины квадратного корня). Обратный квадратный корень - это просто возведение в квадрат!   -  person Oliver Charlesworth    schedule 09.06.2011


Ответы (3)


Уловка состоит в том, чтобы применить метод Ньютона к задаче x - 1 / y ^ 2 = 0. Итак, для данного x решите относительно y, используя итерационную схему.

Y_(n+1) = y_n * (3 - x*y_n^2)/2

Деление на 2 - это просто сдвиг на бит или, в худшем случае, умножение на 0,5. Эта схема сходится к y = 1 / sqrt (x) точно так, как было запрошено, и вообще без каких-либо истинных делений.

Единственная проблема в том, что вам нужно приличное начальное значение y. Насколько я помню, существуют пределы оценки y для сходимости итераций.

person Community    schedule 09.06.2011
comment
Вы можете найти подходящую отправную точку, используя экспоненциальный поиск, чтобы найти интервал, в котором x y y - 1 меняет знак, а затем использовать метод секущей для этого интервала и ньютонов после этого. - person Michael Anderson; 09.06.2011
comment
Но как это будет выглядеть в коде? Как вы думаете, это будет эффективнее того, что у меня уже есть? - person Jonathan; 09.06.2011
comment
Этот код удваивает количество правильных цифр в вашем результате на КАЖДОЙ итерации. Итак, если вы работаете с 16 цифрами, потребуется примерно 4 итерации для схождения, если вы начнете с 1 правильной цифры для первого приближения. Как это будет выглядеть в коде? Это очень похоже на одну строчку, которую я написал выше. - person ; 09.06.2011

Процессоры ARMv5TE обеспечивают быстрый целочисленный умножитель и инструкцию «подсчитывать ведущие нули». Они также обычно поставляются с кешами среднего размера. Исходя из этого, наиболее подходящим подходом для высокопроизводительной реализации является поиск в таблице для начального приближения с последующими двумя итерациями Ньютона-Рафсона для достижения полностью точных результатов. Мы можем еще больше ускорить первую из этих итераций с помощью дополнительных предварительных вычислений, которые включены в таблицу, - метод, использованный компьютерами Cray сорок лет назад.

Функция fxrsqrt() ниже реализует этот подход. Он начинается с 8-битного приближения r к обратному квадратному корню из аргумента a, но вместо сохранения r каждый элемент таблицы хранит 3r (в младших десяти битах 32-битной записи) и r 3 (в верхних 22 битах 32-битной записи). Это позволяет быстро вычислить первую итерацию как r 1 = 0,5 * (3 * r - a * r 3). Вторая итерация затем вычисляется обычным способом как r 2 = 0,5 * r 1 * (3 - r 1 * (r 1 * а)).

Чтобы иметь возможность выполнять эти вычисления точно, независимо от величины входных данных, аргумент a нормализуется в начале вычисления, по сути представляя его как 2.32 число с фиксированной точкой, умноженное на коэффициент масштабирования 2 скаль. В конце вычисления результат денормализуется согласно формуле 1 / sqrt (2 2n) = 2 -n. За счет округления результатов, наиболее значимый отброшенный бит которых равен 1, повышается точность, в результате чего почти все результаты округляются правильно. Исчерпывающие отчеты об испытаниях: results too low: 639 too high: 1454 not correctly rounded: 2093

В коде используются две вспомогательные функции: __clz() определяет количество начальных нулевых битов в ненулевом 32-битном аргументе. __umulhi() вычисляет 32 старших разряда полного 64-битного произведения двух 32-битных целых чисел без знака. Обе функции должны быть реализованы либо с помощью встроенных функций компилятора, либо с помощью встроенной сборки. В приведенном ниже коде я показываю переносимые реализации, хорошо подходящие для процессоров ARM, а также встроенные версии сборки для платформ x86. На платформах ARMv5TE __clz() следует сопоставить карту с инструкцией CLZ, а __umulhi() следует сопоставить с UMULL.

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <math.h>

#define USE_OWN_INTRINSICS 1

#if USE_OWN_INTRINSICS
__forceinline int __clz (uint32_t a)
{
    int r;
    __asm__ ("bsrl %1,%0\n\t" : "=r"(r): "r"(a));
    return 31 - r;
}

uint32_t __umulhi (uint32_t a, uint32_t b)
{
    uint32_t r;
    __asm__ ("movl %1,%%eax\n\tmull %2\n\tmovl %%edx,%0\n\t"
             : "=r"(r) : "r"(a), "r"(b) : "eax", "edx");
    return r;
}
#else // USE_OWN_INTRINSICS
int __clz (uint32_t a)
{
    uint32_t r = 32;
    if (a >= 0x00010000) { a >>= 16; r -= 16; }
    if (a >= 0x00000100) { a >>=  8; r -=  8; }
    if (a >= 0x00000010) { a >>=  4; r -=  4; }
    if (a >= 0x00000004) { a >>=  2; r -=  2; }
    r -= a - (a & (a >> 1));
    return r;
}

uint32_t __umulhi (uint32_t a, uint32_t b)
{
    return (uint32_t)(((uint64_t)a * b) >> 32);
}
#endif // USE_OWN_INTRINSICS

/*
 * For each sub-interval in [1, 4), use an 8-bit approximation r to reciprocal
 * square root. To speed up subsequent Newton-Raphson iterations, each entry in
 * the table combines two pieces of information: The least-significant 10 bits
 * store 3*r, the most-significant 22 bits store r**3, rounded from 24 down to
 * 22 bits such that accuracy is optimized.
 */
uint32_t rsqrt_tab [96] = 
{
    0xfa0bdefa, 0xee6af6ee, 0xe5effae5, 0xdaf27ad9,
    0xd2eff6d0, 0xc890aec4, 0xc10366bb, 0xb9a71ab2,
    0xb4da2eac, 0xadce7ea3, 0xa6f2b29a, 0xa279a694,
    0x9beb568b, 0x97a5c685, 0x9163027c, 0x8d4fd276,
    0x89501e70, 0x8563da6a, 0x818ac664, 0x7dc4fe5e,
    0x7a122258, 0x7671be52, 0x72e44a4c, 0x6f68fa46,
    0x6db22a43, 0x6a52623d, 0x67041a37, 0x65639634,
    0x622ffe2e, 0x609cba2b, 0x5d837e25, 0x5bfcfe22,
    0x58fd461c, 0x57838619, 0x560e1216, 0x53300a10,
    0x51c72e0d, 0x50621a0a, 0x4da48204, 0x4c4c2e01,
    0x4af789fe, 0x49a689fb, 0x485a11f8, 0x4710f9f5,
    0x45cc2df2, 0x448b4def, 0x421505e9, 0x40df5de6,
    0x3fadc5e3, 0x3e7fe1e0, 0x3d55c9dd, 0x3d55d9dd,
    0x3c2f41da, 0x39edd9d4, 0x39edc1d4, 0x38d281d1,
    0x37bae1ce, 0x36a6c1cb, 0x3595d5c8, 0x3488f1c5,
    0x3488fdc5, 0x337fbdc2, 0x3279ddbf, 0x317749bc,
    0x307831b9, 0x307879b9, 0x2f7d01b6, 0x2e84ddb3,
    0x2d9005b0, 0x2d9015b0, 0x2c9ec1ad, 0x2bb0a1aa,
    0x2bb0f5aa, 0x2ac615a7, 0x29ded1a4, 0x29dec9a4,
    0x28fabda1, 0x2819e99e, 0x2819ed9e, 0x273c3d9b,
    0x273c359b, 0x2661dd98, 0x258ad195, 0x258af195,
    0x24b71192, 0x24b6b192, 0x23e6058f, 0x2318118c,
    0x2318718c, 0x224da189, 0x224dd989, 0x21860d86,
    0x21862586, 0x20c19183, 0x20c1b183, 0x20001580
};

/* This function computes the reciprocal square root of its 16.16 fixed-point 
 * argument. After normalization of the argument if uses the most significant
 * bits of the argument for a table lookup to obtain an initial approximation 
 * accurate to 8 bits. This is followed by two Newton-Raphson iterations with
 * quadratic convergence. Finally, the result is denormalized and some simple
 * rounding is applied to maximize accuracy.
 *
 * To speed up the first NR iteration, for the initial 8-bit approximation r0
 * the lookup table supplies 3*r0 along with r0**3. A first iteration computes
 * a refined estimate r1 = 1.5 * r0 - x * r0**3. The second iteration computes
 * the final result as r2 = 0.5 * r1 * (3 - r1 * (r1 * x)).
 *
 * The accuracy for all arguments in [0x00000001, 0xffffffff] is as follows: 
 * 639 results are too small by one ulp, 1454 results are too big by one ulp.
 * A total of 2093 results deviate from the correctly rounded result.
 */
uint32_t fxrsqrt (uint32_t a)
{
    uint32_t s, r, t, scal;

    /* handle special case of zero input */
    if (a == 0) return ~a;
    /* normalize argument */
    scal = __clz (a) & 0xfffffffe;
    a = a << scal;
    /* initial approximation */
    t = rsqrt_tab [(a >> 25) - 32];
    /* first NR iteration */
    r = (t << 22) - __umulhi (t, a);
    /* second NR iteration */
    s = __umulhi (r, a);
    s = 0x30000000 - __umulhi (r, s);
    r = __umulhi (r, s);
    /* denormalize and round result */
    r = ((r >> (18 - (scal >> 1))) + 1) >> 1;
    return r;
}

/* reference implementation, 16.16 reciprocal square root of non-zero argment */
uint32_t ref_fxrsqrt (uint32_t a)
{
    double arg = a / 65536.0;
    double rsq = sqrt (1.0 / arg);
    uint32_t r = (uint32_t)(rsq * 65536.0 + 0.5);
    return r;
}

int main (void)
{
    uint32_t arg = 0x00000001;
    uint32_t res, ref;
    uint32_t err, lo = 0, hi = 0;

    do {
        res = fxrsqrt (arg);
        ref = ref_fxrsqrt (arg);

        err = 0;
        if (res < ref) {
            err = ref - res;
            lo++;
        }
        if (res > ref) {
            err = res - ref;
            hi++;
        }
        if (err > 1) {
            printf ("!!!! arg=%08x  res=%08x  ref=%08x\n", arg, res, ref);
            return EXIT_FAILURE;
        }
        arg++;
    } while (arg);
    printf ("results too low: %u  too high: %u  not correctly rounded: %u\n", 
            lo, hi, lo + hi);
    return EXIT_SUCCESS;
}
person njuffa    schedule 01.09.2015

У меня есть решение, которое я характеризую как «быстрый обратный sqrt, но для 32-битных фиксированных точек». Ни таблицы, ни справочника, просто переходите к делу с хорошим предположением.

Если хотите, перейдите к исходному коду ниже, но остерегайтесь нескольких вещей.

  1. (x * y)>>16 можно заменить любой схемой умножения с фиксированной точкой.
  2. Это не требует 64-битных [длинных слов], я просто использую это для простоты демонстрации. Для предотвращения переполнения при умножении используются длинные слова. Математическая библиотека с фиксированной точкой будет иметь функции умножения с фиксированной точкой, которые справятся с этим лучше.
  3. Первоначальное предположение довольно хорошее, поэтому вы получите относительно точные результаты в первом заклинании.
  4. Код более подробный, чем требуется для демонстрации.
  5. Значения меньше 65536 (‹1) и больше 32767 ‹---------------- 16 использовать нельзя.
  6. Обычно это не быстрее, чем использование таблицы квадратного корня и деления, если ваше оборудование имеет функцию деления. Если этого не происходит, это позволяет избежать разделений.
int fxisqrt(int input){

    if(input <= 65536){
        return 1;
    }

    long xSR = input>>1;
    long pushRight = input;
    long msb = 0;
    long shoffset = 0;
    long yIsqr = 0;
    long ysqr = 0;
    long fctrl = 0;
    long subthreehalf = 0;

    while(pushRight >= 65536){
        pushRight >>=1;
        msb++;
    }

    shoffset = (16 - ((msb)>>1));
    yIsqr = 1<<shoffset;
    //y = (y * (98304 - ( ( (x>>1) * ((y * y)>>16 ) )>>16 ) ) )>>16;   x2
    //Incantation 1
    ysqr = (yIsqr * yIsqr)>>16;
    fctrl = (xSR * ysqr)>>16;
    subthreehalf = 98304 - fctrl;
    yIsqr = (yIsqr * subthreehalf)>>16;
    //Incantation 2 - Increases precision greatly, but may not be neccessary
    ysqr = (yIsqr * yIsqr)>>16;
    fctrl = (xSR * ysqr)>>16;
    subthreehalf = 98304 - fctrl;
    yIsqr = (yIsqr * subthreehalf)>>16;
    return yIsqr;
}
person ponut64    schedule 04.04.2019