Які методи наближення існують для обчислення квадратного кореня?


12

У мене дуже обмежені ресурси, оскільки я працюю з мікроконтролером. Чи є розширення серії Taylor, загальна таблиця пошуку чи рекурсивний підхід?

Я вважаю за краще щось робити, не використовуючи sqh math.h's ()

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


5
Перевірте це посилання: codeproject.com/Articles/69941/…
Matt L.

1
За винятком того, що це більше питання програмування, чому б не зробити так, щоб відповісти Метту?
jojek

Вхід з плаваючою або фіксованою точкою? Для фіксованої точки може бути кращим ітераційний метод, але я не буду намагатися пояснювати це, якщо ви цього не хочете.
Оскар

@Oscar, я хотів би вивчити метод з фіксованою точкою, оскільки я намагаюся не вимагати використання поплавків у своїй прошивці :).
тарабайт

Відповіді:


13

якщо ви хочете дешевого і брудного оптимізованого розширення серій електроенергії (коефіцієнти для серії Тейлора сходяться повільно) для sqrt()та ще безліч інших трансценденталій, у мене є давній код. я раніше продавав цей код, але ніхто не платив мені за це майже десятиліття. тому я думаю, що випущу це для суспільного споживання. цей конкретний файл був для програми, де процесор мав плаваючу крапку (IEEE-754 з одною точністю) і у них був компілятор C і система розробників, але вони немати (або вони не хотіли з'єднуватися) stdlib, який мав би стандартні математичні функції. їм не потрібна була ідеальна точність, але вони хотіли, щоб справи були швидкими. Ви можете досить легко змінити інженерний код, щоб побачити, що таке коефіцієнти ряду потужностей, і написати свій власний код. цей код передбачає IEEE-754 і маскується від бітів для мантіси та експонента.

виявляється, що "розмітка коду", яку має SE, є недружньою з кутовими символами (ви знаєте ">" або "<"), тому вам, мабуть, доведеться натиснути "редагувати", щоб побачити все це.

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

хтось знає, як ця розмітка коду працює з SE? Якщо ви натиснете "редагувати", ви можете побачити код, який я мав намір, але те, що ми бачимо тут, у багатьох рядках коду пропущено, і не тільки в кінці файлу. Я використовую посилання на розмітку, на яке ми направляємо допомогою SE про розмітку . якщо хтось може це зрозуміти, будь ласка, відредагуйте відповідь та скажіть нам, що ви зробили.
Роберт Брістоу-Джонсон

я не знаю, що це @ Рой.
Роберт Брістоу-Джонсон


так що @ Royi, це добре, що цей код розміщено в цьому пастінговому місці. Якщо ви цього хотіли, ви також можете опублікувати цей код, який перетворює двійковий тест у десятковий і десятковий текст у двійковий . він використовувався в тому ж вбудованому проекті, де ми цього не хотіли stdlib.
Роберт Брістоу-Джонсон

7

Якщо ви цього ще не бачили, "квадратний корінь Quake" просто містифікує. Він використовує деяку магію бітового рівня, щоб дати вам дуже хороше спочатку наближення, а потім використовує круглий або два наближення Ньютона для перегляду. Це може допомогти вам, якщо ви працюєте з обмеженими ресурсами.

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

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


6

Ви також можете наблизити функцію квадратного кореня, використовуючи метод Ньютона . Метод Ньютона - це спосіб наближення того, де є коріння функції. Це також ітеративний метод, коли результат попередньої ітерації використовується в наступній ітерації до конвергенції. Рівняння методу Ньютона здогадатися, де корінь є функцією задана початковою здогадкою x 0 , визначається як:f(х)х0

х1=х0-f(х0)f'(х0)

- перша здогадка про те, де знаходиться корінь. Ми продовжуємо переробляти рівняння і використовуємо результати попередніх ітерацій, поки відповідь не зміниться. Загалом, для визначення здогаду кореня на ( n + 1 ) ітерації, враховуючи здогадку на n ітерації, визначають як:х1(н+1)н

хн+1=хн-f(хн)f'(хн)

Щоб використовувати метод Ньютона для наближення до квадратного кореня, припустимо, що нам дано число . Таким чином, для обчислення квадратного кореня нам потрібно обчислити а Як такий, ми прагнемо знайти відповідь таку, щоx=а . Square обидві сторони, і переміщеннядо іншої сторони виходів рівняннях2-а=0. Відповідно до цього рівняння єх=аах2-а=0 і, таким чином, єкоренемфункції. Нехайf(x)=x2-aє рівнянням, в якому ми хочемо знайти корінь. Підставивши це методом Ньютона,f(x)=2x, а отже:аf(х)=х2-аf'(х)=2х

xn+1=1

хн+1=хн-хн2-а2хн
хн+1=12(хн+ахн)

Тому, щоб обчислити квадратний корінь , нам просто потрібно обчислити метод Ньютона, поки ми не сходимося. Однак, як зазначає @ robertbristow-johnson, поділ - це дуже дорога операція - особливо для мікроконтролерів / DSP з обмеженими ресурсами. Крім того, може бути випадок, коли здогадка може бути 0, що призведе до помилки поділу на 0 через операцію ділення. Таким чином, ми можемо використовувати метод Ньютона і вирішити замість нього взаємну функцію, тобто 1а . Це також дозволяє уникнути будь-якого поділу, як ми побачимо пізніше. Сквернувши обидві сторони і перемістившись1х=адо лівої сторони рукитаким чиномдає 1а. Тому рішення цього було б11х2-а=0 . Помноживши наа, ми отримали намічений результат. Знову ж таки, використовуючи метод Ньютона, ми маємо:1аа

xn+1=xn-1

хн+1=хн-f(хн)f'(хн)
хн+1=хн-1(хн)2-а-2(хн)3
хн+1=12(3хн-(хн)3а)

Однак є попередження, яке нам слід враховувати, переглядаючи вищевказане рівняння. Для квадратних коренів рішення має бути позитивним, і для того, щоб ітерації (і результат) були позитивними, необхідно виконати наступну умову:

3хн-(хн)3а>0
3хн>(хн)3а
(хн)2а<3

Тому:

(х0)2а<3

х0х0х010-6

Оскільки ваш тег шукає алгоритм C, давайте запишемо його дуже швидко:

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

Це досить основна реалізація методу Ньютона. Зауважте, що я продовжую зменшувати початкові здогадки вдвічі, поки не буде задоволено умову, про яку ми говорили раніше. Я також намагаюся знайти квадратний корінь 5. Ми знаємо, що це приблизно дорівнює 2,236 або близько того. Використання вищевказаного коду дає такий вихід:

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!

а

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!

Як бачите, єдине, що відрізняється - це скільки ітерацій потрібно для обчислення квадратного кореня. Чим більша кількість того, що ви хочете обчислити, тим більше ітерацій буде потрібно.

Я знаю, що цей метод був запропонований у попередньому дописі, але я зрозумів, що я отримаю метод, а також надам код!


2
f(х)=1ххх

3
це просто те, що для людей, що кодують DSP та деякі інші мікросхеми, цей поділ є особливо дорогим, тоді як ці чіпи можуть примножувати числа так само швидко, як вони можуть переміщувати числа.
Роберт Брістоу-Джонсон

1
@ robertbristow-johnson - і ще один чудовий момент. Я пам’ятаю ще, коли я працював з Motorola 6811, що множення займало кілька циклів, а ділення займало кілька сотень. Не був гарний.
rayryeng

3
ах, добрий ol '68HC11. мав деякі речі з 6809 (як швидке множення), але більше мікроконтролера.
Роберт Брістоу-Джонсон

1
@ robertbristow-johnson - Так, сер, 68HC11 :). Я використовував це для створення системи генерування біомедичних сигналів, яка створювала штучні серцеві сигнали для калібрування медичного обладнання та підготовки студентів-медиків. Це було давно, але дуже люблять спогади!
rayryeng

6

х

так, серія потужностей може швидко та ефективно наближати функцію квадратного кореня та лише на обмеженій області. чим ширший домен, тим більше термінів вам знадобиться у вашій серії потужностей, щоб помилка була достатньо низькою.

1х2

х  1+а1(х-1)+а2(х-1)2+а3(х-1)3+а4(х-1)4=1+(х-1)(а1+(х-1)(а2+(х-1)(а3+(х-1)а4)))

де

а1

а2

а3

а4

х=1х=2

2нн2

якщо це плаваюча точка, вам потрібно відокремити показник і мантісу, як це робить мій код С в іншій відповіді.



3

а>б

а2+б20,96а+0,4б.

У межах 4% точності, якщо я добре пам’ятаю. Його використовували інженери, перед логарифмічними лінійками та калькуляторами. Я дізнався про це у Notes et formules de l'ingénieur, De Laharpe , 1923

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.