当前计算器工具不够完善且有计算bug,请移步Java实现常用加减乘除公式解析及计算-2.0查看最新实现~
近期接到一个实现公式解析计算、能够多层嵌套并自动拆解的需求。即对于给定的字符串公式进行解析及计算,实现从持久层自动装载公式,拆解多层嵌套的公式,得到计算结果。大致的设计思路为:定义一个计算器基类,实现基本属性的装载及加减乘除公式的定义,实现公式拆解、运算符分级及解析计算;定义子类实现不同的装载逻辑和公式解析逻辑。
基类:
package cn.ac.sict.calculator;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 计算器抽象类定义
* @author yjj
* 2022-07-01 13:48
*/
public abstract class Calculator {
//公式id
private String id;
//公式名称
private String name;
//公式内容
private String formula;
//公式类型,true代表formula为数值,false代表formula为计算公式
private boolean type = false;
//计算精度
public static int precision = 0;
//默认值
public static String defaultValue = "0";
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getFormula() {
return formula;
}
public void setFormula(String formula) {
//把公式中的负数都转化为0-N的形式,方便计算
if(isValidString(formula)) {
StringBuffer stringBuffer = new StringBuffer();
//匹配公式开头的负数部分或中间的负数部分,即以-开头或-前为其他运算符号
Matcher matcher = Pattern.compile("(^-\\d+\\.?\\d*)|([\\(\\+\\-\\*\\/]{1}-\\d+\\.?\\d*)").matcher(formula);
int start = 0;
while(matcher.find()) {
String group = matcher.group();
String replacement;
if(group.charAt(1) >= '0' && group.charAt(1) <= '9') {
replacement = "(0" + group + ")";
} else {
replacement = group.substring(0, 1) + "(0" + group.substring(1) + ")";
}
stringBuffer.append(formula, start, matcher.start()).append(replacement);
start = matcher.end();
}
stringBuffer.append(formula, start, formula.length());
this.formula = stringBuffer.toString();
} else {
this.formula = defaultValue;
}
}
public boolean isType() {
return type;
}
public void setType(boolean type) {
this.type = type;
}
protected Calculator() {
}
/**
* 是否有效的String
* @param string
* @return boolean
*/
private static boolean isValidString(String string) {
return string != null && !"".equals(string) && !"null".equalsIgnoreCase(string);
}
/**
* 是否有效的数值
* @param value
* @return boolean
*/
private static boolean isValidDigit(String value) {
if(isValidString(value)) {
value = value.trim();
return Pattern.compile("^(\\+|-)?\\d+($|\\.\\d+$)").matcher(value).matches();
} else {
return false;
}
}
/**
* 数值格式化
* @param value 数值字符串
* @return String
*/
private static String format(String value) {
StringBuffer expression = new StringBuffer("0");
if(precision > 0) {
expression.append(".");
for(int i=0;i<precision;i++) {
expression.append("0");
}
}
DecimalFormat decimalFormat = new DecimalFormat(expression.toString());
return decimalFormat.format(new BigDecimal(validate(value)));
}
/**
* 数值有效化
* @param value 数值字符串
* @return String
*/
private static String validate(String value) {
if(isValidDigit(value)) {
return value.trim();
} else {
return defaultValue;
}
}
/**
* 加
* @param left 左操作数
* @param right 右操作数
* @return String
*/
private static String add(String left, String right) {
BigDecimal arg1 = new BigDecimal(validate(left));
BigDecimal arg2 = new BigDecimal(validate(right));
BigDecimal arg3 = arg1.add(arg2);
return format(arg3.toString());
}
/**
* 减
* @param left 左操作数
* @param right 右操作数
* @return String
*/
private static String subtract(String left, String right) {
BigDecimal arg1 = new BigDecimal(validate(left));
BigDecimal arg2 = new BigDecimal(validate(right));
BigDecimal arg3 = arg1.subtract(arg2);
return format(arg3.toString());
}
/**
* 乘
* @param left 左操作数
* @param right 右操作数
* @return String
*/
private static String multiply(String left, String right) {
BigDecimal arg1 = new BigDecimal(validate(left));
BigDecimal arg2 = new BigDecimal(validate(right));
BigDecimal arg3 = arg1.multiply(arg2);
return format(arg3.toString());
}
/**
* 除
* @param left 左操作数
* @param right 右操作数
* @return String
*/
private static String divide(String left, String right) {
BigDecimal arg1 = new BigDecimal(validate(left));
BigDecimal arg2 = new BigDecimal(validate(right));
if(arg2.compareTo(BigDecimal.ZERO) == 0) {
return defaultValue;
}
BigDecimal arg3 = arg1.divide(arg2, precision, RoundingMode.HALF_EVEN);
return format(arg3.toString());
}
/**
* 获取操作符的优先级
* @param operator
* @return int
*/
private static int getPriority(String operator) {
if("(".equals(operator)) {
return -1;
}
if(")".equals(operator)) {
return 0;
}
if("+".equals(operator) || "-".equals(operator)) {
return 1;
}
if("*".equals(operator) || "/".equals(operator)) {
return 2;
}
return -2;
}
/**
* 计算
* @param left
* @param right
* @param operator
* @return String
*/
private static String calculate(String left, String right, String operator) {
if("+".equals(operator)) {
return add(left, right);
}
if("-".equals(operator)) {
return subtract(left, right);
}
if("*".equals(operator)) {
return multiply(left, right);
}
if("/".equals(operator)) {
return divide(left, right);
}
return defaultValue;
}
/**
* 拆分公式
* @return List
*/
protected List<String> splitFormula() {
int length = formula.length();
List<String> result = new ArrayList<>(length);
StringBuffer item = new StringBuffer();
for(int i=0;i<length;i++) {
char c = formula.charAt(i);
if(c == '(' || c == ')' || c == '+' || c == '-' || c == '*' || c == '/') {
if(item.length() > 0) {
result.add(item.toString());
item.setLength(0);
}
item.append(c);
result.add(item.toString());
item.setLength(0);
} else if((c >= '0' && c <= '9') || c == '#' || c == '.') {
item.append(c);
}
}
if(item.length() > 0) {
result.add(item.toString());
}
return result;
}
/**
* 抽象的初始化方法,由子类实现
*/
public abstract void init();
/**
* 抽象的解析公式方法,由子类实现
* @return List
*/
public abstract List<String> parseFormula();
/**
* 公式计算方法
* @param list 解析后的公式
* @return String
*/
public static String calculate(List<String> list) {
if(list == null || list.size() == 0) {
return defaultValue;
}
Stack<String> operatorStack = new Stack<>();
Stack<String> numberStack = new Stack<>();
int size = list.size();
for(int i=0;i<size;i++) {
String str = list.get(i);
if("(".equals(str)) {
operatorStack.push(str);
} else if(isValidDigit(str)) {
numberStack.push(str);
} else if("+".equals(str) || "-".equals(str) || "*".equals(str) || "/".equals(str) || ")".equals(str)) {
while(numberStack.size() >= 2 && operatorStack.size() >= 1 && getPriority(str) <= getPriority(operatorStack.peek())) {
String operator = operatorStack.pop();
String right = numberStack.pop();
String left = numberStack.pop();
numberStack.push(calculate(left, right, operator));
}
operatorStack.push(str);
if(")".equals(str)) {
while(!"(".equals(operatorStack.peek())) {
operatorStack.pop();
}
operatorStack.pop();
}
}
}
while(numberStack.size() >= 2 && operatorStack.size() >= 1) {
String operator = operatorStack.pop();
String right = numberStack.pop();
String left = numberStack.pop();
numberStack.push(calculate(left, right, operator));
}
return numberStack.pop();
}
}
解析json格式数据的子类:
package cn.ac.sict.calculator;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
/**
* 计算器json方式实现类
* @author yjj
* 2022-06-30 13:47
*/
public class CalculatorJsonImpl extends Calculator {
public String filePath = "D:\\ide\\project\\Calculator\\cn\\ac\\sict\\calculator\\data.json";
private CalculatorJsonImpl() {
super();
}
/**
* 创建实例
* @param id
* @return CalculatorJsonImpl
*/
public static CalculatorJsonImpl create(String id) {
CalculatorJsonImpl result = new CalculatorJsonImpl();
result.setId(id);
return result;
}
/**
* 初始化方法
*/
@Override
public void init() {
JSONArray jsonArray = JSONUtil.readJSONArray(new File(filePath), CharsetUtil.charset("UTF-8"));
jsonArray.stream().forEach(obj -> {
JSONObject jsonObject = JSONUtil.parseObj(obj);
if(getId().equals(jsonObject.getStr("id"))) {
setName(jsonObject.getStr("name"));
setFormula(jsonObject.getStr("formula"));
setType(jsonObject.getBool("type"));
}
});
}
/**
* 解析公式
* @return List
*/
@Override
public List<String> parseFormula() {
List<String> result = new ArrayList<>();
if(isType()) {
result.add(getFormula());
return result;
}
List<String> list = splitFormula();
for(String str : list) {
if(str.startsWith("#")) {
String id = str.substring(1);
CalculatorJsonImpl sub = CalculatorJsonImpl.create(id);
sub.init();
result.addAll(sub.parseFormula());
} else {
result.add(str);
}
}
return result;
}
}
在实际项目中可以把子类定义为从数据库获取公式定义,方便管理。
测试数据:
[
{
"id": "1",
"name": "测试1",
"formula": "1+2",
"type": false
},
{
"id": "2",
"name": "测试2",
"formula": "#1+3",
"type": false
},
{
"id": "3",
"name": "测试3",
"formula": "#2*-1*#4",
"type": false
},
{
"id": "4",
"name": "测试4",
"formula": "5",
"type": true
},
{
"id": "5",
"name": "测试5",
"formula": "2/(3.6+((5-100)+50.3))*(3-1)+6-8",
"type": false
}
]
测试类:
package cn.ac.sict.calculator;
import java.util.List;
/**
* @author yjj
* 2022-07-01 15:59
*/
public class Test {
public static void main(String[] args) {
Calculator calculator = CalculatorJsonImpl.create("3");
calculator.init();
System.out.println(calculator.getName());
List<String> list = calculator.parseFormula();
System.out.println("解析后的公式为:" + list);
String result = Calculator.calculate(list);
System.out.println("计算结果为:" + result);
calculator = CalculatorJsonImpl.create("5");
calculator.init();
System.out.println(calculator.getName());
list = calculator.parseFormula();
System.out.println("解析后的公式为:" + list);
Calculator.precision = 2;
result = Calculator.calculate(list);
System.out.println("计算结果为:" + result);
}
}
测试结果:
测试3
解析后的公式为:[1, +, 2, +, 3, *, (, 0, -, 1, ), *, 5]
计算结果为:-12测试5
解析后的公式为:[2, /, (, 3.6, +, (, (, 5, -, 100, ), +, 50.3, ), ), *, (, 3, -, 1, ), +, 6, -, 8]
计算结果为:-2.10