javafx实现简易计算器

javafx?

由Sun公司发布于2008年12月05日,多用于编写窗体程序。界面由xml文件设计完成,在java源码中使用注解获取xml文件定义的控件以及定义事件。相较于java swing提供了更多控件并且更易于使用。

运行效果

实现原理

整体思路

这是一个经典的用栈求解表达式的问题。这里将操作数和运算符统称为运算单元,并且会在表达式的两端分别添加一个#,表示表达式的开始与结束。首先建立操作数以及运算符两个栈,按照表达式的顺序,取一个运算单元,如果取到的是一个操作数,将其入栈并取下一个运算单元;如果取到的是一个运算符,将其与运算符栈的栈顶运算符的优先级进行比较,若栈顶运算符的优先级较低,将新运算符入栈并取下一个运算单元,若优先级相等,将栈顶运算符退栈并取下一个运算单元,若栈顶运算符的优先级较高,从操作数栈退栈两个数分别作为操作数2和操作数1,从运算符栈退栈一个运算符,对两个操作数作该运算,并入栈操作数栈。最终的运算结果就是操作数栈的栈顶元素。
表达式优先级表如下所示。叹号表示绝对不可能出现的对比,出现则可以认为表达式有误,倒数第三行和最后一行的叹号说明发生了小括号不匹配,而倒数第二行则是因为右括号绝对不可能入栈。

op1/op2 + - × ÷ ( ) #
+ > > < < < > >
- > > < < < > >
× > > > > < > >
÷ > > > > < > >
( < < < < < = !
) ! ! ! ! ! ! !
# < < < < < ! =

具体实现

1.定义Unit类表示运算单元

运算单元,表示一个操作数或者一个运算符。

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

public class Unit {

private UnitType unitType;

private char op;

private double val;

public Unit() {
}

public Unit(char op) {
this.unitType = UnitType.OPERATOR;
this.op = op;
}

public Unit(double val) {
this.unitType = UnitType.OPERAND;
this.val = val;
}

enum UnitType{OPERATOR, OPERAND};

public Unit operate(Unit operand1, Unit operand2) {
if(unitType != UnitType.OPERATOR || operand1.unitType != UnitType.OPERAND || operand2.unitType != UnitType.OPERAND){
return null;
}
if(op == '+'){
return new Unit(operand1.val + operand2.val);
}else if(op == '-'){
return new Unit(operand1.val - operand2.val);
}else if(op == '×' || op == '*') {
return new Unit(operand1.val * operand2.val);
}else {
return new Unit(operand1.val / operand2.val);
}
}

@Override
public String toString() {

if(unitType == UnitType.OPERATOR){
return "运算符 " + op + " ";
}else{
return "操作数 " + val + " ";
}
}
}

2.表达式转为运算单元的列表

之所以进行这一步,是因为操作数占得字符长度一般大于1,而且需要考虑可能存在的小数点以及正负号。在转运算单元时还考虑到了几种特殊的操作数,由非0数除以0得到的无穷,以及未定义结果NaN(比如0/0,正无穷加负无穷,结果均为NaN)。如果判断出表达式错误将抛出自定义异常。

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
private static List<Unit> parseUnits(String s) throws ExpressionIllegalException{

List<Unit> units = new ArrayList<Unit>();

/**
* 当前操作数前面是否有负号
* 若一个Unit是操作数,且hasSign为true,该操作数的值乘-1
*/

boolean hasSign = false;

/**
* 若一个Unit是乘号或除号或左括号,flag置为true,否则置为false
* 若一个Unit是加号,且flag为true,说明该加号表示正号
* 若一个Unit是减号,且flag为true,说明该减号表示负号,hasSign置为true
* flag初始值为true,表示字符串开头的加减号一定为正负号
*/

boolean flag = true;

for(int i = 0; i < s.length(); i++){

if(s.charAt(i) == '.'){
throw new ExpressionIllegalException();
}
/**
* 操作数,∞无穷,用于处理上次运算结果为无穷然后继续运算的情形
*/

else if(s.charAt(i) == '∞'){
units.add(new Unit(hasSign?Double.NEGATIVE_INFINITY:Double.POSITIVE_INFINITY));
hasSign = false;
flag = false;
}
/**
* 操作数,NaN,表示未定义的运算结果,如0/0
*/

else if(s.charAt(i) == 'N'){
units.add(new Unit(Double.NaN));
hasSign = false;
flag = false;
i += 2;
}
/**
* 添加表示操作数的Unit
*/

else if(s.charAt(i) >= '0' && s.charAt(i) <= '9'){

double val = s.charAt(i) - '0';
boolean hasPoint = false; //是否出现小数点
double level = 0.1; //小数部分的单位
int k;
for(k = i+1; k < s.length(); k++){
if(s.charAt(k) >= '0' && s.charAt(k) <= '9'){
if(hasPoint){
val = val + (s.charAt(k) - '0') * level;
level *= 0.1;
}else{
val = val * 10 + (s.charAt(k) - '0');
}
}else if(s.charAt(k) == '.'){
if(hasPoint){
throw new ExpressionIllegalException();
}
hasPoint = true;
}else{
break;
}
}
i = k-1;
units.add(new Unit(hasSign?-val:val));
hasSign = false;
flag = false;
}
/**
* 正负号或者加减乘除号或者括号
* 若是正号,置flag为false
* 若是负号,置hasSign为true,置flag为false
* 不是正负号,就添加一个表示运算符的Unit
*/

else{
if(s.charAt(i) == '-' && flag){
hasSign = true;
flag = false;
}else if(s.charAt(i) == '+' && flag){
flag = false;
}else{
char c = s.charAt(i);
units.add(new Unit(c));
flag = (c == '*' || c == '/' || c == '×' || c == '÷' || c == '(');
}
}
}

return units;
}

3.计算

运算符优先级的比较。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 根据优先级矩阵返回两个运算符优先级比较的结果
* !表示不可能出现比较这两个运算符的情形,可认为输入有误
* 乘法接受*或×两种符号
* 除法接受/或÷两种符号
*/

public static char compareOperator(char op1, char op2){

Map<Character, Integer> map = new HashMap<Character, Integer>();
map.put('+', 0);
map.put('-', 1);
map.put('×', 2);
map.put('*', 2);
map.put('÷', 3);
map.put('/', 3);
map.put('(', 4);
map.put(')', 5);
map.put('#', 6);
String[] compare = new String[]{">><<<>>", ">><<<>>", ">>>><>>", ">>>><>>", "<<<<<=!", "!!!!!!!", "<<<<<!="};
return compare[map.get(op1)].charAt(map.get(op2));
}

表达式的求值。若判断出表达式错误统一抛出自定义异常。

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
33
34
35
36
37
38
39
40
private static double _calculate(String s) throws ExpressionIllegalException{

List<Unit> units = new ArrayList<Unit>();
units.add(new Unit('#'));
units.addAll(parseUnits(s));
units.add(new Unit('#'));

Stack<Unit> operators = new Stack<Unit>();
Stack<Unit> operands = new Stack<Unit>();

int index = 0;
while(index < units.size() || operators.size() != 0){
Unit unit = units.get(index);
if(unit.getUnitType() == Unit.UnitType.OPERAND){
operands.push(unit);
index += 1;
}else if(unit.getUnitType() == Unit.UnitType.OPERATOR){
if(operators.empty() || compareOperator(operators.peek().getOp(), unit.getOp()) == '<'){
operators.push(unit);
index += 1;
}else if(compareOperator(operators.peek().getOp(), unit.getOp()) == '='){
Unit tmp = operators.pop();
index += 1;
}else if(compareOperator(operators.peek().getOp(), unit.getOp()) == '>'){
if(operands.size() < 2){
throw new ExpressionIllegalException();
}
Unit operand2 = operands.pop();
Unit operand1 = operands.pop();
Unit operator = operators.pop();
Unit res = operator.operate(operand1, operand2);
operands.push(res);
}else{
throw new ExpressionIllegalException();
}
}
}
double res = operands.peek().getVal();
return res;
}

4.格式化输出

之所以特地判断是否NaN是因为后面的函数对于NaN的结果是一个奇怪的字符,而对于正负无穷可以正确的输出。

1
2
3
4
5
public static String calculate(String s) throws ExpressionIllegalException{

double res = _calculate(s);
return Double.isNaN(res)?"NaN":new DecimalFormat("#.##############").format(res);
}

github

完整的项目已经开源到github上,项目由Intellij IDEA创建。
github项目主页: Calculator