本篇博客將利用“后綴表達(dá)式”,100多行Java代碼(<u>不包括注釋</u>)實(shí)現(xiàn)一個簡單強(qiáng)大的計(jì)算器,支持的運(yùn)算符包括加、減、乘、除、以及小括號。
GitHub代碼鏈接(已經(jīng)做好封裝,可以直接使用)
實(shí)現(xiàn)原理及說明:
先將計(jì)算式變?yōu)槌绦蛉菀子?jì)算的后綴表達(dá)式,然后通過后綴表達(dá)式進(jìn)行計(jì)算得到結(jié)果。
文章分成兩部分進(jìn),第一部分介紹如何將普通的計(jì)算式變?yōu)楹缶Y表達(dá)式,第二部分為程序?qū)崿F(xiàn)。
一、后綴表達(dá)式:
我們平常生活中使用的計(jì)算式是叫中綴表達(dá)式,像這樣8×4-(10+12÷3),變?yōu)楹缶Y表達(dá)式是這樣8 4 × 10 12 3 ÷ + - ,后面這樣的表達(dá)式就便于我們程序計(jì)算了,那么后綴表達(dá)式是如何得出的呢?
生成規(guī)則(需要用到一個棧):
1.遇到操作數(shù),直接輸出;
2.棧為空時,遇到運(yùn)算符,直接入棧;
3.遇到左括號,直接入棧;
4.遇到右括號,右括號不入棧也不輸出,依次彈出棧頂元素直到彈出左括號為止,彈出元素依次輸出,左括號不輸出;
5.遇到其他運(yùn)算符“+”、“-”、“×”、“÷”時,彈出所有優(yōu)先級大于或等于該運(yùn)算符的棧頂元素并輸出,然后將該運(yùn)算符入棧;
6.最終將棧中的剩余元素依次出棧,輸出。
舉一個例子,下面的圖形代表?xiàng)#?/p>

以上就是 “原表達(dá)式(中綴表達(dá)式)” 變?yōu)? “后綴表達(dá)式” 的一種方法。
二、程序?qū)崿F(xiàn)
1、調(diào)用方法:
String result1 = Calculator.input("8×4-(10+12÷3)").getResult();
String result2 = Calculator.input("8/3").getResult(3); // 保留三位小數(shù)結(jié)果
**** GitHub代碼鏈接(已經(jīng)做好封裝,可以直接使用)****
2、全部代碼如下:
package com.csw.calculator;
import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import java.util.Stack;
/**
* 說明:
* 1.用到了BigDecimal類,它的精度范圍比較高,確保計(jì)算結(jié)果的準(zhǔn)確性。
* 2.關(guān)于處理負(fù)號,如-5+2,會變?yōu)?-5+2,在負(fù)號前添0。
* Created by 叢 on 2018/2/12 0012.
*/
public class Calculator {
private static String formula; // 輸入的計(jì)算式
private Calculator() {}
public static Calculator input(String inputFormula) {
formula = inputFormula;
return new Calculator();
}
/**
* 獲取結(jié)果,帶有指定保留位數(shù)功能
* @param accurate 小數(shù)點(diǎn)后保留的位數(shù)
* @return 經(jīng)過小數(shù)保留后的字符串結(jié)果
*/
public String getResult(int accurate) {
if (accurate >= 0)
return getRawResult().setScale(accurate, RoundingMode.HALF_UP).toPlainString();
else
return getResult();
}
/**
* 獲取結(jié)果
* @return 獲取字符串形式的結(jié)果
*/
public String getResult() {
return getRawResult().stripTrailingZeros().toPlainString();
}
/**
* 獲取BigDecimal結(jié)果方法
* @return 以BigDecimal的形式返回結(jié)果
*/
public BigDecimal getRawResult() {
handleFormula(); // 將字符串計(jì)算式中的空格處理掉,為生成后綴表達(dá)式做一些處理。
getSuffixFormula(); // 生成后綴表達(dá)式計(jì)算公式字符串,便于程序處理。
return calculate(); // 返回計(jì)算結(jié)果
}
/**
* 將輸入計(jì)算式中的每個數(shù)字以“_”結(jié)尾,表示一個數(shù)字結(jié)束,方便得到后綴表達(dá)式以及計(jì)算結(jié)果
*/
private void handleFormula() {
formula = formula.replace(" ", ""); // 處理計(jì)算式中的空格
formula = formula.replace("×", "*"); // 將×替換為*用于計(jì)算
formula = formula.replace("÷", "/"); // 將÷替換為/用于計(jì)算
if (formula.charAt(0) == '-') { // 計(jì)算式以負(fù)號開頭,在前面加0
formula = "0" + formula;
}
// 處理負(fù)號前面是括號的情況
formula = formula.replace("(-", "(0-");
// 下面5種運(yùn)算符前可能是數(shù)字,在前面加下劃線"_"
formula = formula.replace("+", "_+");
formula = formula.replace("-", "_-");
formula = formula.replace("*", "_*");
formula = formula.replace("/", "_/");
formula = formula.replace(")", "_)");
formula += "_"; // 計(jì)算式結(jié)尾可能是數(shù)字,結(jié)尾后加"下劃線_"
// 計(jì)算式如果帶括號,上面加的下劃線可能沒有加在數(shù)字后面,糾正一下
if (formula.contains("(") || formula.contains(")")) {
formula = formula.replace(")_+", ")+"); // 加號的左面是括號
formula = formula.replace(")_-", ")-"); // 減號的左面是括號
formula = formula.replace(")_*", ")*"); // 乘號的左面是括號
formula = formula.replace(")_/", ")/"); // 除號的左面是括號
formula = formula.replace(")_", ")"); // 結(jié)尾是括號
}
}
/**
* 獲取后綴表達(dá)式,后綴表達(dá)字符串中會保留handleFormula()中的下劃線
* Stack是Java提供的一個實(shí)現(xiàn)棧效果的類,stack.push(xxx)是入棧,stack.pop(xxx)是出棧,stack.peek()是查看棧頂元素
*/
private void getSuffixFormula() {
StringBuilder suffixFormula = new StringBuilder(); // 后綴表達(dá)式字符串
Stack<Character> stackOperator = new Stack<>();
for (char c: formula.toCharArray()) { // 將計(jì)算式字符串以char型的方式遍歷一遍
if (isNumber(c) || c == '_') { // 當(dāng)前char為數(shù)字或下劃線
suffixFormula.append(c);
} else if (c == '.') { // 小數(shù)點(diǎn)
suffixFormula.append(c);
} else if (c == '+' || c == '-') {
if (stackOperator.empty()) { // 棧為空
stackOperator.push(c);
}
else {
char c1 = stackOperator.peek();
while (c1 != '(') { // 是 +、-、*、/ 符號
suffixFormula.append(stackOperator.pop());
if (stackOperator.empty())
break;
c1 = stackOperator.peek();
}
stackOperator.push(c);
}
} else if (c == '*' || c == '/') {
if (stackOperator.empty()) {
stackOperator.push(c);
}
else {
char c1 = stackOperator.peek();
while (c1 == '*' || c1 == '/') {
suffixFormula.append(stackOperator.pop());
if (stackOperator.empty())
break;
c1 = stackOperator.peek();
}
stackOperator.push(c);
}
} else if (c == '(') {
stackOperator.push(c);
} else if (c == ')') { // 不需形成數(shù)字添加_,因?yàn)?) 前肯定是數(shù)字,后肯定是運(yùn)算符,形成數(shù)字交給后面的運(yùn)算符處理
char c1 = stackOperator.peek();
while (c1 != '(') {
suffixFormula.append(stackOperator.pop());
if (stackOperator.empty())
break;
c1 = stackOperator.peek();
}
stackOperator.pop(); // 彈出“(”
}
}
while (!stackOperator.empty())
suffixFormula.append(stackOperator.pop());
formula = suffixFormula.toString(); // 將后綴表達(dá)式賦予全局計(jì)算式,后綴表達(dá)式中會保留下劃線
}
/**
* 最后的計(jì)算方法,得到計(jì)算結(jié)果
*/
private BigDecimal calculate() {
Stack<BigDecimal> stackNumber = new Stack<>();
Stack<Character> stackSingleNumber = new Stack<>();
int intBit = 1; // 整數(shù)部分位數(shù)
int dotBit = 0; // 小數(shù)部分位數(shù)
for (char c: formula.toCharArray()) {
if (isNumber(c) || c == '.') {
stackSingleNumber.push(c);
} else if (c == '_') {
BigDecimal b = new BigDecimal(String.valueOf(stackSingleNumber.pop() - 48)); // char轉(zhuǎn)int
while (!stackSingleNumber.empty()) {
char c1 = stackSingleNumber.pop();
if (c1 != '.') { // char轉(zhuǎn)int
b = b.add(new BigDecimal(String.valueOf(c1 - 48)).multiply(new BigDecimal("10").pow(intBit)));
intBit++;
} else {
dotBit = intBit;
}
}
if (dotBit != 0)
b = b.multiply(new BigDecimal("0.1").pow(dotBit));
stackNumber.push(b);
// 初始化位數(shù)變量
intBit = 1;
dotBit = 0;
} else if (c == '+') {
BigDecimal b1 = stackNumber.pop();
BigDecimal b2 = stackNumber.pop();
stackNumber.push(b2.add(b1));
} else if (c == '-') {
BigDecimal b1 = stackNumber.pop();
BigDecimal b2 = stackNumber.pop();
stackNumber.push(b2.subtract(b1));
} else if (c == '*') {
BigDecimal b1 = stackNumber.pop();
BigDecimal b2 = stackNumber.pop();
stackNumber.push(b2.multiply(b1));
} else if (c == '/') {
BigDecimal b1 = stackNumber.pop();
BigDecimal b2 = stackNumber.pop();
stackNumber.push(b2.divide(b1, MathContext.DECIMAL128));
}
}
return stackNumber.pop();
}
/**
* 判斷傳入的char是否為數(shù)字
*/
private boolean isNumber(char c) {
return (c >= 48 && c <= 57);
}
}