整体思路
整体思路保持不变,先对表达式分析,将其转化为若干运算单元(运算单元表示一个广义运算符或者一个操作数),然后根据运算单元的列表对表达式求值。
变化点
一元运算符的处理
先前的简易计算器表示运算的运算符只有加减乘除、正负号,正负号虽然是一元运算符,但做了特殊处理(直接将紧跟其后的操作数的正负性做出相应的改变,当引入高于正负号优先级的乘方阶乘后就变为了错误的做法)。
科学计算器新引入了阶乘、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表示值。1
2
3
4enum 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 operate(Unit ...operands) throws ExpressionIllegalException;
可运算的运算符的优先级设置。优先级设置为6的运算符作为函数进行特殊处理,后面必须紧跟着括号,由ExpressionParser处理,因此不会与其他运算冲突。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29switch(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大于负号,此时应该先计算负号,因此返回小于。
1 | public class OperatorComparator { |
Operation类
此类中实现了所有的基本运算,为了避免double类型运算时的精度损失,如果结果不是无穷或者NaN就使用BigDecimal类型进行计算,以加法为例。1
2
3
4
5
6
7
8
9
10
11/**
* 加
*/
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