Какие методы аппроксимации существуют для вычисления квадратного корня?

12

У меня очень ограниченные ресурсы, так как я работаю с микроконтроллером. Существует ли расширение ряда Тейлора, общая таблица поиска или рекурсивный подход?

Я бы предпочел сделать что-то без использования sqrt () из math.h

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

tarabyte
источник
5
Проверьте эту ссылку: codeproject.com/Articles/69941/…
Мэтт Л.
1
Кроме того, что это скорее вопрос программирования, почему бы не сделать это ответом Мэтта?
jojek
Вход с плавающей точкой или с фиксированной запятой? Для фиксированной точки предпочтительным может быть итеративный метод, но я не буду объяснять его, если вы действительно этого не хотите.
Оскар
@ Оскар, я хотел бы изучить метод с фиксированной запятой, поскольку я стараюсь не требовать использования поплавков в моей прошивке :).
Тарабайт

Ответы:

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.
Роберт Бристоу-Джонсон
@Royi, это нормально для меня, что этот код размещен в этой папке. Если вы хотите, вы также можете опубликовать этот код, который преобразует двоичный в десятичный тест и десятичный текст в двоичный . это использовалось в том же самом встроенном проекте, где мы не хотели stdlibв нем.
Роберт Бристоу-Джонсон
7

Если вы еще не видели, «Квадратный корень Quake» просто загадочный. Он использует магию на уровне битов, чтобы дать вам очень хорошее первое приближение, а затем использует раунд или два приближения Ньютона для пересмотра. Это может помочь вам, если вы работаете с ограниченными ресурсами.

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

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

Адам Смит
источник
6

f(x)x0

x1=x0f(x0)f(x0)

x1(n+1)n

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

aax=aax2a=0af(x)=x2af(x)=2x

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

a1x=aa1x2a=01a . Умножив на , мы получили бы ожидаемый результат. Опять же, используя метод Ньютона, мы имеем:a

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

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

3 x n > ( x n ) 3 a ( x n ) 2 a < 3

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

Следовательно:

(x0)2a<3

Поэтому, учитывая число, которое мы хотим вычислить для квадратного корня, исходное предположение должно удовлетворять вышеуказанному условию. Поскольку это в конечном итоге будет размещено на микроконтроллере, мы могли бы начать с любого значения (скажем, 1), затем продолжать цикл и уменьшать значение до тех пор, пока не будет выполнено вышеуказанное условие. Обратите внимание, что я избегал деления для прямого вычисления значениях 0 х 0 10 - 6x0x0x0должно быть, так как деление это дорогая операция. Как только у нас будет первоначальное предположение, повторите метод Ньютона. Обратите внимание, что в зависимости от первоначального предположения может потребоваться более короткое или более длительное время для схождения. Все зависит от того, насколько вы близки к реальному ответу. Вы можете либо ограничить количество итераций, либо подождать, пока относительная разница между двумя корнями станет меньше некоторого порога (например, или около того).106

Поскольку ваш тег ищет алгоритм 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!

Обратите внимание, что метод Ньютона находит решение взаимного решения, и мы умножаем на в конце, чтобы получить наш окончательный ответ. Также обратите внимание, что первоначальное предположение было изменено, чтобы обеспечить соответствие критериям, о которых я говорил выше. Просто для удовольствия, давайте попробуем найти квадратный корень из 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!

Как видите, отличается только то, сколько итераций требуется для вычисления квадратного корня. Чем больше число того, что вы хотите вычислить, тем больше итераций потребуется.

Я знаю, что этот метод уже был предложен в предыдущем посте, но я решил, что получу метод, а также предоставлю некоторый код!

rayryeng - Восстановить Монику
источник
2
f(x)=1xxx
3
просто для людей, кодирующих DSP и некоторые другие микросхемы, такое деление особенно дорого, тогда как эти микросхемы могут умножать числа так же быстро, как и перемещать числа.
Роберт Бристоу-Джонсон
1
@ robertbristow-johnson - и еще один замечательный момент. Я помню, когда я работал с Motorola 6811, умножение занимало несколько циклов, а деление - несколько сотен. Не был хорош
rayryeng - Восстановить Монику
3
ааа, добрый старик 68HC11. были некоторые вещи из 6809 (например, быстрое умножение), но больше микроконтроллера.
Роберт Бристоу-Джонсон,
1
@ robertbristow-johnson - Да, сэр 68HC11 :). Я использовал его для создания системы генерирования биомедицинских сигналов, которая создавала искусственные сердечные сигналы для калибровки медицинского оборудования и обучения студентов-медиков. Это было давно, но очень нежные воспоминания!
rayryeng - Восстановить Монику
6

x

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

1x2

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

где

a1

a2

a3

a4

x=1x=2

2nn2

если это с плавающей запятой, вам нужно разделить экспоненту и мантиссу, как это делает мой C-код в другом ответе.

Роберт Бристоу-Джонсон
источник
3

a>b

a2+b20.96a+0.4b.

С точностью до 4%, если я хорошо помню. Он использовался инженерами, до логарифмических линейок и калькуляторов. Я узнал об этом в « Записках и формулах», De Laharpe , 1923

Лоран Дюваль
источник