IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 写一个math.c -> 正文阅读

[C++知识库]写一个math.c

学习目标:

用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就乘几次底数

后面因为要多次用到幂运算,这里再把代码修改一下
一个函数将指数变成正整数,另一个执行幂运算

//{Number>=0|Number∈R} {Secondary>0|Secondary∈N+}
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;
}

/*开方
MightRoot:		可能根
OffsetValue:	偏移值
DifferenceValue:偏差值
flag:         	标记符号
*/
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);			//0.00001是允许的最小误差
    
    if(flag){
        return -MightRoot;
    }else{
        return MightRoot;
    }
} 

其中当Number<1时,计算误差值计算两个幂的倒数差,可以增大精度,但这样会增加每次循环的负担,所以又加了一个倒数标记,在开方准备时,把被开方数变成大于1的数;

/*开方准备
Number_R     --->  Number_R     >0
Secondary_R  --->  Secondary_R  >0    &&    Nflag_R记符号   Fflag_R记是否是倒数
*/
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;
}

/*开方*方法一
MightRoot:		可能根
OffsetValue:	偏移值
DifferenceValue:偏差值
NegativeFlag:   标记符号
FallFlag:       标记倒数
*/
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);			//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的幂,还有误差值

  • 代码
/*开方准备
Number_R     --->  Number_R     >0
Secondary_R  --->  Secondary_R  >0    &&    Nflag_R记符号   Fflag_R记是否是倒数
*/
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;
}

/*开方*方法二
MightRoot:		可能根
Root_Power_1:	可能根的开方次数-1的幂
DifferenceValue:偏差值
flag:         	标记符号
*/
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;

		//近视根的开方次数-1的次方
        Root_Power_1 = Power_Start(MightRoot,Secondary-1);

		//这次的偏差值
        DifferenceValue = Root_Power_1*MightRoot - Number;
    }while(Absolute_Value(DifferenceValue) > 0.00001);         //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次,当底数是负数时不能开根,所以不能解底数是负数的运算。

  • 代码
/*幂运算(有理数)
方法一:十进制

Number          底数
Secondary       次数/剩余的次数

mightRoot       可能解
mightRootAddEnd 开n次后的底数
Secondarying    次数的整数部分
FallFlag:       标记倒数
*/
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);//底数开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=N
B2=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

  • 代码
    因为每次底数要开十次,底数不能为负数。
/*对数
BottomNum:      底数
NationsNum:    真数

BottomNumAdd:   对应小数位数
NumAddEnd:      对应小数位上的值
UnNationsNum:  解的真数
Untion:         解

NegativeFlag:   标记符号
*/
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;
    }

    //将底数变成大于1的数
    if(BottomNum<1){
        BottomNum=1/BottomNum;
        NegativeFlag=!NegativeFlag;
    }

    //将真数变成大于1的数
    if(NationsNum<0){
        NationsNum=1/NationsNum;
        NegativeFlag=!NegativeFlag;
    }

    //开始求解准备
    UnNationsNum=1;             //求解时的作比较值
    NumAddEnd=1;                //指向解的第几位,从个位开始1-->0.1-->0.01
    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
/*正弦函数(弧度)
方法一:泰勒级数求值

Radian:      弧度(弧度=π*角度/180)  0~π/2
taylorLever: 泰勒级数
tayloritem:  泰勒项
untie:       解
*/
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;
}

??弧度平时不怎么使用,都是直接使用角度表示,所以又写了一个函数

/*正弦函数(角度)
方法一:泰勒级数
angle:      角度(弧度=π*角度/180)
*/
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
/*余弦函数(弧度)
方法一:泰勒展开求解

Radian:      弧度(弧度=π*角度/180)  0~π/2
taylorLever: 泰勒级数
tayloritem:  泰勒项
untie:       解
*/
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
/*余弦函数(弧度)
angle:      角度(弧度=π*角度/180)*/
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次数减一,把它简化成一张图就是这样:

??这个图有以下的规律:

  • 代码
/*正切函数(弧度)
方法一:泰勒展开求解

taylorLever:        泰勒级数
tempLaver:          临时级数
taylorStore:        泰勒项
taylorBase:         泰勒项的基值
tangent0Value:      泰勒项tan0的Lever次求导后每个项的常数
untie:              解
*/
double tangent_Radian(double Radian){
#define MAXLAVER 40
    double untie=0;
    unsigned short  taylorLever=1,tempLaver;
    // double taylorStore;
    double tangent0Value[MAXLAVER];

    //调整Radian的值到90°~-90°
    if(Radian>pi_2 || Radian<-pi_2){
        Radian+=pi_2;
        tempLaver=Radian/pi;    //调整Radian的值,暂时借用tempLaver变量
        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);
}

这个方法虽然有误差,但没有那么大,也不会有度数越大,误差越大的现象。

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-09-04 00:51:43  更:2022-09-04 00:54:03 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/19 10:35:21-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码