Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1659965
  • 博文数量: 585
  • 博客积分: 14610
  • 博客等级: 上将
  • 技术积分: 7402
  • 用 户 组: 普通用户
  • 注册时间: 2008-05-15 10:52
文章存档

2013年(5)

2012年(214)

2011年(56)

2010年(66)

2009年(44)

2008年(200)

分类: C/C++

2011-09-10 13:00:33


任意数学表达式计算

作者:南京航空航天大学能源与动力学院 



本文将从算法描述及编程思路,样例分析和注意事项等方面来阐述表达式计算的实现。

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 IsPul***ist(CString str);//- bool IsTime***ist(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.txt FILE *resultF; ///////////预处理函数,包括将大写字母转换为小写,检查表达式合法形 BOOL Cheak(CString &str);

2. 有几点需说明:

  1. 程序中需要判断符号函数的位置,需加上一标记符,故规定表达式中调用符号函数是必须加上[ ],如sin[30],exp[2+3+sin[30]]等。
  2. 对于pow和log,为了使程序具有通用行,规定pow和log的参数为两个,如根号2为pow[2,0.5], log[10,20],log[E,30]等,若输入参数为单个,程序会出现错误报警:表达式非法。
  3. 一些基本常识规则在计算表达式是必须遵守,如根号下不能出现负数,即pow[-10,0.5]是违反规则的。
  4. 程序中定义了几个基本常数,PI为3.1415926,E为2.71828。如计算ln20时,可输入log[E,20]。
  5. 程序涉及角度计算,规定用角度,如30度角正弦函数值:sin[30]
  6. 程序中函数调用时必须小写,如SIN,COS,EXP等大写方式为非法调用;变量如PI,E等一定要大写;参数X,Y,Z等务必大写。主要原因是为了防止函数名和变量,常量混合,变量在程序中是通过替换实现的。
  7. 程序中若存在符号函数嵌套,需在内层符号函数上加括号,如:pow[2,(sin[30])].对于单个参数的函数,不加括号可以,如sin[sin[30]];对于pow和log必须在嵌套函数中加括号以保证计算的正确性。
  8. 程序中很多代码看起来很怪,但是请勿随意删除,每段代码都是调试后加上去的,目的是保证程序的健壮。如减去负数2--2,在程序中要变换为2+2.等。
  9. 每次计算均生成result.txt文件存储计算过程,另外step[]用于存储计算步骤,显示计算内容,最多100步,stepNum为当前部数。
  10. 程序中定义了参数输入函数:
  11. 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一般编程问题处可以得到它,里面有相关文档,说明,使用方法以及样例程序。

阅读(713) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~