マイクロコントローラーを使用しているため、リソースが非常に限られています。テイラー級数展開、共通ルックアップテーブル、または再帰的アプローチはありますか?
math.hのsqrt()を使用せずに何かをしたい
マイクロコントローラーを使用しているため、リソースが非常に限られています。テイラー級数展開、共通ルックアップテーブル、または再帰的アプローチはありますか?
math.hのsqrt()を使用せずに何かをしたい
回答:
安価でダーティな最適化されたべき級数展開(テイラー級数の係数はゆっくりと収束します)sqrt()
や他の超越の束が必要な場合、私は昔のコードをいくつか持っています。私は以前このコードを販売していましたが、10年近く前から誰も私に支払いませんでした。なので、一般向けにリリースするつもりです。この特定のファイルは、プロセッサが浮動小数点(IEEE-754単精度)を有し、それらはCコンパイラおよびDEVシステムを有していたアプリケーションのためだったが、彼らはなかったではありません標準の数学関数があったはずの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;
}
stdlib
ました。
あなたがそれを見たことがないなら、「地震の平方根」は単に不可解です。それはあなたに非常に良い最初の近似を与えるためにいくつかのビットレベルの魔法を使い、それから修正するためにニュートンの近似の1つか2つを使います。限られたリソースで作業している場合に役立ちます。
https://en.wikipedia.org/wiki/Fast_inverse_square_root
http://betterexplained.com/articles/understanding-quakes-fast-inverse-square-root/
ニュートン法を使用して平方根関数を近似することもできます。ニュートン法は、関数の根がどこにあるかを概算する方法です。また、収束するまで前の反復の結果が次の反復で使用される反復法でもあります。初期推定が与えられた場合、根が関数どこであるかを推定するニュートン法の方程式は、次のように定義されます。
は、ルートが配置されている場所の最初の推測です。方程式がリサイクルされ、答えが変わらないまで、以前の反復の結果が使用されます。一般に、回の反復での根の推測を決定するには、回の反復での推測を次のように定義します。
ニュートン法を使用して平方根を近似するには、数値が与えられてます。したがって、平方根を計算するには、を計算する必要があります そのため、ような答えを見つけようとします。両側を平方し、を方程式の反対側に移動と、得られ。そのため、この方程式の答えはあり、関数のルートになります。したがって、根を求めたい方程式としましょう。これをニュートン法にすると、となり、次のようになります。
したがって、平方根を計算するには、収束するまでニュートン法を計算するだけです。ただし、@ robertbristow-johnsonが指摘しているように、除算は非常にコストのかかる操作です。特に、リソースが限られているマイクロコントローラー/ DSPの場合はそうです。また、推測が0になる場合もあり、除算演算により0除算エラーが発生する場合があります。したがって、私たちにできることは、ニュートンの方法を使用して、代わりに逆関数を解くことです。つまり、です。また、後で説明するように、これにより分割が回避されます。したがって、両側を二乗し、を左側に移動すると、ます。したがって、これに対する解決策は。を掛けること、意図した結果が得られます。ここでも、ニュートンの方法を使用して、次のようになります。
ただし、上記の方程式を検討する際に考慮すべき警告があります。平方根の場合、解は正である必要があるため、反復(および結果)が正であるためには、次の条件を満たす必要があります。
したがって:
したがって、平方根を計算する数を指定すると、初期推定は上記の条件を満たす必要があります。これは最終的にマイクロコントローラーに配置されるため、(たとえば1)の任意の値から開始し、上記の条件が満たされるまでループを続け、の値を減らします。の値を直接計算するために除算を行うのを避けたことに注意してください除算は高価な操作であるためです。最初の推測が得られたら、ニュートンの方法を繰り返します。最初の推測に応じて、収束に時間がかかる場合とかかる場合があることに注意してください。それはすべて、実際の答えにどれだけ近いかによって異なります。反復回数の上限を設定するか、2つのルート間の相対差がしきい値(など)未満になるまで待つことができます。
タグがでアルゴリズムを探している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の平方根を見つけてみましょう。
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!
ご覧のとおり、唯一異なるのは、平方根を計算するために必要な反復回数です。計算する数が多いほど、より多くの反復が必要になります。
この方法は以前の投稿ですでに提案されていることは知っていますが、私はこの方法を導出し、いくつかのコードを提供すると思いました!
SEのコードマークアップはたわごとのように機能するように思われるので、特に関数については、これに直接直接回答してみます。
はい、べき級数は平方根関数を迅速かつ効率的に近似でき、限られた領域でのみ有効です。ドメインが広いほど、誤差を十分に低く保つためにべき級数で必要となる項が多くなります。
以下のための
どこ
= 0.49959804148061
= -0.12047308243453
= 0.04585425015501
= -0.01076564682800
これらの係数は、修正されたRemez交換アルゴリズムを使用して決定され、等式はとあり、その間の最大相対誤差は最小になります。
そのため、実装が固定小数点の場合、固定小数点スキームのスケーリングを使用して、値が1と2の間になるまでビットをシフトする必要があります(シフトされたビット位置の数を数えます)。引数を右[左]にビットシフトして、引数が1〜2になるようにすると、結果は左[右]にビットシフトする必要があります。シフトビットの数が奇数の場合、追加のビットシフトは追加の乗算で補正されます。これは定数としてコードに格納できます。
浮動小数点の場合、私のCコードが他の答えで行うように、指数と仮数を分離する必要があります。
実際には、ニュートン法を使用して2次方程式を解くことによって行われます。
http://en.wikipedia.org/wiki/Methods_of_computing_square_roots
1より大きい数の場合、次のテイラー展開を使用できます。
過去に私を困惑させた1つの平方根展開は、複雑な大きさ(または長方形の対角線)の展開です。場合:
4%以内の精度、よく覚えていれば。対数定規と電卓の前に、エンジニアによって使用されました。私はそれをNotes et formules de l'ingénieur、De Laharpe、1923で学びました