제곱근을 계산하기 위해 어떤 근사 기법이 있습니까?


12

마이크로 컨트롤러로 작업 할 때 리소스가 매우 제한적입니다. 테일러 시리즈 확장, 공통 조회 테이블 또는 재귀 적 접근이 있습니까?

math.h의 sqrt ()를 사용하지 않고 무언가를 선호합니다.

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


5
이 링크를 확인하십시오 : codeproject.com/Articles/69941/…
Matt L.

1
더 많은 프로그래밍 질문이라는 사실을 제외하고는 왜 대답하지 않습니까?
jojek

부동 소수점 또는 고정 소수점 입력? 고정 소수점의 경우 반복적 인 방법이 바람직 할 수 있지만 실제로 원하지 않는 한 설명하지 않아도됩니다.
Oscar

@Oscar, 펌웨어에서 플로트를 사용할 필요가 없으므로 고정 소수점 방법을 배우고 싶습니다. :).
tarabyte

답변:


13

저렴하고 더러운 최적화 된 전력 계열 확장 (테일러 계열의 계수가 느리게 수렴 됨) sqrt()과 다른 많은 초월을 원한다면 오래 전에 코드가 있습니다. 나는이 코드를 팔았지만 거의 10 년 동안 아무도 돈을 지불하지 않았다. 대중 소 비용으로 출시 할 예정입니다. 이 특정 파일은 프로세서에 부동 소수점 (IEEE-754 단 정밀도)이 있고 C 컴파일러와 dev 시스템이있는 응용 프로그램을위한 것이지만 그렇지 않았습니다.표준 수학 함수를 가지고 있었던 stdlib를 가지고 있거나 연결하고 싶지 않았습니다. 그들은 완벽한 정확성을 필요로하지 않았지만 일이 빨리되기를 원했습니다. 코드를 쉽게 역 엔지니어링하여 전력 계열 계수가 무엇인지 확인하고 자신의 코드를 작성할 수 있습니다. 이 코드는 IEEE-754를 가정하고 가수와 지수를 위해 비트를 마스킹했습니다.

SE에있는 "코드 마크 업"은 앵글 문자 ( ">"또는 "<")와 친숙하지 않으므로 "edit"를 눌러야합니다.

//
//    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 마크 업 도움말에서 안내 하는 마크 업 참조 를 사용하고 있습니다. 누구든지 알아낼 수 있으면 답변을 수정하고 수행 한 내용을 알려주십시오.
robert bristow-johnson

나는 @Royi 무엇을 몰라.
robert bristow-johnson


@Royi,이 코드가 해당 pastebin 위치에 게시되어 있다는 것은 괜찮습니다. 원한다면 바이너리를 10 진수 테스트로, 10 진수 텍스트를 바이너리로 변환하는이 코드를 게시 할 수도 있습니다 . 우리가 원하지 않는 동일한 임베디드 프로젝트에서 사용되었습니다 stdlib.
Robert bristow-johnson


6

Newton 's Method 를 사용하여 제곱근 함수를 근사화 할 수도 있습니다 . 뉴턴의 방법은 함수의 근이 어디에 있는지 근사하는 방법입니다. 또한 이전 반복의 결과가 수렴 할 때까지 다음 반복에서 사용되는 반복적 방법입니다. 초기 추측 x 0 이 주어 졌을 때 근이 함수 근을 추측하는 뉴턴의 방법에 대한 방정식 은 다음과 같이 정의됩니다.에프(엑스)엑스0

엑스1=엑스0에프(엑스0)에프'(엑스0)

은 근이 위치한 곳의 첫 번째 추측입니다. 우리는 방정식을 재활용하고 대답이 변경되지 않을 때까지 이전 반복의 결과를 사용합니다. 일반적으로, n 반복에서의 추측은 다음과 같이 정의 될때 ( n + 1 ) 반복에서의 근사 추측을 결정합니다.엑스1(+1)

엑스+1=엑스에프(엑스)에프'(엑스)

뉴턴의 방법을 사용하여 제곱근에 근사하기 위해 숫자 가 있다고 가정합니다 . 따라서, 제곱근을 계산하려면 를 계산해야합니다. 따라서, 우리는 답을 찾기 위해 추구 등 그X=. 양변을 제곱하고 방정식의 반대쪽으로a를이동하면x2a=0이됩니다. 따라서이 방정식에 대한 답은 √입니다.엑스=엑스2=0 는 따라서함수의근본입니다. 따라서f(x)=x2-a를근이 구하려는 방정식으로둡니다. 이것을 Newton의 방법으로 대치함으로써f(x)=2x이므로에프(엑스)=엑스2에프'(엑스)=2엑스

xn+1=1

엑스+1=엑스엑스22엑스
엑스+1=12(엑스+엑스)

따라서,의 제곱근 계산 , 우리는 단순히 우리가 수렴 할 때까지 뉴턴의 방법을 계산해야합니다. 그러나 @ robertbristow-johnson이 지적한 바와 같이, 부서는 특히 리소스가 제한된 마이크로 컨트롤러 / DSP의 경우 매우 비용이 많이 드는 작업입니다. 또한, 추측이 0 일 수 있으며, 이는 분할 연산으로 인해 0으로 나누기 오류를 야기 할 수있다. 따라서 우리가 할 수있는 일은 뉴턴의 방법을 사용하고 대신 역함수를 푸는 것입니다. 즉 1. 또한 나중에 볼 수 있듯이 분할을 피할 수 있습니다. 양변을 제곱하고 움직이기1엑스=위에 좌측에 따라서 제공 1입니다. 따라서 이에 대한 해결책은1입니다.1엑스2=0. 곱함으로써, 우리는 우리의 의도 한 결과를 얻을 것입니다. 다시, Newton의 방법을 사용하여1

xn+1=xn1

엑스+1=엑스에프(엑스)에프'(엑스)
엑스+1=엑스1(엑스)22(엑스)
엑스+1=12(엑스(엑스))

그러나 위의 방정식을 볼 때 고려해야 할 경고가 있습니다. 제곱근의 경우 해는 양수 여야하며 반복 (및 결과)이 양수이면 다음 조건이 충족되어야합니다.

엑스(엑스)>0
엑스>(엑스)
(엑스)2<

따라서:

(엑스0)2<

엑스0엑스0엑스0106

태그가에서 알고리즘을 찾고 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
광선, 내가 목표로하는 기능이 이라고 제안 할 수 있습니다.에프(엑스)=1엑스엑스엑스

3
DSP와 다른 칩을 코딩하는 사람들에게는 분할이 특히 비싸지 만,이 칩들은 숫자를 움직일 수있는 한 빨리 숫자를 곱할 수 있습니다.
robert bristow-johnson

1
@ robertbristow-johnson-그리고 또 다른 훌륭한 포인트. 모토로라 6811과 함께 일했을 때 곱셈에 몇 사이클이 걸렸고 나눗셈에 수백이 걸렸다는 것을 기억합니다. 예쁘지 않았습니다.
rayryeng-복원 모니카

3
아아, 좋은 ol '68HC11. 6809의 일부 (예 : 빠른 곱셈)이지만 더 많은 마이크로 컨트롤러가있었습니다.
robert bristow-johnson

1
@ robertbristow-johnson-예, 68HC11 :). 나는 그것을 사용하여 의료 기기를 교정하고 의대생을 훈련시키기 위해 인공 심장 신호를 생성하는 생체 의학 신호 생성 시스템을 만들었습니다. 오랜 시간이 지났지 만 추억을 매우 좋아합니다!
rayryeng-복원 모니카

6

SE의 코드 마크 업이 똥처럼 작동하는 것처럼 보이기 때문에 대해 더 직접적으로 답변하려고합니다.엑스

예, 전력 계열 은 제곱근 함수를 빠르고 효율적으로 근사 할 수 있으며 제한된 도메인에서만 가능합니다. 영역이 넓을수록 오차를 충분히 낮추기 위해 전력 계열에 더 많은 항이 필요합니다.

1엑스2

엑스  1+1(엑스1)+2(엑스1)2+(엑스1)+4(엑스1)4=1+(엑스1)(1+(엑스1)(2+(엑스1)(+(엑스1)4)))

어디

1

2

4

이 계수는 등식이 가되도록 수정 된 Remez 교환 알고리즘을 사용하여 결정되었습니다.엑스=1엑스=2

22

부동 소수점 인 경우 다른 답변에서 내 C 코드와 같이 지수와 가수를 분리해야합니다.



3

>

2+20.96+0.4.

기억이 잘 나면 정확도가 4 % 이내입니다. 이것은 엔지니어, 대수 눈금자 및 계산기 이전에 사용되었습니다. 나는 1923 년 노트와 드 라 잉페, 드 라하르 페 에서 배웠다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.