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

整体思路

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

变化点

一元运算符的处理

先前的简易计算器表示运算的运算符只有加减乘除、正负号,正负号虽然是一元运算符,但做了特殊处理(直接将紧跟其后的操作数的正负性做出相应的改变,当引入高于正负号优先级的乘方阶乘后就变为了错误的做法)。
科学计算器新引入了阶乘、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
4
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 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
29
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大于负号,此时应该先计算负号,因此返回小于。

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
29
30
31
32
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类型进行计算,以加法为例。

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