Tôi có nguồn lực rất hạn chế khi tôi làm việc với một vi điều khiển. Có mở rộng sê-ri taylor, bảng tra cứu chung hoặc phương pháp đệ quy không?
Tôi muốn làm một cái gì đó mà không cần sử dụng math.h's sqrt ()
Tôi có nguồn lực rất hạn chế khi tôi làm việc với một vi điều khiển. Có mở rộng sê-ri taylor, bảng tra cứu chung hoặc phương pháp đệ quy không?
Tôi muốn làm một cái gì đó mà không cần sử dụng math.h's sqrt ()
Câu trả lời:
nếu bạn muốn mở rộng chuỗi năng lượng được tối ưu hóa rẻ và bẩn (hệ số cho chuỗi Taylor hội tụ chậm) sqrt()
và một loạt các trancendentals khác, tôi đã có một số mã từ lâu. tôi đã từng bán mã này, nhưng không ai trả tiền cho tôi trong gần một thập kỷ. Vì vậy, tôi nghĩ rằng tôi sẽ phát hành nó cho tiêu dùng công cộng. tệp đặc biệt này là dành cho một ứng dụng trong đó bộ xử lý có dấu phẩy động (độ chính xác đơn IEEE-754) và chúng có trình biên dịch C và hệ thống dev, nhưng chúng khôngcó (hoặc họ không muốn liên kết) stdlib sẽ có các hàm toán học tiêu chuẩn. họ không cần độ chính xác hoàn hảo, nhưng họ muốn mọi thứ phải nhanh chóng. bạn có thể dễ dàng đảo ngược mã để xem hệ số chuỗi lũy thừa là gì và viết mã của riêng bạn. mã này giả định IEEE-754 và che dấu các bit cho mantissa và số mũ.
có vẻ như "mã đánh dấu" mà SE có không thân thiện với các ký tự góc (bạn biết ">" hoặc "<"), vì vậy bạn có thể phải nhấn "chỉnh sửa" để xem tất cả.
//
// 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
trong đó.
Nếu bạn chưa nhìn thấy nó, "Quake vuông root" chỉ đơn giản là bí ẩn. Nó sử dụng một số phép thuật cấp bit để cung cấp cho bạn một xấp xỉ đầu tiên rất tốt, và sau đó sử dụng một hoặc hai phép tính gần đúng của Newton để sửa đổi. Nó có thể giúp bạn nếu bạn đang làm việc với nguồn lực hạn chế.
https://en.wikipedia.org/wiki/Fast_inverse_sapes_root
http://betterexplained.com/articles/under Hiểu-quakes-fast-inverse-sapes-rot /
Bạn cũng có thể tính gần đúng hàm căn bậc hai bằng cách sử dụng Phương pháp của Newton . Phương pháp của Newton là một cách gần đúng nơi gốc rễ của một hàm. Nó cũng là một phương pháp lặp trong đó kết quả từ lần lặp trước được sử dụng trong lần lặp tiếp theo cho đến khi hội tụ. Phương trình của phương pháp Newton để đoán vị trí gốc của hàm với dự đoán ban đầu được định nghĩa là:
là dự đoán đầu tiên về vị trí của gốc. Chúng tôi tiếp tục tái chế phương trình và sử dụng kết quả từ các lần lặp trước cho đến khi câu trả lời không thay đổi. Nói chung, để xác định dự đoán gốc ở lần lặp , đưa ra dự đoán tại lần lặp được định nghĩa là:
Để sử dụng phương pháp của Newton để tính gần đúng cho căn bậc hai, giả sử rằng chúng ta được cung cấp một số . Như vậy, để tính căn bậc hai, chúng ta cần tính Như vậy, chúng ta tìm cách tìm một câu trả lời sao cho . Bình phương cả hai cạnh và di chuyển sang phía bên kia của phương trình mang lại . Như vậy, câu trả lời cho phương trình này là và do đó là gốc của hàm. Như vậy, hãy để là phương trình chúng ta muốn tìm gốc của. Bằng cách thay thế điều này vào phương pháp của Newton, và do đó:
Do đó, để tính căn bậc hai của , chúng ta chỉ cần tính toán phương pháp của Newton cho đến khi chúng ta hội tụ. Tuy nhiên, như được lưu ý bởi @ robertbristow-johnson, bộ phận là một hoạt động rất tốn kém - đặc biệt đối với các bộ vi điều khiển / DSP với nguồn lực hạn chế. Ngoài ra, có thể có trường hợp đoán có thể bằng 0, dẫn đến lỗi chia cho 0 do thao tác chia. Như vậy, những gì chúng ta có thể làm là sử dụng phương pháp của Newton và giải quyết cho hàm đối ứng thay vào đó, tức là . Điều này cũng tránh bất kỳ phân chia, như chúng ta sẽ thấy sau. Bình phương cả hai bên và di chuyển sang bên trái, do đó sẽ cho . Do đó, giải pháp cho vấn đề này sẽ là . Bằng cách nhân với , chúng ta sẽ có được kết quả như mong muốn. Một lần nữa, sử dụng phương pháp của Newton, do đó chúng ta có:
Tuy nhiên, có một cảnh báo mà chúng ta nên xem xét khi nhìn vào phương trình trên. Đối với căn bậc hai, giải pháp phải dương và vì vậy để các lần lặp (và kết quả) là dương, điều kiện sau phải được thỏa mãn:
3 x n > ( x n ) 3 a ( x n ) 2 a < 3
Vì thế:
Do đó, với số lượng chúng tôi muốn tính căn bậc hai, dự đoán ban đầu phải thỏa mãn điều kiện trên. Vì điều này cuối cùng sẽ được đặt trên một vi điều khiển, chúng ta có thể bắt đầu với bất kỳ giá trị nào của (giả sử 1), sau đó tiếp tục lặp và giảm giá trị của cho đến khi điều kiện trên được thỏa mãn. Lưu ý rằng tôi đã tránh thực hiện phép chia để tính trực tiếp giá trị củax 0 x 0 10 - 6nên như phân chia là một hoạt động đắt tiền. Khi chúng ta có dự đoán ban đầu, hãy lặp lại thông qua phương pháp của Newton. Lưu ý rằng tùy thuộc vào dự đoán ban đầu, có thể mất một khoảng thời gian ngắn hơn hoặc dài hơn để hội tụ. Tất cả phụ thuộc vào mức độ gần gũi của bạn với câu trả lời thực tế. Bạn có thể giới hạn số lần lặp hoặc đợi cho đến khi chênh lệch tương đối giữa hai gốc nhỏ hơn một số ngưỡng (như hoặc hơn).
Vì thẻ của bạn đang tìm kiếm một thuật toán C
, hãy viết một cách nhanh chóng:
#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!");
}
Đây là một triển khai khá cơ bản của phương pháp Newton. Lưu ý rằng tôi tiếp tục giảm một nửa dự đoán ban đầu cho đến khi điều kiện chúng ta nói trước đó được thỏa mãn. Tôi cũng đang cố gắng tìm căn bậc hai của 5. Chúng tôi biết rằng điều này gần bằng với 2.236 hoặc hơn. Sử dụng mã trên cho đầu ra sau:
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!
Lưu ý rằng phương pháp của Newton là tìm giải pháp của giải pháp đối ứng và chúng tôi nhân với kết thúc để có câu trả lời cuối cùng. Ngoài ra, hãy lưu ý rằng dự đoán ban đầu đã được thay đổi để đảm bảo rằng các tiêu chí tôi đã nói ở trên được thỏa mãn. Để giải trí, chúng ta hãy thử tìm căn bậc hai của 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!
Như bạn có thể thấy, điều duy nhất khác biệt là có bao nhiêu lần lặp được yêu cầu để tính căn bậc hai. Số lượng những gì bạn muốn tính toán càng cao, càng nhiều lần lặp lại.
Tôi biết rằng phương pháp này đã được đề xuất trong một bài viết trước đó, nhưng tôi đoán rằng tôi đã rút ra được phương pháp cũng như cung cấp một số mã!
bởi vì mã đánh dấu cho SE dường như hoạt động như shit, tôi sẽ cố gắng trả lời trực tiếp hơn, đặc biệt cho hàm .
có, một chuỗi lũy thừa có thể xấp xỉ nhanh chóng và hiệu quả với hàm căn bậc hai và chỉ trên một miền giới hạn. miền càng rộng, bạn càng cần nhiều thuật ngữ trong chuỗi sức mạnh của mình để giữ cho lỗi đủ thấp.
Ở đâu
nếu đó là điểm nổi, bạn cần tách số mũ và số mũ giống như mã C của tôi trong câu trả lời khác.
Trên thực tế, nó được thực hiện bằng cách giải phương trình bậc hai bằng phương pháp Newton:
http://en.wikipedia.org/wiki/Methods_of_computing_sapes_roots
Đối với các số lớn hơn một, bạn có thể sử dụng khai triển Taylor sau:
Trong vòng 4% chính xác, nếu tôi nhớ tốt. Nó được sử dụng bởi các kỹ sư, trước khi các máy tính và máy tính logarit. Tôi đã học nó ở Notes et formules de l'ingénieur, De Laharpe , 1923