学习目标:
用c语言编写math中各种函数
绝对值
double Absolute_Value(double Number){
return Number>0?Number:-Number;
}
- 解
- 当调用absolute_Value函数时,创建double 类型的变量Number
- 将输入的参数值赋给Number
- 判断Number是否大于0,是返回Number,不是返回-Number
幂运算(整数)
- 目标
给一个底数Number,一个指数Secondary,返回Number的Secondary次方。 - 代码
double Power(double Number,int Secondary){
double PowerNumber = 1;
if(Secondary < 0){
Secondary = -Secondary;
Number = 1/Number;
}
while(Secondary--){
PowerNumber*=Number;
}
return PowerNumber;
}
- 解
- if判断,将指数变成正整数,若指数是负数,将底数取它的倒数
- while循环,指数是多少,1就乘几次底数
后面因为要多次用到幂运算,这里再把代码修改一下 一个函数将指数变成正整数,另一个执行幂运算
double Power_Start(double Number,int Secondary){
double PowerNumber = 1;
while(Secondary--){
PowerNumber*=Number;
}
return
}
double Power(double Number,int Secondary){
if(Secondary < 0){
Secondary = -Secondary;
Number = 1/Number;
}
return Power_Start(Number,Secondary);
}
开方运算(整数)
- 目标
给一个被开方数Number,一个开方次数Secondary,返回Number开Secondary次的根。
方法一:二分法
??取1到被开方数Number的一半值a作偏移值,设x0等于被开方数,看根在xn的哪边,就往哪边偏移,每次偏移后,偏移值取自己的一半,使xn不断接近根。
因为Number>1和Number<1的情况不太一样,所以当输入不同的值时,运算的方法可能不一样,这里举个实际的例子,看看是要怎么处理。
求8开3次的根x
假设x0=8,因为x0>1,设偏移值a0=(8-1)÷2=3.5, x0现在肯定是大于根x,所以x1=x0-a0=8-3.5=4.5 a1=a0÷2 d1=x13-8
如果d1>0,说明x1>x,所以x2=x1-a1 a2=a1÷2 d2=x23-8 如果d1<0,说明x1<x,所以x2=x1+a1 a2=a1÷2 d2=x23-8
同理判断,直到dn<0.000001
求0.125开3次的根x
假设x0=0.125,因为x0<1,设偏移值a0=(1-0.125)÷2 x0现在肯定是小于根x,所以x1=x0+a0 a1=a0÷2 d1=x13-0.125
如果d1>0,说明x1>x,所以x2=x1-a1 a2=a1÷2 d2=x23-8 如果d1<0,说明x1<x,所以x2=x1+a1 a2=a1÷2 d2=x23-8
同理判断,直到dn<0.000001
由上可以知道开最开始的处理会有不一样,在求近似根时是一样的。
因为开方次数Secondary和被开方数Number都有可能是负数,所以在求根之前要先把得到的数都变成正数。
如果Number<0,Secondary是2的倍数,实数内无解 如果Number<0,Secondary不是2的倍数,Number=-Number,作一个标记flag 如果Number ==0,根x ==0 如果Secondary<0,Number取其倒数,Secondary取绝对值 如果Secondary ==0,无法开根>
char Prescrption_Ready(double *Number,int Secondary,char *flag){
if(Number<=0){
if((int)Absolute_Value(*Secondary)%2==0){
return 0xff;
}else if(Number == 0){
return 0;
}else{
*Number=-*Number;
*flag=1;
}
}
if(Secondary<=0){
if(Secondary == 0){
return 0xff;
}else{
*Number=1/(*Number);
*Secondary=-*Secondary;
}
}
return 1;
}
double Prescrption(double Number,int Secondary){
double MightRoot,OffsetValue,DifferenceValue;
char flag=0;
if(Prescrption_Ready(&Number,&Secondary,&flag)!=1){
return Prescrption_Ready(&Number,&Secondary,&flag);
}
MightRoot=Number;
OffsetValue=Absolute_Value(MightRoot - 1)/2;
DifferenceValue = Number>1?1:-1;
do{
if(DifferenceValue>0){
MightRoot-=OffsetValue;
}else{
MightRoot+=OffsetValue;
}
OffsetValue/=2;
if(Number>1){
DifferenceValue = Power_Start(MightRoot,Secondary) - Number;
}else{
DifferenceValue = 1/Number - 1/Power_Start(MightRoot,Secondary);
}
}while(Absolute_Value(DifferenceValue) > 0.00001);
if(flag){
return -MightRoot;
}else{
return MightRoot;
}
}
其中当Number<1时,计算误差值计算两个幂的倒数差,可以增大精度,但这样会增加每次循环的负担,所以又加了一个倒数标记,在开方准备时,把被开方数变成大于1的数;
char Prescrption_Ready(double* Number_R,int* Secondary_R,char* Nflag_R,char* Fflag_R){
if(*Number_R<=0){
if((int)Absolute_Value(*Secondary_R)%2==0){
return 0xff;
}else if(*Number_R==0){
return 0;
}else{
*Number_R=-*Number_R;
*Nflag_R=1;
}
}
if(*Secondary_R<=0){
if(*Secondary_R==0){
return 0xff;
}else{
*Number_R = 1/(*Number_R);
*Secondary_R = -*Secondary_R;
}
}
if(*Number_R < 1){
*Number_R = 1/(*Number_R);
*Fflag_R = 1;
}
return 1;
}
double Prescrption1(double Number,int Secondary){
double MightRoot,OffsetValue,DifferenceValue;
char NegativeFlag=0,FallFlag=0;
if(Prescrption_Ready(&Number,&Secondary,&NegativeFlag,&FallFlag)!=1){
return Prescrption_Ready(&Number,&Secondary,&NegativeFlag,&FallFlag);
}
MightRoot=Number;
OffsetValue=Absolute_Value(MightRoot - 1)/2;
DifferenceValue = Number>1?1:-1;
do{
if(DifferenceValue>0){
MightRoot-=OffsetValue;
}else{
MightRoot+=OffsetValue;
}
OffsetValue/=2;
DifferenceValue = Power_Start(MightRoot,Secondary) - Number;
}while(Absolute_Value(DifferenceValue) > 0.00001);
if(NegativeFlag){
MightRoot = -MightRoot;
}
if(FallFlag){
return 1/MightRoot;
}else{
return MightRoot;
}
}
方法二:牛顿迭代法
??在方法一实现后想知道有没有更加快的求根方法,在网上看到叫“牛顿迭代法”的方式求根,所以也试着去实现。
设r是f(x)的根,选取x0作为r的初始近似值,过点(x0,f(x0))做曲线 的切线L0,L0:y=f(x0)+f’(x0) (x-x0),则L0与x轴交点的横坐标x1=x0-f(x0)/f’(x0) ,称x1为r的一次近似值。过点(x1,f(x1))做曲线y=f(x)的切线,并求该切线与x轴交点的横坐标x2=x1-f(x1)/f’(x1) ,称x2为r的二次近似值。重复以上过程,得r的近似值序列,其中,xn+1=xn-f(xn)/f’(xn)称为r的n+1次近似值,上式称为牛顿迭代公式。 牛顿迭代法
??根据牛顿迭代法的解释,求一下开方的求根公式。
求开方的值,就是在就求幂的底数,如8的3次开方,就是求几的3次方等于8,换成公式: 求y=3√x的y,就是求y=x3的x,举一个实际例子
已知被开数Number=27和开方次数Secondary=3,求根 即在y=xn中已知y=27,n=3,求x
8=x3 0=x3-27 设y=x3-27 设x0=27,过(x0,x03-27)作y的切线l1:y=k1x+b1 ∵y’=3x2 ∴k1=3x02 ∵l1过点(x0,x03-27) ∴x03-27=k1x0+b1 ?x03-27=3x02x0+b~1 ?x03-27=3x03+b1 ?b1=-2x03-27 ∴l1:y=3x02x-2x03-27 l1交x轴时,y=0,0=3x02x-2x03-27,得x1=(2x03+27)/3x02 同上:xi=(2xi-13+27)/3xi-12
通过上面的过程可以得到一个就近视根的公式,把旧的值代入公式得到的新的值会更加接近根,现在> 求一下开方求解的通用接近公式。
y=xn,当y=a时,求x a=xn 0=xn-a 设y=xn-a 设x0=a,过(x0,x0n-a)作y的切线l1:y=k1x+b1 ∵y’=nxn-1 ∴k1=nx0n-1 ∵l1过点(x0,x0n-a) ∴x0n-a=k1x0+b1 ?x0n-a=nx0n+b1 ?b1=(1-n)x0n-a ∴l1:y=nx0n-1x+(1-n)x0n-a l1交x轴时,y=0,0=nx0n-1x+(1-n)x0n - a,得x1=((n-1)x0n+a)/(nx0n-1) 同上:xi=((n - 1)xi-1n+a)/(nxi-1n-1) 化简:xi=a/(nxi-1n-1) + xi-1 - xi/n
由上可知,函数里需要有变量近视值,近似值的开方次数-1的幂,还有误差值
char Prescrption_Ready(double* Number_R,int* Secondary_R,char* Nflag_R,char* Fflag_R){
if(*Number_R<=0){
if((int)Absolute_Value(*Secondary_R)%2==0){
return 0xff;
}else if(*Number_R==0){
return 0;
}else{
*Number_R=-*Number_R;
*Nflag_R=1;
}
}
if(*Secondary_R<=0){
if(*Secondary_R==0){
return 0xff;
}else{
*Number_R = 1/(*Number_R);
*Secondary_R = -*Secondary_R;
}
}
if(*Number_R < 1){
*Number_R = 1/(*Number_R);
*Fflag_R = 1;
}
return 1;
}
double Prescrption2(double Number,int Secondary){
double MightRoot,Root_Power_1,DifferenceValue;
char NegativeFlag=0,FallFlag=0;
if(Prescrption_Ready(&Number,&Secondary,&NegativeFlag,&FallFlag)!=1){
return Prescrption_Ready(&Number,&Secondary,&NegativeFlag,&FallFlag);
}
MightRoot=Number;
Root_Power_1 = Power_Start(MightRoot,Secondary-1);
do{
MightRoot = Number/(Secondary*Root_Power_1)+MightRoot-MightRoot/Secondary;
Root_Power_1 = Power_Start(MightRoot,Secondary-1);
DifferenceValue = Root_Power_1*MightRoot - Number;
}while(Absolute_Value(DifferenceValue) > 0.00001);
if(NegativeFlag){
MightRoot = -MightRoot;
}
if(FallFlag){
return 1/MightRoot;
}else{
return MightRoot;
}
}
使用stm32时钟计时,发现当开方次数<3时,牛顿迭代法十分的快,当开方次数>3时,方法一反而会快一些,如果开方次数更大,方法二的速度会比方法一快得多,所以又写了一个函数,来切换使用方法一和方法二。
double Prescrption(double *Number,int *Secondary){
if(Absolute_Value(*Secondary) > 3 ){
if(260-Absolute_Value(*Secondary)*40.0<Absolute_Value(*Number)){
return Prescrption1(*Number,*Secondary);
}
}
return Prescrption2(*Number,*Secondary);
}
方法二比方法一慢是因为当次数过大时,使用切线不能快速接近根,要执行几百几千次才可以。 更快的求解可以在第一次MightRoot入手,目前我把它改成了MightRoot=1+(Number-1)/Power_Start(3.0,Secondary-1);
方法三:神秘常数
??有一段时间没有写,在网上看到一个新的得到开二次方后倒数的方法,他的原理不是很明白,这里就附上他的代码:
float Q_rsqrt( float number )
{
long i;
float x2, y;
const float threehalfs = 1.5F;
x2 = number * 0.5F;
y = number;
i = * ( long * ) &y;
i = 0x5f3759df - ( i >> 1 );
y = * ( float * ) &i;
y = y * ( threehalfs - ( x2 * y * y ) );
y = y * ( threehalfs - ( x2 * y * y ) );
return y;
}
幂运算(有理数)
- 目标
给一个底数Number,一个指数Secondary,返回Number的Secondary次方。
方法一:十进制
??这可能是最笨的办法了吧,但也是最简单的。 ??先提出次数的整数部分,进行整数幂运算,次数减去整数,之后把次数乘10,底数开十次根,再次提出次数的整数部分,进行整数幂运算,次数减去整数,这样不断循环,直到次数等于0或者底数被开到足够小。 ??这里举个例子:
求8的2.25次幂
设Mn为解,Sn为剩余次数,En为被开第n次的底数 M1=82=64 S1=2.25-2=0.25
E1=81/10=1.2311444133 S1=0.25*10=2.5 M2=M1*E12 S2=S1-2=0.5
这是第一次循环
E2=E11/10 S2=0.5*10=5.0 M3=M2*E25 S3=S2-5=0
因为S3=0,最后解就是S3
这个方法每次都要把底数开10次,当底数是负数时不能开根,所以不能解底数是负数的运算。
double Power_(double Number,double Secondary){
double mightRoot=1,mightRootAddEnd=Number;
unsigned int Secondarying;
unsigned char FallFlag=0;
if(Number<=0){
return 0;
}
if(Secondary==0){
return 1;
}
if(Secondary<0){
mightRoot=1/mightRoot;
Secondary=-Secondary;
FallFlag=1;
}
while(1){
Secondarying=Secondary;
mightRoot*=Power_Start(mightRootAddEnd,Secondarying);
Secondary-=Secondarying;
if(Secondary==0 || mightRootAddEnd<1.000001){
break;
}
Secondary*=10;
mightRootAddEnd=Prescrption(mightRootAddEnd,10);
}
if(FallFlag){
return 1/mightRoot;
}
else{
return mightRoot;
}
}
对数
- 目标
给一个底数BottomNum,一个真数NationsNum:,返回以BottomNum为底NationsNum的对数。
方法一:十进制
??向幂运算(有理数)时一样,先求出整数部分,求小数部分时,将底数开十次,求十分位上的值,再将底数开十次,求千分位上的值,这样循环,直到解在允许误差内。举个例子。
求log10244194304。(10242.2=4194304)
设Mn为解,Bn为底数,An为当前求的位数,En为当前求的位数上的值,N为1024的Mn次 M0=0 A1=1 E1=0 B1=1024 N=1
N=N* B1=1 * 1024=1024 N<4194304 E1=E1+1=0+1=1 N=N+B1=1024 * 1024=1048576 N<4194304 E1=E1+1=1+1=2 N=N+B1=1048576*1024=1073741824 N>4194304
N=1048576 M1=M0+A1* E1=0+1*2=2 A2=A2/10=1/10=0.1 E2=0 B2=B11/10=10241/10=2 4194304-N>0.00001
N=NB2=1048576 * 2=2097152 N<4194304 E2=E2+1=0+1=1 N=NB2=2097152* 2=4194304 N == 4194304 E2=E2+1=1+1=2 N=N*B2=4194304 * 2=8388608 N>4194304
N=4194304 M2=M0+A1* E1=2+0.1*2=2.2 A3=A2/10=0.1/10=0.01 E3=0 B3=B11/10=21/10 4194304-N<0.00001
解等于M2=2.2
double logarithm(double BottomNum,double NationsNum){
double Untion=0,UnNationsNum,NumAddEnd;
unsigned char BottomNumAdd=0;
char NegativeFlag=0;
if(NationsNum<=0 || BottomNum<=0){
return 0;
}
if(BottomNum<1){
BottomNum=1/BottomNum;
NegativeFlag=!NegativeFlag;
}
if(NationsNum<0){
NationsNum=1/NationsNum;
NegativeFlag=!NegativeFlag;
}
UnNationsNum=1;
NumAddEnd=1;
BottomNumAdd=0;
do{
while(UnNationsNum*BottomNum<=NationsNum){
UnNationsNum*=BottomNum;
BottomNumAdd+=1;
}
Untion+=BottomNumAdd*NumAddEnd;
BottomNumAdd=0;
NumAddEnd/=10;
BottomNum=Prescrption(BottomNum,10);
}while(NationsNum-UnNationsNum>0.000001);
if(NegativeFlag){
return -Untion;
}
else{
return Untion;
}
}
倒数
- 目标
给一个数Number,返回Numberd的倒数。 - 代码
double Reciprecal(double Number){
if(Number==0){
return 0;
}
else{
return 1.0 / Number;
}
}
- 解
- 当调用Reciprecal函数时,创建double 类型的变量Number
- 将输入的参数值赋给Number
- 判断Number是否为0,是返回0,不是返回1/Number
阶乘
- 目标
给一个数Number,返回Numberd的阶乘。 - 代码
unsigned long Factorial(unsigned short Number){
unsigned long Value=1;
while(Number){
Value*=Number--;
}
return Value;
}
- 解
- 当调用Factorial函数时,创建unsigned short类型的变量Number
- 将输入的参数值赋给Number
- 循环判断Number是否为0,是返回Value,不是Value再乘上Number,Number-1
排列
- 目标
给一个总数Total,取出个数Number,返回有多少种排列方法。 - 代码
unsigned int Arrangement(unsigned short Total ,unsigned short Number) {
if(Number > Total){
return 0xFFFFFFFF;
}
return Factorial(Total)/Factorial(Total-Number);
}
- 解
- 当调用Factorial函数时,创建两个unsigned short类型的变量Total和Number
- 将输入的参数值赋给Total和Number
- 判断取出个数Number是否大于总个数Total,是退出函数并返回0xFFFFFFFF
- 计算Total的阶乘和(Total-Number)的阶乘,返回它们的商
组合
- 目标
给一个总数Total,取出个数Number,返回有多少种组合方法。 - 代码
unsigned int Combination(unsigned short Total ,unsigned short Number){
if(Number > Total){
return 0xFFFFFFFF;
}
return Arrangement(Total,Number)*Factorial(Number);
}
- 解
- 当调用Factorial函数时,创建两个unsigned short类型的变量Total和Number
- 将输入的参数值赋给Total和Number
- 判断取出个数Number是否大于总个数Total,是退出函数并返回0xFFFFFFFF
- 计算Total和Number的排列数,排列数再除以Number的阶乘,返回结果
三角函数
??三角函数比较常见的有正弦函数,余弦函数,正切函数,除了这些还有余切函数,正割函数,余割函数。三角函数之间都有关系,只要能求出一个就能求出其他的。
正弦函数
- 目标
给一个弧度Number,返回sin(Numberd)的值。 - 方法:泰勒展开
利用微积分的性质,sinx的值等于sin’x与x轴在[0,x]区间组成的面积,根据这个性质可以理解柯西中值定理。
y=sinx y=sin0+(x-0)sin’x y=sin0+(x-0)[sin0+(x-0)sin’‘x] ?=sin0+xsin’0+(x-0)2sin’‘x y=sin0+xsin’0+(x-0)2 [sin0+(x-0)sin’‘‘x] ?=sin0+xsin’0+x2sin’‘0+(x-0)3sin’’‘x y=sin0+xsin’0+x2sin’‘0+(x-0)3 [sin0+(x-0)sin’‘’‘x] ?=sin0+xsin’0+x2sin’‘0+x3sin’‘‘0+(x-0)4sin’’‘‘x … … y=sin0+xsin’0+x2sin’‘0+x3sin’’‘0+x4sin’‘’'0+… ?=sin0+xcos0-x2sin0-x3cos0+x4sin0+… ?=0+x*1-x2*0-x3*1+x4*0+… ?=x1-x3+x5-x7+x9-x11+…
但这样求出来的解果离真正的结果误差比较大,由其是(x-x0)这个值比较大时,在柯西中值定理的基础上改善,推出泰勒展开式。
y=sinx y=sin0+(x-0)sin’x/1 y=sin0+(x-0)[sin0+(x-0)sin’‘x/2]/1 ?=sin0+xsin’0/1+(x-0)2sin’‘x/2/1 ?=sin0+xsin’0/1!+(x-0)2sin’‘x/2! y=sin0+xsin’0/1!+(x-0)2 [sin0+(x-0)sin’‘‘x/3]/2! ?=sin0+xsin’0/1!+x2sin’‘0/2!+(x-0)3sin’’‘x/3/2! ?=sin0+xsin’0/1!+x2sin’‘0/2!+(x-0)3sin’‘‘x/3! y=sin0+xsin’0/1!+x2sin’‘0/2!+(x-0)3 [sin0+(x-0)sin’’‘‘x/4]/3! ?=sin0+xsin’0/1!+x2sin’‘0/2!+x3sin’’‘0/3!+(x-0)4sin’‘’‘x/4/3! ?=sin0+xsin’0/1!+x2sin’‘0/2!+x3sin’‘‘0/3!+(x-0)4sin’’‘‘x/4! … … y=sin0+xsin’0/1!+x2sin’‘0/2!+x3sin’’‘0/3!+x4sin’‘’'0/4!+… ?=sin0+xcos0/1!-x2sin0/2!-x3cos0/3!+x4sin0/4!+… ?=0+x*1/1!-x2*0/2!-x3*1/3!+x4*0/4!+… ?=x1/1!-x3/3!+x5/5!-x7/7!+x9/9!-x11/11!+…
由上可知sinx的泰勒展开式就是x1/1!-x3/3!+x5/5!-x7/7!+x9/9!-x11/11!+…
#define pi 3.1415926535897932384626433832795028841971f
double Sine_Radian(double Radian){
double untie=0,taylorStore;
unsigned short taylorLever;
taylorStore = Radian;
taylorLever = 1;
Radian *= Radian*-1;
while(taylorLever<200){
untie += taylorStore;
taylorStore*=Radian;
taylorStore/=(++taylorLever);
taylorStore/=(++taylorLever);
}
return untie;
}
??弧度平时不怎么使用,都是直接使用角度表示,所以又写了一个函数
double Sine_Angle(double angle){
return Sine_Radian(pi*angle/180);
}
余弦函数
- 目标
给一个弧度Number,返回sin(Numberd)的值。
方法一:泰勒展开
和正弦函数一样,先求cosx的泰勒展开式。
y=cosx y=cos0+(x-0)cos’x/1 ?=cos0+xcos’x/1 y=cos0+x[cos0+(x-0)cos’‘x/2]/1 ?=cos0+xcos’0/1+x2cos’‘x/2/1 ?=cos0+xcos’0/1!+x2cos’‘x/2! y=cos0+xcos’0/1!+x2 [cos0+(x-0)cos’‘‘x/3]/2! ?=cos0+xcos’0/1!+x2cos0/2!+x3cos’’‘x/3! y=cos0+xcos’0/1!+x2cos0/2!+x3 [cos0+(x-0)cos’‘‘x/4]/3! ?=cos0+xcos’0/1!+x2cos0/2!+x3cos’’‘0/3!+x4cos’‘’‘x/4! … … y=cos0+xcos’0/1!+x2cos0/2!+x3cos’‘‘0/3!+x4cos’’''0/4!+… ?=cos0-x1sin0/1!-x2cos0/2!+x3sin0/3!+x4cos0/4!-x5sin0/5!-… ?=1-x2/2!+x4/4!-x6/6!+x8/8!-x10/10!+…
由上可知cosx的泰勒展开式就是1-x2/2!+x4/4!-x6/6!+x8/8!-x10/10!+…
#define pi 3.1415926535897932384626433832795028841971f
double Cosine_Radian(double Radian){
double untie=0,taylorStore;
unsigned short taylorLever;
taylorStore = 1;
taylorLever = 1;
Radian *= Radian*-1;
while(taylorLever<200){
untie += taylorStore;
taylorStore*=Radian;
taylorStore/=(taylorLever++);
taylorStore/=(taylorLever++);
}
return untie;
}
方法二:调用正弦函数
因为cos(90-x)=sinx所以可以直接用之前写好的正弦求解
#define pi 3.1415926535897932384626433832795028841971f
double Cosine_Radian(double Radian){
return Sine_Radian(pi-Radian)
}
正切函数
- 目标
给一个弧度Number,返回tan(Numberd)的值。
方法一:泰勒展开
先求tanx的泰勒展开式。
y=tanx y=tan0+(x-0)tan’x/1 ?=tan0+xtan’x/1 y=tan0+x[tan0+(x-0)tan’‘x/2]/1 ?=tan0+xtan’0/1+x2tan’‘x/2/1 ?=tan0+xtan’0/1!+x2tan’‘x/2! y=tan0+xtan’0/1!+x2 [tan0+(x-0)tan’‘‘x/3]/2! ?=tan0+xtan’0/1!+x2tan0/2!+x3tan’’‘x/3! y=tan0+xtan’0/1!+x2tan0/2!+x3 [tan0+(x-0)tan’‘‘x/4]/3! ?=tan0+xtan’0/1!+x2tan0/2!+x3tan’’‘0/3!+x4tan’‘’‘x/4! … … y=tan0+xtan’0/1!+x2tan0/2!+x3tan’‘‘0/3!+x4tan’’''0/4!+… ?=tan0 + x1sec20/1! + 2x2sec20*tan0/2! + 2x3(2sec20tan20 + sec40)/3! + ?8x4(sec20tan30+2sec40tan0)/4! +… ?=x1/1!+2x3/3!+16x6/5!+272x8/7!-7936x10/9!+…
tanx的n次求导后,将0代入的结果有点复杂,要对tanx的导数分析一下。
y=tanx y’=sec2x y’‘=2sec2xtanx y’‘’=4sec2xtan2x+2sec4x y’‘’‘=8sec2xtan3x+16sec4tanx y’‘’‘’=16sec2xtan4x+88sec4xtan2x+16sec6x …
因为将0代入后tan0=0,sec0=1。所以主要分析一下求导后每个项前的常数有什么关系。 0次求导???1 1次求导???1 2次求导???2 3次求导???4+2 4次求导???8+16 5次求导???16+88+16
??在不断求导时。发现每个secnxtannx求导时会有两个结果,一个是secnx不变,tann次数加一,另一个就secn次数加二,tann次数减一,把它简化成一张图就是这样:
??这个图有以下的规律:
double tangent_Radian(double Radian){
#define MAXLAVER 40
double untie=0;
unsigned short taylorLever=1,tempLaver;
double tangent0Value[MAXLAVER];
if(Radian>pi_2 || Radian<-pi_2){
Radian+=pi_2;
tempLaver=Radian/pi;
Radian-=tempLaver*pi;
Radian-=pi_2;
}
tangent0Value[0] = Radian;
Radian*=Radian/2;
while(taylorLever<MAXLAVER){
untie += tangent0Value[taylorLever-1];
for(tempLaver=0;tempLaver<taylorLever+1;tempLaver++){
if(tempLaver<taylorLever){
tangent0Value[tempLaver]*=2*(tempLaver+1)*Radian/taylorLever/(taylorLever*2+1);
if(tempLaver){
tangent0Value[tempLaver]+=tangent0Value[tempLaver-1]*(taylorLever-tempLaver+1);
}
}
else{
tangent0Value[tempLaver]=tangent0Value[tempLaver-1];
}
}
taylorLever++;
}
return untie;
}
??经过测试,这个公式的精度并不高,并且当度数接近90度时误差变大,就和柯西中值定理一样,(x-x0)越大,误差越大。不知道是不是公式推导错了。
方法二:调用正弦余弦函数
tanx=sinx/cosx,所以可以直接用正弦余弦函数。
double tangent_Radian(double Radian){
return Sine_Radian(Radian)/Cosine_Radian(Radian);
}
这个方法虽然有误差,但没有那么大,也不会有度数越大,误差越大的现象。
|