Jakie techniki aproksymacji istnieją przy obliczaniu pierwiastka kwadratowego?

12

Mam bardzo ograniczone zasoby, ponieważ pracuję z mikrokontrolerem. Czy istnieje rozszerzenie serii Taylor, wspólna tabela odnośników lub podejście rekurencyjne?

Wolałbym zrobić coś bez użycia sqrt () math.h

http://www.cplusplus.com/reference/cmath/sqrt/

tarabajt
źródło
5
Sprawdź ten link: codeproject.com/Articles/69941/...
Matt L.,
1
Pomijając fakt, że jest to bardziej pytanie programistyczne, dlaczego nie dać odpowiedzi Matt?
jojek
Dane zmiennoprzecinkowe czy stałe? W przypadku punktu stałego preferowana może być metoda iteracyjna, ale nie zawracam sobie głowy jej wyjaśnianiem, chyba że naprawdę tego chcesz.
Oscar
@Oscar, chciałbym nauczyć się metody stałoprzecinkowej, ponieważ staram się nie wymagać użycia pływaków w moim oprogramowaniu :).
tarabyte

Odpowiedzi:

13

jeśli chcesz tanie i brudne zoptymalizowane rozszerzenie serii mocy (współczynniki dla serii Taylora zbiegają się powoli) sqrt()i kilka innych transcendentałów, mam trochę kodu z dawno temu. sprzedałem ten kod, ale nikt nie zapłacił mi za niego od prawie dekady. więc myślę, że wydam go do publicznego użytku. ten konkretny plik był dla aplikacji, w której procesor miał zmiennoprzecinkowy (pojedyncza precyzja IEEE-754) i mieli kompilator C i system deweloperski, ale niemają (lub nie chcieli połączyć) stdlib, który miałby standardowe funkcje matematyczne. nie potrzebowali doskonałej precyzji, ale chcieli, aby wszystko było szybkie. możesz dość łatwo odtworzyć kod, aby zobaczyć, jakie są współczynniki serii mocy i napisać własny kod. kod ten zakłada IEEE-754 i zamaskował bity dla mantysy i wykładnika.

wygląda na to, że „znacznik kodu” SE jest nieprzyjazny dla znaków kąta (wiesz „>” lub „<”), więc prawdopodobnie będziesz musiał nacisnąć „edytuj”, aby zobaczyć wszystko.

//
//    FILE: __functions.h
//
//    fast and approximate transcendental functions
//
//    copyright (c) 2004  Robert Bristow-Johnson
//
//    [email protected]
//


#ifndef __FUNCTIONS_H
#define __FUNCTIONS_H

#define TINY 1.0e-8
#define HUGE 1.0e8

#define PI              (3.1415926535897932384626433832795028841972)        /* pi */
#define ONE_OVER_PI     (0.3183098861837906661338147750939)
#define TWOPI           (6.2831853071795864769252867665590057683943)        /* 2*pi */
#define ONE_OVER_TWOPI  (0.15915494309189535682609381638)
#define PI_2            (1.5707963267948966192313216916397514420986)        /* pi/2 */
#define TWO_OVER_PI     (0.636619772367581332267629550188)
#define LN2             (0.6931471805599453094172321214581765680755)        /* ln(2) */
#define ONE_OVER_LN2    (1.44269504088896333066907387547)
#define LN10            (2.3025850929940456840179914546843642076011)        /* ln(10) */
#define ONE_OVER_LN10   (0.43429448190325177635683940025)
#define ROOT2           (1.4142135623730950488016887242096980785697)        /* sqrt(2) */
#define ONE_OVER_ROOT2  (0.707106781186547438494264988549)

#define DB_LOG2_ENERGY          (3.01029995663981154631945610163)           /* dB = DB_LOG2_ENERGY*__log2(energy) */
#define DB_LOG2_AMPL            (6.02059991327962309263891220326)           /* dB = DB_LOG2_AMPL*__log2(amplitude) */
#define ONE_OVER_DB_LOG2_AMPL   (0.16609640474436811218256075335)           /* amplitude = __exp2(ONE_OVER_DB_LOG2_AMPL*dB) */

#define LONG_OFFSET     4096L
#define FLOAT_OFFSET    4096.0



float   __sqrt(float x);

float   __log2(float x);
float   __exp2(float x);

float   __log(float x);
float   __exp(float x);

float   __pow(float x, float y);

float   __sin_pi(float x);
float   __cos_pi(float x);

float   __sin(float x);
float   __cos(float x);
float   __tan(float x);

float   __atan(float x);
float   __asin(float x);
float   __acos(float x);

float   __arg(float Imag, float Real);

float   __poly(float *a, int order, float x);
float   __map(float *f, float scaler, float x);
float   __discreteMap(float *f, float scaler, float x);

unsigned long __random();

#endif




//
//    FILE: __functions.c
//
//    fast and approximate transcendental functions
//
//    copyright (c) 2004  Robert Bristow-Johnson
//
//    [email protected]
//

#define STD_MATH_LIB 0

#include "__functions.h"

#if STD_MATH_LIB
#include "math.h"   // angle brackets don't work with SE markup
#endif




float   __sqrt(register float x)
    {
#if STD_MATH_LIB
    return (float) sqrt((double)x);
#else
    if (x > 5.877471754e-39)
        {
        register float accumulator, xPower;
        register long intPart;
        register union {float f; long i;} xBits;

        xBits.f = x;

        intPart = ((xBits.i)>>23);                  /* get biased exponent */
        intPart -= 127;                             /* unbias it */

        x = (float)(xBits.i & 0x007FFFFF);          /* mask off exponent leaving 0x800000*(mantissa - 1) */
        x *= 1.192092895507812e-07;                 /* divide by 0x800000 */

        accumulator =  1.0 + 0.49959804148061*x;
        xPower = x*x;
        accumulator += -0.12047308243453*xPower;
        xPower *= x;
        accumulator += 0.04585425015501*xPower;
        xPower *= x;
        accumulator += -0.01076564682800*xPower;

        if (intPart & 0x00000001)
            {
            accumulator *= ROOT2;                   /* an odd input exponent means an extra sqrt(2) in the output */
            }

        xBits.i = intPart >> 1;                     /* divide exponent by 2, lose LSB */
        xBits.i += 127;                             /* rebias exponent */
        xBits.i <<= 23;                             /* move biased exponent into exponent bits */

        return accumulator * xBits.f;
        }
     else
        {
        return 0.0;
        }
#endif
    }




float   __log2(register float x)
    {
#if STD_MATH_LIB
    return (float) (ONE_OVER_LN2*log((double)x));
#else
    if (x > 5.877471754e-39)
        {
        register float accumulator, xPower;
        register long intPart;

        register union {float f; long i;} xBits;

        xBits.f = x;

        intPart = ((xBits.i)>>23);                  /* get biased exponent */
        intPart -= 127;                             /* unbias it */

        x = (float)(xBits.i & 0x007FFFFF);          /* mask off exponent leaving 0x800000*(mantissa - 1) */
        x *= 1.192092895507812e-07;                 /* divide by 0x800000 */

        accumulator = 1.44254494359510*x;
        xPower = x*x;
        accumulator += -0.71814525675041*xPower;
        xPower *= x;
        accumulator += 0.45754919692582*xPower;
        xPower *= x;
        accumulator += -0.27790534462866*xPower;
        xPower *= x;
        accumulator += 0.12179791068782*xPower;
        xPower *= x;
        accumulator += -0.02584144982967*xPower;

        return accumulator + (float)intPart;
        }
     else
        {
        return -HUGE;
        }
#endif
    }


float   __exp2(register float x)
    {
#if STD_MATH_LIB
    return (float) exp(LN2*(double)x);
#else
    if (x >= -127.0)
        {
        register float accumulator, xPower;
        register union {float f; long i;} xBits;

        xBits.i = (long)(x + FLOAT_OFFSET) - LONG_OFFSET;       /* integer part */
        x -= (float)(xBits.i);                                  /* fractional part */

        accumulator = 1.0 + 0.69303212081966*x;
        xPower = x*x;
        accumulator += 0.24137976293709*xPower;
        xPower *= x;
        accumulator += 0.05203236900844*xPower;
        xPower *= x;
        accumulator += 0.01355574723481*xPower;

        xBits.i += 127;                                         /* bias integer part */
        xBits.i <<= 23;                                         /* move biased int part into exponent bits */

        return accumulator * xBits.f;
        }
     else
        {
        return 0.0;
        }
#endif
    }


float   __log(register float x)
    {
#if STD_MATH_LIB
    return (float) log((double)x);
#else
    return LN2*__log2(x);
#endif
    }

float   __exp(register float x)
    {
#if STD_MATH_LIB
    return (float) exp((double)x);
#else
    return __exp2(ONE_OVER_LN2*x);
#endif
    }

float   __pow(float x, float y)
    {
#if STD_MATH_LIB
    return (float) pow((double)x, (double)y);
#else
    return __exp2(y*__log2(x));
#endif
    }




float   __sin_pi(register float x)
    {
#if STD_MATH_LIB
    return (float) sin(PI*(double)x);
#else
    register float accumulator, xPower, xSquared;

    register long evenIntPart = ((long)(0.5*x + 1024.5) - 1024)<<1;
    x -= (float)evenIntPart;

    xSquared = x*x;
    accumulator = 3.14159265358979*x;
    xPower = xSquared*x;
    accumulator += -5.16731953364340*xPower;
    xPower *= xSquared;
    accumulator += 2.54620566822659*xPower;
    xPower *= xSquared;
    accumulator += -0.586027023087261*xPower;
    xPower *= xSquared;
    accumulator += 0.06554823491427*xPower;

    return accumulator;
#endif
    }


float   __cos_pi(register float x)
    {
#if STD_MATH_LIB
    return (float) cos(PI*(double)x);
#else
    register float accumulator, xPower, xSquared;

    register long evenIntPart = ((long)(0.5*x + 1024.5) - 1024)<<1;
    x -= (float)evenIntPart;

    xSquared = x*x;
    accumulator = 1.57079632679490*x;                       /* series for sin(PI/2*x) */
    xPower = xSquared*x;
    accumulator += -0.64596406188166*xPower;
    xPower *= xSquared;
    accumulator += 0.07969158490912*xPower;
    xPower *= xSquared;
    accumulator += -0.00467687997706*xPower;
    xPower *= xSquared;
    accumulator += 0.00015303015470*xPower;

    return 1.0 - 2.0*accumulator*accumulator;               /* cos(w) = 1 - 2*(sin(w/2))^2 */
#endif
    }


float   __sin(register float x)
    {
#if STD_MATH_LIB
    return (float) sin((double)x);
#else
    x *= ONE_OVER_PI;
    return __sin_pi(x);
#endif
    }

float   __cos(register float x)
    {
#if STD_MATH_LIB
    return (float) cos((double)x);
#else
    x *= ONE_OVER_PI;
    return __cos_pi(x);
#endif
    }

float   __tan(register float x)
    {
#if STD_MATH_LIB
    return (float) tan((double)x);
#else
    x *= ONE_OVER_PI;
    return __sin_pi(x)/__cos_pi(x);
#endif
    }




float   __atan(register float x)
    {
#if STD_MATH_LIB
    return (float) atan((double)x);
#else
    register float accumulator, xPower, xSquared, offset;

    offset = 0.0;

    if (x < -1.0)
        {
        offset = -PI_2;
        x = -1.0/x;
        }
     else if (x > 1.0)
        {
        offset = PI_2;
        x = -1.0/x;
        }
    xSquared = x*x;
    accumulator = 1.0;
    xPower = xSquared;
    accumulator += 0.33288950512027*xPower;
    xPower *= xSquared;
    accumulator += -0.08467922817644*xPower;
    xPower *= xSquared;
    accumulator += 0.03252232640125*xPower;
    xPower *= xSquared;
    accumulator += -0.00749305860992*xPower;

    return offset + x/accumulator;
#endif
    }


float   __asin(register float x)
    {
#if STD_MATH_LIB
    return (float) asin((double)x);
#else
    return __atan(x/__sqrt(1.0 - x*x));
#endif
    }

float   __acos(register float x)
    {
#if STD_MATH_LIB
    return (float) acos((double)x);
#else
    return __atan(__sqrt(1.0 - x*x)/x);
#endif
    }


float   __arg(float Imag, float Real)
    {
#if STD_MATH_LIB
    return (float) atan2((double)Imag, (double)Real);
#else
    register float accumulator, xPower, xSquared, offset, x;

    if (Imag > 0.0)
        {
        if (Imag <= -Real)
            {
            offset = PI;
            x = Imag/Real;
            }
         else if (Imag > Real)
            {
            offset = PI_2;
            x = -Real/Imag;
            }
         else
            {
            offset = 0.0;
            x = Imag/Real;
            }
        }
     else
        {
        if (Imag >= Real)
            {
            offset = -PI;
            x = Imag/Real;
            }
         else if (Imag < -Real)
            {
            offset = -PI_2;
            x = -Real/Imag;
            }
         else
            {
            offset = 0.0;
            x = Imag/Real;
            }
        }

    xSquared = x*x;
    accumulator = 1.0;
    xPower = xSquared;
    accumulator += 0.33288950512027*xPower;
    xPower *= xSquared;
    accumulator += -0.08467922817644*xPower;
    xPower *= xSquared;
    accumulator += 0.03252232640125*xPower;
    xPower *= xSquared;
    accumulator += -0.00749305860992*xPower;

    return offset + x/accumulator;
#endif
    }




float   __poly(float *a, int order, float x)
    {
    register float accumulator = 0.0, xPower;
    register int n;

    accumulator = a[0];
    xPower = x;
    for (n=1; n<=order; n++)
        {
        accumulator += a[n]*xPower;
        xPower *= x;
        }

    return accumulator;
    }


float   __map(float *f, float scaler, float x)
    {
    register long i;

    x *= scaler;

    i = (long)(x + FLOAT_OFFSET) - LONG_OFFSET;         /* round down without floor() */

    return f[i] + (f[i+1] - f[i])*(x - (float)i);       /* linear interpolate between points */
    }


float   __discreteMap(float *f, float scaler, float x)
    {
    register long i;

    x *= scaler;

    i = (long)(x + (FLOAT_OFFSET+0.5)) - LONG_OFFSET;   /* round to nearest */

    return f[i];
    }


unsigned long __random()
    {
    static unsigned long seed0 = 0x5B7A2775, seed1 = 0x80C7169F;

    seed0 += seed1;
    seed1 += seed0;

    return seed1;
    }
Robert Bristol-Johnson
źródło
czy ktoś wie, jak ten znacznik kodu działa z SE? jeśli naciśniesz „edytuj”, zobaczysz kod, który zamierzyłem, ale w tym, co tutaj przeglądamy, zostało pominiętych wiele wierszy kodu, i to nie tylko na końcu pliku. Używam odwołania do znaczników , do którego kieruje nas pomoc dotycząca znaczników SE . jeśli ktoś może to rozgryźć, edytuj odpowiedź i powiedz nam, co zrobiłeś.
Robert Bristol-Johnson
Nie wiem, co to jest @Royi.
Robert Bristol-Johnson
więc @Royi, nie mam nic przeciwko temu, że ten kod jest wysyłany do tej lokalizacji pastebin. Jeśli chcesz, możesz również opublikować ten kod, który konwertuje binarny na test dziesiętny i tekst dziesiętny na binarny . został użyty w tym samym osadzonym projekcie, w którym nie chcieliśmy stdlibtego.
Robert Bristol-Johnnson
7

Jeśli go nie widziałeś, „pierwiastek kwadratowy Quake” jest po prostu mistyczny. Wykorzystuje trochę magii na poziomie bitowym, aby dać ci bardzo dobre pierwsze przybliżenie, a następnie używa rundy lub dwóch przybliżeń Newtona, aby dokonać korekty. Może ci to pomóc, jeśli pracujesz z ograniczonymi zasobami.

https://en.wikipedia.org/wiki/Fast_inverse_square_root

http://betterexplained.com/articles/understanding-quakes-fast-inverse-square-root/

Adam Smith
źródło
6

Możesz także przybliżyć funkcję pierwiastka kwadratowego za pomocą metody Newtona . Metoda Newtona jest sposobem przybliżenia, gdzie są pierwiastki funkcji. Jest to również metoda iteracyjna , w której wynik z poprzedniej iteracji jest wykorzystywany w następnej iteracji aż do konwergencji. Równanie metody Newtona do odgadnięcia, gdzie jest pierwiastek z funkcji przy początkowym zgadywaniu x 0, jest zdefiniowane jako:f(x)x0

x1=x0-fa(x0)fa(x0)

x1(n+1)n

xn+1=xn-fa(xn)fa(xn)

zazax=zazax2)-za=0zafa(x)=x2)-zafa(x)=2)x

xn+1=xn-xn2)-za2)xn
xn+1=12)(xn+zaxn)

za1x=zaza1x2)-za=01zaza

xn+1=xn-fa(xn)fa(xn)
xn+1=xn-1(xn)2)-za-2)(xn)3)
xn+1=12)(3)xn-(xn)3)za)

Jest jednak ostrzeżenie, które powinniśmy rozważyć, patrząc na powyższe równanie. W przypadku pierwiastków kwadratowych rozwiązanie powinno być dodatnie, więc aby iteracje (i wynik) były dodatnie, należy spełnić następujący warunek:

3)xn-(xn)3)za>0
3)xn>(xn)3)a
(xn)2)za<3)

W związku z tym:

(x0)2)za<3)

x0x0x010-6

Gdy twój tag szuka algorytmu C, napiszmy go bardzo szybko:

#include <stdio.h> // For printf
#include <math.h> // For fabs
void main() 
{
   float a = 5.0; // Number we want to take the square root of
   float x = 1.0; // Initial guess
   float xprev; // Root for previous iteration
   int count; // Counter for iterations

   // Find a better initial guess
   // Half at each step until condition is satisfied
   while (x*x*a >= 3.0)
       x *= 0.5;

   printf("Initial guess: %f\n", x);

   count = 1; 
   do { 
       xprev = x; // Save for previous iteration
       printf("Iteration #%d: %f\n", count++, x);                   
       x = 0.5*(3*xprev - (xprev*xprev*xprev)*a); // Find square root of the reciprocal
   } while (fabs(x - xprev) > 1e-6); 

   x *= a; // Actual answer - Multiply by a
   printf("Square root is: %f\n", x);
   printf("Done!");
}

Jest to dość podstawowa implementacja metody Newtona. Zauważ, że ciągle zmniejszam początkowe domysły o połowę, dopóki warunek, o którym mówiliśmy wcześniej, nie jest spełniony. Próbuję również znaleźć pierwiastek kwadratowy z 5. Wiemy, że jest to mniej więcej równa 2.236. Użycie powyższego kodu daje następujące dane wyjściowe:

Initial guess: 0.500000
Iteration #1: 0.500000
Iteration #2: 0.437500
Iteration #3: 0.446899
Iteration #4: 0.447213
Square root is: 2.236068
Done!

za

Initial guess: 0.015625
Iteration #1: 0.015625
Iteration #2: 0.004601
Iteration #3: 0.006420
Iteration #4: 0.008323
Iteration #5: 0.009638
Iteration #6: 0.010036
Iteration #7: 0.010062
Square root is: 99.378067
Done!

Jak widać, jedyną różnicą jest to, ile iteracji jest wymaganych do obliczenia pierwiastka kwadratowego. Im większa liczba tego, co chcesz obliczyć, tym więcej iteracji zajmie.

Wiem, że ta metoda została już zasugerowana we wcześniejszym poście, ale pomyślałem, że wyprowadzę tę metodę i dostarczę trochę kodu!

rayryeng
źródło
2
fa(x)=1xxx
3
po prostu dla osób kodujących DSP i niektóre inne układy scalone podział ten jest szczególnie drogi, podczas gdy układy te mogą zwielokrotniać liczby tak szybko, jak mogą przenosić liczby.
robert bristow-johnson
1
@ robertbristow-johnson - i kolejny doskonały punkt. Pamiętam, kiedy pracowałem z Motorolą 6811, że mnożenie trwało kilka cykli, a podział trwał kilkaset. Nie było ładne.
rayryeng
3
ahh, dobry stary 68HC11. miał trochę rzeczy z 6809 (jak szybki mnożenie), ale więcej mikrokontrolera.
robert bristow-johnson
1
@ robertbristow-johnson - Tak, proszę pana 68HC11 :). Użyłem go do stworzenia biomedycznego systemu generowania sygnałów, który stworzył sztuczne sygnały serca do kalibracji sprzętu medycznego i szkolenia studentów medycyny. To było dawno, ale bardzo miłe wspomnienia!
rayryeng
6

x

tak, seria mocy może szybko i skutecznie aproksymować pierwiastek kwadratowy i tylko w ograniczonej dziedzinie. im szersza domena, tym więcej terminów będziesz potrzebować w swojej serii mocy, aby utrzymać błąd na wystarczająco niskim poziomie.

1x2)

x  1+za1(x-1)+za2)(x-1)2)+za3)(x-1)3)+za4(x-1)4=1+(x-1)(za1+(x-1)(za2)+(x-1)(za3)+(x-1)za4)))

gdzie

za1

za2)

za3)

za4

x=1x=2)

2)nn2)

jeśli jest zmiennoprzecinkowy, musisz oddzielić wykładnik potęgi i mantysę, tak jak robi to mój kod C w drugiej odpowiedzi.

Robert Bristol-Johnson
źródło
3

za>b

za2)+b2)0,96za+0,4b.

W granicach 4% precyzji, jeśli dobrze pamiętam. Był używany przez inżynierów, przed linijkami logarytmicznymi i kalkulatorami. Nauczyłem się tego w Notes et formules de l'ingénieur, De Laharpe , 1923

Laurent Duval
źródło