从简易计算器到科学计算器

大兔子大兔子 提交于 2019-12-01 18:53:48

整体思路保持不变,先对表达式分析,将其转化为若干运算单元(运算单元表示一个广义运算符或者一个操作数),然后根据运算单元的列表对表达式求值。

变化点

一元运算符的处理

先前的简易计算器表示运算的运算符只有加减乘除、正负号,正负号虽然是一元运算符,但做了特殊处理(直接将紧跟其后的操作数的正负性做出相应的改变,当引入高于正负号优先级的乘方阶乘后就变为了错误的做法)。
科学计算器新引入了阶乘、sin、cos、tan、ln、log、sqrt这些一元运算符,也要求能够更好的处理一元运算符。

运算符数量大大增加

运算符数量增加,将会加大修改运算符优先级比较矩阵的难度,如果每新增支持一种新的运算就去修改这个矩阵,显然过于繁琐,因此采用了一种基于规则的比较方式。

代码量增加

随着代码量的增加,需要对项目重构。

项目结构

主要代码位于cn.zhikaizhang.algorithm和cn.zhikaizhang.main两个包下。前者是算法,后者是界面与交互。

package class description
cn.zhikaizhang.algorithm Calculator 表达式求值
cn.zhikaizhang.algorithm ExpressionIllegalException 表达式不合法异常类
cn.zhikaizhang.algorithm ExpressionParser 表达式分析器,将表达式转化为运算单元列表
cn.zhikaizhang.algorithm Operation 运算
cn.zhikaizhang.algorithm OperatorComparator 运算符优先级比较器
cn.zhikaizhang.algorithm Unit 运算单元,使用一个枚举型变量标识操作数以及每种运算符
cn.zhikaizhang.main FxCalcApplication 继承自Application,fx-Calc启动类
cn.zhikaizhang.main FxCalcLookAndFeel 控制fx-Calc的外观
cn.zhikaizhang.main FxCalcMainController 交互,界面上按钮的点击事件

运行效果

上面的有些运算是基于其他的基本运算的,如倒数利用乘方的基本运算,EE(1.0E3表示1.0乘10的三次方)使用乘法和乘方。

实现细节

Unit类

运算单元(操作数或广义的运算符),将可运算的运算符、左右括号、起止符并称为广义运算符。
type变量标识着一个运算单元的类型。可运算的运算符使用priority表示优先级,操作数使用val表示值。

1234
enum Type{ADD, SUBSTRACT, SIN, COS, TAN, LN, LOG, SQRT, MULTIPLY, DIVIDE, EE, POSITIVE, NEGATIVE, POWER, FACTORIAL, LEFT_BRACKET, RIGHT_BRACKET, START_STOP_SIGN, OPERAND};private Type type;private int priority;private double val;

可运算运算符对传入的操作数进行运算。不合法抛异常。通过可变长参数列表可以对一元和二元运算符作统一的处理。

1
public Unit (Unit ...operands) throws ExpressionIllegalException;

可运算的运算符的优先级设置。优先级设置为6的运算符作为函数进行特殊处理,后面必须紧跟着括号,由ExpressionParser处理,因此不会与其他运算冲突。

1234567891011121314151617181920212223242526272829
switch(type){	case ADD:	case SUBSTRACT:		this.priority = 1;		break;	case MULTIPLY:	case DIVIDE:	case EE:		this.priority = 2;		break;	case POSITIVE:	case NEGATIVE:		this.priority = 3;		break;	case POWER:		this.priority = 4;	 大专栏  从简易计算器到科学计算器	break;	case FACTORIAL:		this.priority = 5;		break;	case SIN:	case COS:	case TAN:	case LN:	case LOG:	case SQRT:		this.priority = 6;		break;}

OperatorComparator类

基于规则比较优先级,isOperator()表示是否广义运算符,isNormalOperator()表示是否可运算运算符,分别考虑两个都是可运算运算符以及存在左右括号、起止符的情形。
如果两个都是可运算运算符,只要前者的优先级大于等于后者的优先级,就认为前者大,唯一一个例外是前者是乘方符号,后者是负号,虽然乘方的priority大于负号,此时应该先计算负号,因此返回小于。

1234567891011121314151617181920212223242526272829303132
public class OperatorComparator {    public static char compareOperator(Unit u1, Unit u2) throws ExpressionIllegalException{        if(!(u1.isOperator() && u2.isOperator())){            throw new ExpressionIllegalException();        }        if(u1.isNormalOperator() && u2.isNormalOperator()){            if(u1.getType() == Unit.Type.POWER && u2.getType() == Unit.Type.NEGATIVE){                return '<';            }            return u1.getPriority()>=u2.getPriority()?'>':'<';        }else if(u2.getType() == Unit.Type.LEFT_BRACKET){            return '<';        }else if(u1.getType() == Unit.Type.LEFT_BRACKET){            if(u2.isNormalOperator()){                return '<';            }else if(u2.getType() == Unit.Type.RIGHT_BRACKET){                return '=';            }        }else if(u1.getType() == Unit.Type.START_STOP_SIGN){            if(u2.isNormalOperator()){                return '<';            }else if(u2.getType() == Unit.Type.START_STOP_SIGN){                return '=';            }        }else if(u1.isNormalOperator()){            return '>';        }        throw new ExpressionIllegalException();    }}

Operation类

此类中实现了所有的基本运算,为了避免double类型运算时的精度损失,如果结果不是无穷或者NaN就使用BigDecimal类型进行计算,以加法为例。

1234567891011
 * 加 */public static double add(double n1, double n2){	if(Double.isInfinite(n1 + n2) || Double.isNaN(n1 + n2)){		return n1 + n2;	}else{		return new BigDecimal(String.valueOf(n1)).add(new BigDecimal(String.valueOf(n2))).doubleValue();	}}

github

github项目主页: Calculator

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!