Quali tecniche di approssimazione esistono per il calcolo della radice quadrata?


12

Ho risorse molto limitate mentre lavoro con un microcontrollore. Esiste un'espansione della serie taylor, una tabella di ricerca comune o un approccio ricorsivo?

Preferirei fare qualcosa senza usare sqrt () di math.h

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



1
Tranne il fatto che è più una domanda di programmazione, perché non farne una risposta Matt?
jojek

Ingresso a virgola mobile o fissa? Per il punto fisso è preferibile un metodo iterativo, ma non mi preoccuperò di spiegarlo a meno che tu non lo voglia davvero.
Oscar

@Oscar, mi piacerebbe imparare il metodo a virgola fissa mentre provo a non richiedere l'uso di float nel mio firmware :).
Tarabyte,

Risposte:


13

se vuoi un'espansione economica e sporca della serie di potenza ottimizzata (i coefficienti per la serie di Taylor convergono lentamente) per sqrt()e un gruppo di altri trancendentali, ho un po 'di codice di molto tempo fa. vendevo questo codice, ma nessuno mi ha pagato per quasi un decennio. quindi penso che lo rilascerò per il consumo pubblico. questo particolare file era per un'applicazione in cui il processore aveva virgola mobile (precisione singola IEEE-754) e avevano un compilatore C e un sistema di sviluppo, ma nonhanno (o non volevano collegarsi) lo stdlib che avrebbe avuto le funzioni matematiche standard. non avevano bisogno di una precisione perfetta, ma volevano che le cose fossero veloci. puoi facilmente decodificare il codice per vedere quali sono i coefficienti della serie di potenze e scrivere il tuo codice. questo codice presuppone IEEE-754 e mascherato i bit per mantissa ed esponente.

sembra che il "markup del codice" che ha SE sia ostile ai caratteri angolari (sai ">" o "<"), quindi probabilmente dovrai premere "modifica" per vederlo tutto.

//
//    FILE: __functions.h
//
//    fast and approximate transcendental functions
//
//    copyright (c) 2004  Robert Bristow-Johnson
//
//    rbj@audioimagination.com
//


#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
//
//    rbj@audioimagination.com
//

#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;
    }

qualcuno sa come funziona questo markup di codice con SE? se premi "modifica" puoi vedere il codice che intendevo, ma ciò che vediamo qui ha molte righe di codice omesse, e non solo alla fine del file. sto usando il riferimento markup a cui siamo indirizzati dall'aiuto markup SE . se qualcuno riesce a capirlo, modifica la risposta e dicci cosa hai fatto.
robert bristow-johnson,

non so cosa sia @Royi.
robert bristow-johnson,


quindi @Royi, va bene per me che questo codice è pubblicato in quella posizione di pastebin. Se lo desideri, puoi anche pubblicare questo codice che converte il binario in test decimale e il testo decimale in binario . è stato utilizzato nello stesso progetto incorporato in cui non volevamo stdlib.
Robert Bristow-Johnson


6

Puoi anche approssimare la funzione radice quadrata usando il metodo di Newton . Il metodo di Newton è un modo per approssimare dove sono le radici di una funzione. È anche un metodo iterativo in cui il risultato dell'iterazione precedente viene utilizzato nell'iterazione successiva fino alla convergenza. L'equazione del metodo di Newton per indovinare dove si trova la radice di una funzione data un'ipotesi iniziale è definita come:f(x)x0

x1=x0f(x0)f(x0)

x1 è la prima ipotesi di dove si trova la radice. Continuiamo a riciclare l'equazione e ad utilizzare i risultati delle iterazioni precedenti fino a quando la risposta non cambia. In generale, per determinare la congettura della radice al iterazione, data l'ipotesi al iterazione è definito come:(n+1)n

xn+1=xnf(xn)f(xn)

Per usare il metodo di Newton per approssimare la radice quadrata, supponiamo che ci venga dato un numero . Pertanto, per calcolare la radice quadrata, dobbiamo calcolare In quanto tale, cerchiamo di trovare una risposta tale che . Quadrare entrambi i lati, e spostando verso l'altro lato delle rese equazione . Pertanto, la risposta a questa equazione è ed è quindi la radice della funzione. Come tale, lascia che sia l'equazione di cui vogliamo trovare la radice. Sostituendo questo nel metodo di Newton, , e quindi:aax=aax2a=0af(x)=x2af(x)=2x

xn+1=xnxn2a2xn
xn+1=12(xn+axn)

Pertanto, per calcolare la radice quadrata di , dobbiamo semplicemente calcolare il metodo di Newton fino a quando non convergere. Tuttavia, come notato da @ robertbristow-johnson, la divisione è un'operazione molto costosa, specialmente per i microcontrollori / DSP con risorse limitate. Inoltre, potrebbe esserci un caso in cui un'ipotesi può essere 0, il che comporterebbe un errore di divisione per 0 a causa dell'operazione di divisione. Pertanto, ciò che possiamo fare è usare il metodo di Newton e risolvere invece la funzione reciproca , ovvero . Ciò evita anche qualsiasi divisione, come vedremo più avanti. Quadrando entrambi i lati e spostando sul lato sinistro si ottiene così . Pertanto, la soluzione sarebbea1x=aa1x2a=01a . Moltiplicando per , otterremmo il risultato desiderato. Ancora una volta, usando il metodo di Newton, abbiamo quindi:a

xn+1=xnf(xn)f(xn)
xn+1=xn1(xn)2a2(xn)3
xn+1=12(3xn(xn)3a)

Tuttavia, c'è un avvertimento che dovremmo considerare quando guardiamo l'equazione sopra. Per le radici quadrate, la soluzione dovrebbe essere positiva e quindi affinché le iterazioni (e il risultato) siano positive, devono essere soddisfatte le seguenti condizioni:

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

Perciò:

(x0)2a<3

Pertanto, dato il numero di cui vogliamo calcolare la radice quadrata, la supposizione iniziale deve soddisfare la condizione di cui sopra. Dato che alla fine questo verrà posizionato su un microcontrollore, potremmo iniziare con qualsiasi valore di (diciamo 1), quindi continuare ad eseguire il looping e ridurre il valore di fino a quando la condizione di cui sopra è soddisfatta. Nota che ho evitato di fare divisione per calcolare direttamente quale sia il valore dix 0 x 0 10 - 6x0x0x0dovrebbe essere come divisione è un'operazione costosa. Una volta che abbiamo la nostra ipotesi iniziale, scorrere il metodo di Newton. Si noti che, a seconda dell'ipotesi iniziale, la convergenza potrebbe richiedere un tempo più o meno lungo. Tutto dipende da quanto sei vicino alla risposta effettiva. Puoi limitare il numero di iterazioni o attendere fino a quando la differenza relativa tra le due radici è inferiore a qualche soglia (come o giù di lì).106

Poiché il tag è alla ricerca di un algoritmo C, scriviamolo molto rapidamente:

#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!");
}

Questa è un'implementazione piuttosto basilare del metodo di Newton. Si noti che continuo a ridurre la supposizione iniziale della metà fino a quando la condizione di cui abbiamo parlato in precedenza è soddisfatta. Sto anche cercando di trovare la radice quadrata di 5. Sappiamo che questo è approssimativamente uguale a 2.236 o giù di lì. L'uso del codice sopra fornisce il seguente output:

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!

Si noti che il metodo di Newton è trovare la soluzione della soluzione reciproco e moltiplichiamo per alla fine per ottenere la nostra risposta finale. Inoltre, tieni presente che l'ipotesi iniziale è stata modificata per garantire che i criteri di cui ho parlato sopra siano soddisfatti. Solo per divertimento, proviamo a trovare la radice quadrata di 9876.a

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!

Come puoi vedere, l'unica cosa diversa è quante iterazioni sono necessarie per calcolare la radice quadrata. Maggiore è il numero di ciò che si desidera calcolare, più iterazioni ci vorranno.

So che questo metodo è già stato suggerito in un post precedente, ma ho pensato che avrei derivato il metodo e fornito un po 'di codice!


2
ray, potrei suggerire che la funzione a cui miri è invece . non sarà necessaria alcuna divisione nell'iterazione e tutto ciò che devi fare è moltiplicare il risultato con per ottenere . ecco perché vedi tutte queste cose sulla reciproca radice quadrata nelle implementazioni illuminate e nel mondo reale. xf(x)=1xxx
robert bristow-johnson,

3
è solo che, per le persone che codificano DSP e alcuni altri chip, quella divisione è particolarmente costosa, mentre questi chip possono moltiplicare i numeri più velocemente che possono spostarli.
robert bristow-johnson,

1
@ robertbristow-johnson - e un altro punto eccellente. Ricordo quando ho lavorato con il Motorola 6811 che la moltiplicazione ha richiesto alcuni cicli mentre la divisione ha richiesto diverse centinaia. Non era carino.
Rayryeng - Ripristina Monica il

3
ahh, il buon vecchio 68HC11. aveva alcune cose dal 6809 (come una rapida moltiplicazione) ma più di un microcontrollore.
robert bristow-johnson,

1
@ robertbristow-johnson - Sì signore 68HC11 :). L'ho usato per creare un sistema di generazione di segnali biomedici che creava segnali cardiaci artificiali per calibrare attrezzature mediche e formare studenti di medicina. È passato molto tempo, ma ricordi molto cari!
Rayryeng - Ripristina Monica il

6

poiché il markup del codice per SE sembra funzionare come merda, cercherò di rispondere più direttamente, in particolare per la funzione .x

sì, una serie di potenze può approssimare in modo rapido ed efficiente la funzione radice quadrata e solo su un dominio limitato. più ampio è il dominio, più termini saranno necessari nelle serie di potenze per mantenere l'errore sufficientemente basso.

per1x2

x  1+a1(x1)+a2(x1)2+a3(x1)3+a4(x1)4=1+(x1)(a1+(x1)(a2+(x1)(a3+(x1)a4)))

dove

a1 = 0.49959804148061

a2 = -0.12047308243453

a3 = 0,04585425015501

a4 = -0.01076564682800

questi coefficienti sono stati determinati usando un algoritmo di scambio Remez modificato in modo tale che l'uguaglianza sia a e e il massimo errore relativo nel mezzo sia minimizzato.x=1x=2

quindi, se l'implementazione è a virgola fissa, è necessario spostare i bit (contando il numero di posizioni di bit spostati) fino a quando i valori sono compresi tra 1 e 2 utilizzando il ridimensionamento dello schema a virgola fissa. se si sposta a destra [a sinistra] l'argomento di bit per ottenere che l'argomento sia compreso tra 1 e 2, il risultato dovrebbe essere spostato a sinistra [a destra] di bit. se il numero di bit di spostamento è dispari, lo spostamento di bit aggiuntivo viene compensato con un'ulteriore moltiplicazione di , che può essere memorizzata come costante nel codice.2nn2

se è in virgola mobile, devi separare l'esponente e la mantissa come fa il mio codice C nell'altra risposta.



3

Un'espansione di radice quadrata che mi ha confuso in passato è quella per magnitudo complessa (o diagonale in un rettangolo); se , quindi:a>b

a2+b20.96a+0.4b.

Con una precisione del 4%, se ricordo bene. È stato utilizzato dagli ingegneri, prima dei righelli e dei calcolatori logaritmici. L'ho imparato in Note et formules de l'ingénieur, De Laharpe , 1923

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.