Chinaunix首页 | 论坛 | 博客
  • 博客访问: 589271
  • 博文数量: 752
  • 博客积分: 40000
  • 博客等级: 大将
  • 技术积分: 5005
  • 用 户 组: 普通用户
  • 注册时间: 2008-10-13 14:47
文章分类

全部博文(752)

文章存档

2011年(1)

2008年(751)

我的朋友

分类:

2008-10-13 16:48:38

基于表达式计算的科学计算器

作者:



  同诸多网友一样,受益于VCKBASE,觉得应为他做点贡献了,于是做了这么一个基于表达式求值的科学计算器与各位爱好编程的朋友分享。
  如您所知,这方面的程序很多,看过ZF.Yi的相关作品,也见过黄江峰的相关程序,但我觉得我的计算类有不同于二位的特色,如计算结果的有效位较长(16位);支持不严格的表达式输入(如cos(23)*sin(34)与cos(23)*sin(34与cos23*sin34等价);支持四种进制的数在一个表达式中同时出现的进制混合运算(除十进制外的各进制数不限于整数,如12d.3axh,xh是我的计算类所能识别的十六进制数的标识符);且程序做得也比较精细(如制作了鼠标键盘、窗口跟随、计算历史查看等),这才使我觉得拙作不致于滥竽充数,相信网友们看了会另有收获的。


一、简单的思路是这样的:对于用户输入的表达式,
1.将其中的括号按从里到外,从左到右的顺序找到第一对,提取其中的表达式;
2.将表达式中的所有一元计算部分编译计算出结果再转换成字串放回表达式中;
3.将其中的所有二元计算部分编译计算出结果再转换成字串放回表达式中。
4.回到第一步,除非表达式中没有括号了。


二、实现代码的一些说明:
以下是计算类中的一个主过程函数:
	  CString CCalculation::MainPro(CString strExp)
	  {
		if(strExp.IsEmpty()) return "表达式不能为空";
		Macro(&strExp);
		strExp.MakeLower(); //表达式全部小写
		/**********给表达式加上保护括号************/
		strExp.Insert(0,"(");
		strExp+=")";
		/******************************************/
		int pos=strExp.Find(" ");
		int n=BraCheck(strExp);
		CString str;
		str.Format("%d",abs(n));
		if(n==1) strExp+=")";
		else if(n==-1) strExp.Insert(0,"(");
		else if(n>0) return "缺少"+str+"个右括号";
		else if(n<0) return "缺少"+str+"个左括号";
		while(pos!=-1) //去掉表达式中的空格符
		{
			strExp.Delete(pos);
			pos=strExp.Find(" ");
		}
		Oct2Dec(&strExp); //将表达式中的八进制数转换成十进制
		Hex2Dec(&strExp); //将表达式中的十六进制数转换成十进制
		Bin2Dec(&strExp); //将表达式中的二进制数转换成十进制
		while(!IsDigital(strExp))
		{
			DelBracket(&strExp);
			if(!SynRes(&strExp)) return strExp;
		}
		if(!SynRes(&strExp)) return strExp;
		else return ModiResult(strExp);
	}

首先的Macro(&strExp)是将表达式中的所有常数符号代换为相应的字数字串;
  接着的所谓对表达式两边加上保护括号实际上是为了方便对表达式的处理,因为按照上面所讲的思路,后面的二三步其实就是一个对不含括号的表达式的计算过程,这个过程可以放到第一步里面去执行,这样整个对表达式的计算过程就成了一个不断去括号,计算括号内的表达式的过程。为整个表达式加上一对括号就使得我们可以做这样一个以上形式的循环,则最后一个循环就是对整个表达式的计算。
  在while循环过程中,SynRes(&strExp)用于检查表达式中是否有ERROE标记,有的话说明用户所输入的表达式有语法错误或是表达式的结果有错(如计算中遇到了除数为0的情况)。最后的ModiResult(strExp)中对表达式结果的一个格式化,如将结果科学记数化、提取错误信息等。
  既然是对字符串表达式求值,整个处理过程中少不了的就是字串到数值,数值到字串的相互转换,前者我主要是使用了strtod函数,后者主要是使用了_ecvt函数,这两个函数在MSDN中可以查到,这里我就不细说了。当然转换过程光用这两个函数还不够,还要有对错误表达式的一些控制过程,具体请看源代码。


三、结束语

  大概情况就是如此,具体的函数处理过程可以参见源代码,大家有什么问题或指正可以,谢谢!

四、代码更新说明

  有的网友发邮件给我指出了其中的不足之处,如没有处理好连加连减或加减号混合出现的情况(如:1++++1,1----1,--+-+-+1-+-++++---1)。我也发现了这个问题,所以重写了其中的MultiE(CString *strExp)计算函数。同时应一些网友的议建,加入了对结果的十六、八、二进制转换(以前只能在计算过程中转换)。
--------------------next---------------------

由于2005-12-20更新的代码已经重写了MultiE(CString *strExp)函数的部分代码,所以就用不着改了。 ( gamsn 发表于 2005-12-21 14:28:00)
 
目前发现了一个优先级的BUG,需要如下修改:

1.修改运算符opt2的三个初始化值:opt2[1]='/';opt2[2]='*';opt2[3]='%'; 
  这样改是使除法优先级高于乘法和取模运算,从而符合平时的习惯。

2.将calculation.cpp中的第275、276行改为:
if(ch=='-' && k>0 &&  IsDigital(strExp->Mid(k+1,1)) ) k++;
else break;
  这样改是修正提取表达式时的一个BUG。

3.将calculation.cpp中的第279行删去:即删去如下行:
if(k==pos) k=strExp->GetLength();

4.将calculation.cpp中的第255行改为:int pos=strExp->Find(opt2[i]);
即将 int pos=strExp->ReverseFind(opt2[i]);
改为 int pos=strExp->Find(opt2[i]);
  这样改使计算优先级为从左到右以符合平时计算习惯。 ( gamsn 发表于 2005-11-12 13:25:00)
 
.......................................................

--------------------next---------------------

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