任意数学表达式计算
本文将从算法描述及编程思路,样例分析和注意事项等方面来阐述表达式计算的实现。
1.编程思路
任何表达式可分为3类:1.简单表达式,即只带加减乘除的运算表达式,如1+2,1+3/2,1*2*3+4*6等。2.只带有函数符号不带括号的表达式,如sin[30],exp[3+4*5],sin[1+sin[2]]等。3.一般表达式,即带有括号,简单表达式以及符号函数的表达式,如(1+2+sin[30])*3,2+sin[1+(sin[30]+1)*2]等。
基本算法流程图:
于是问题归结为几个子过程:1.判断表达式中是否存在括号 2.获取最内层括号内容 3.无括号计算函数 4.给定字符串位置,用已知字符串代替原字符串内容。
步骤1,2,4分别可以用一个函数实现:
//判断表达式中是否有括号bool IsContainBracket(CString str);//若存在括号,则获得最内层括号位置void GetBracketPos(CString str,int *start,int *end);//用一段字符代替两个位置之间的内容CString ReplaceBetweenPos(CString toBeReplace,CString str,int start,int end);
步骤3较复杂,可以分解为几个子步骤,思路如下:
于是原问题转换为:1.判断表达式中是否存在符号函数,程序中支持的函数有:sin,cos,tan,asin,acos,atan,exp,log,sign,pow。2.简单表达式计算函数。3. 获得最内层符号函数类型 4. 获得符号函数的参数 5. 根据参数和符号函数类型计算结果 6.代替过程。
步骤1,3,4,5,6可以有单个函数实现,步骤2为一过程,可以分解为若干简单步骤,实现流程图如下:
具体函数如下:
///////////////////////////////////////////////////////*基础函数 南京航空航天大学 能源与动力学院 庄三少tel:13512524413 09.3.17*///由起始位置和终止位置得到表达式两者之间的内容CString GetStrFromStartAndEnd(CString str,int start,int end);//判断表达式中是否有括号bool IsContainBracket(CString str);//若存在括号,则获得最内层括号位置void GetBracketPos(CString str,int *start,int *end);//用一段字符代替两个位置之间的内容CString ReplaceBetweenPos(CString toBeReplace,CString str,int start,int end);//判断表达式中是否含有符号函数bool IsContainSign(CString str);//获得最内层符号函数类型,即符号函数中不存在符号函数,分别返回函数名的位置和参数扩号[]的位置int GetSignStyle(CString str,int *sing_s,int *sign_e,int *p_s,int *p_e);//获得参数表达式CString GetParmString(CString str,int start,int end);//如果是双参数的话,则分别得到每个参数的表达式void GetParmTwo(CString str,int start,int end,CString *s1,CString *s2);//判断是否存在加减乘除bool IsJJCC(CString str); bool IsAddExist(CString str);//+bool IsPulsExist(CString str);//-bool IsTimesExist(CString str);//*bool IsDivideExist(CString str);//chu//达到加减乘除的两个参数void GetJJCCTwoParm(CString str,int pos,CString *s1,CString *s2,int *p_start,int *p_end);//若存在加减乘除的话,先判断符号类型,再获得加减乘除号的位置,输入具体体判别类型,将获得该符号从左向右的第一个位置//调用判别函数后再调用该函数int GetJJCCPos(CString str,CString style);//得到加减乘除号的数目int GetJJCCNum(CString str,CString name);///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////************************************************************************//* 中间函数 *//************************************************************************/////计算无符号函数,无括号表达式的值void GetSingleValue(CString str,double &result);//计算含有符号函数的,无括号的表达式void GetSignFuncVal(CString str,double &result);//计算括号内容void GetBracketResult(CString str,double &result);////////计算步骤及结果存储为文件 result.txtFILE *resultF;///////////预处理函数,包括将大写字母转换为小写,检查表达式合法形BOOL Cheak(CString &str);
2. 有几点需说明:
- 程序中需要判断符号函数的位置,需加上一标记符,故规定表达式中调用符号函数是必须加上[ ],如sin[30],exp[2+3+sin[30]]等。
- 对于pow和log,为了使程序具有通用行,规定pow和log的参数为两个,如根号2为pow[2,0.5], log[10,20],log[E,30]等,若输入参数为单个,程序会出现错误报警:表达式非法。
- 一些基本常识规则在计算表达式是必须遵守,如根号下不能出现负数,即pow[-10,0.5]是违反规则的。
- 程序中定义了几个基本常数,PI为3.1415926,E为2.71828。如计算ln20时,可输入log[E,20]。
- 程序涉及角度计算,规定用角度,如30度角正弦函数值:sin[30]
- 程序中函数调用时必须小写,如SIN,COS,EXP等大写方式为非法调用;变量如PI,E等一定要大写;参数X,Y,Z等务必大写。主要原因是为了防止函数名和变量,常量混合,变量在程序中是通过替换实现的。
- 程序中若存在符号函数嵌套,需在内层符号函数上加括号,如:pow[2,(sin[30])].对于单个参数的函数,不加括号可以,如sin[sin[30]];对于pow和log必须在嵌套函数中加括号以保证计算的正确性。
- 程序中很多代码看起来很怪,但是请勿随意删除,每段代码都是调试后加上去的,目的是保证程序的健壮。如减去负数2--2,在程序中要变换为2+2.等。
- 每次计算均生成result.txt文件存储计算过程,另外step[]用于存储计算步骤,显示计算内容,最多100步,stepNum为当前部数。
- 程序中定义了参数输入函数:
double GetResultFrString1(CString str,CString parm,double val);//一个未知参数double GetResultFrString2(CString str,CString parm1,double val1,CString parm2,double val2);//两个未知参数double GetResultFrStringN(CString str,CString parm[],double val[],int N);//N个未知
参数
如要计算sin[2*X+30]在1到10的值,实现方法为:For (int i=1;i<10;i++){ Val= GetResultFrString1(“sin[2*X+30]”,”X”,double(i));}
如要计算sin[Y*X+30]在X为1到10,Y为20时的值实现方法为:
For (int i=1;i<10;i++){ Val= GetResultFrString1(“sin[Y*X+30]”,”X”,double(i),”Y”,20);}
3.样例分析
3.1例一:绘制任意函数的波形
结合本人编写的DataShow类,用CExpress类计算表达式值,用DataShow显示绘图结果。绘图用点越多,绘图越精确,但时间较长,本例子中画100个点,程序运行结果如下:
函数为:100*pow[2,(sin[X])],(注意:符号函数嵌套符号函数需加括号)
函数:100*sign[sin[X]] 注:sign为符号函数,参数为正时1,为负时得-1,为零时得0.
绘图结果如下:
函数:200*sin[X+30]+100*sin[X/2.0] 绘图结果:
3.2样例二:数值计算器
输入表达式可计算结果;
运行效果如下:
用matlab验证计算结果:
pow[(log[2,3+2]*2*log[2,3+2]*2),0.5]
程序计算值:4.64386 matlab计算值:(log2(3+2)*2*log2(3+2)*2)^0.5 ans =4.6439
1+2+3/4+sin[30]+log[10,100]
程序计算值:6.25
matlab计算值:1+2+3/4+sin(30*3.1415/180.0)+log10(100) ans =6.2500
(注意:matlab中使用弧度)
为了检验程序的正确形使用6层嵌套计算结果并同matlab计算结果比较
matlab计算值
exp(sin(exp(sin(exp(sin(30*3.1415/180.0))*3.1415/180.0))*3.1415/180.0)) ans= 1.0181
(注意:matlab中使用弧度)
4. 关于CExpress的几点说明
以前上C++课时,老师讲过可以用堆栈的方法实现表达式计算,但是那时候没好好学,所以对堆栈一点也不懂。CExpress纯粹是表达式分析,实现所有功能使用的时CString类,靠的是它的成员函数如:Find, Delete,GetAT,SetAT。不存在什么堆栈,链表等问题。所以在效率上有不如堆栈方法等高,但是很实用。比较满意的一点是能实现一些复杂表达式的计算和函数计算,如:
exp[(56-(((((((sin[(1+29)]+3+2.5)+4)+sin[30]+4.5)+6)+7)+8)+9)-10)]+56-sin[30]+4.5
计算结果:62.7183
网上估计也有一些表达式计算,但是到目前为止还未找到令人兴奋的代码。我用了两天从算法分析,程序设计,调试BUG,文章写作总算顺利。一直犹豫要不要公布代码(包括自己很辛苦写的DataShow类),自己学习的时候从网上得到了很多东西。自己写的东西若对大家有用,那是编程乐趣所在,顾决定公布代码。
若你发现Bug请联系我;若你想获得代码(当然在VCKBASE上能得到)请联系我;若你想和我交流,请联系我。
联系方式:TEL:13512524413,mail:visualsan@yahoo.cn
南京航空航天大学能源与动力学院 庄三少
5.关于DataShow的说明:
DataShow是本人编写的一个数据显示类,可以显示X,Y轴的数据,用它基本上可以实现一般的数据显示,在VCKBASE的代码下载处的C++MFC一般编程问题处可以得到它,里面有相关文档,说明,使用方法以及样例程序。