数学运算公式的计算
字符串数学运算公式的计算
数学运算公式就不必多介绍了,简单交流一下Java怎么实现数学运算公式的计算,在看到此篇博客前,如果你有需求,可能已经翻阅了无数篇文章了,或许已经得到你想要的答案,不过说不定也还没有找到你想要的,这里分享一下本人在开发中的求与获。
字符串运算公式
最简单的情况:
3
+
4
3 + 4
3+4
次简单的情况:
(
3
+
4
)
∗
5
(3 + 4) * 5
(3+4)∗5
稍有难度:
3
2
+
4
3^2 + 4
32+4
疑难:
(
1
/
2
3
+
(
3
−
4.1
)
3
−
1
)
3
∗
888
/
33
(1 / 2^3 + (3 - 4.1)^3 - 1 )^3 * 888 / 33
(1/23+(3−4.1)3−1)3∗888/33
针对以上几种情况,一二种情况就不说了,对于后面几种,夹带次方和嵌套括号运算的,踩坑之路且漫漫!
分析
第一种情况: 不说了!
第二种情况: 也不说了!
第三种情况: 3的二次方需要替换成3 * 3,如果是3的三次放则替换成3 * 3 * 3,咱不用Math.pow(),因为底数和指数不是固定的。
第四种情况: 看见这种这样复杂的运算公式,想想就头疼,思绪万千,先计算括号里面的值,如果遇到次方,将次方转为乘除的样式去计算,算式或许将会变得简单。比如将疑难公式转为先解析成1/2/2/2 + (-1.1)(-1.1)(-1.1) - 1,把这个值计算完,乘方后,再乘以888除以33。当然,第四种情况,既然是字符串,次方可能是⁰,¹,²,³,⁴,⁵,⁶,⁷,⁸,⁹,不过这简单,替换成^
就可以了。
1.
先将字符串的次方替换成我们熟悉的^
代表次方的方式,然后以^
所在字符索引往前移动,当遇到运算符或者到字符串起始位置之间的字符即为底数;往后移动,当遇到运算符或者到字符串末尾之间的字符即为指数,然后再换成乘数或者除数的运算形式,这里为什么一再强调乘数或者除数,因为如果指数幂做分母,必须要换成除法的形式,比如1/2^3即需要根据"/"进行判断,然后将其换成1/2/2/2。
2.
括号多层嵌套,需要先将内层括号的运算表达式识别出来,计算完成后再计算外层括号的表达式。怎么从内层括号一层一层的剥,可以借助栈,先将字符都往栈里面放,当遇到)
时,往栈里面取字符,直到取到(
,然后把取出来的字符串【简单的运算表达式】计算完成后,将原计算完后的值重新放入栈中,这样,既完成了括号内表达式的计算,又将括号移除了。
3.
表达式的值怎么计算?要自己重新写或学习的在网上找一下都有,更多的也是利用栈完成计算。不过我比较懒,走了一条投机的路,引用了ScriptEngine类的eval()方法来计算运算表达式。
实现代码
package com.pks.common.utils;
import org.apache.commons.lang3.StringUtils;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.Stack;
import java.util.regex.Pattern;
/**
* @description: 运算公式计算工具类
* @author: pks
*/
public class CalculateUtils {
public static final List<String> OPERATORS = Arrays.asList("+", "-", "*", "/", "(", ")");
/**
* 运算公式
*/
public static final String CALCULATION_FORMULA = "^(?:[\\*\\/\\+\\-\\s]*\\d+(?:\\.\\d*)?){1,}$";
/**
* 次方映射 ⁰,¹,²,³,⁴,⁵,⁶,⁷,⁸,⁹
* 百分号%用 /100替换 乘以符号的替换 减号的替换
*/
public static final Map<String, String> SPECIAL_POWER_MMAP = new ImmutableMap.Builder<String, String>()
.put("⁰", "^0")
.put("¹", "^1")
.put("²", "^2")
.put("³", "^3")
.put("⁴", "^4")
.put("⁵", "^5")
.put("⁶", "^6")
.put("⁷", "^7")
.put("⁸", "^8")
.put("⁹", "^9")
.put("×", "*")
.put("x", "*")
.put("X", "*")
.put("一", "-")
.put("+", "+")
.put("÷", "/")
.put("%", "/100")
.build();
/**
* 计算公式的值
*
* @param formula 计算公式
* @return 计算的值
*/
public static String calculateFormula(String formula) {
formula = transformCalculateFormula(formula);
formula = getCalculateFormula(formula);
if (!isCalculateFormula(formula)) {
return formula;
}
// 使用script引擎来计算
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("js");
try {
Object result = engine.eval(formula);
BigDecimal bd = new BigDecimal(result.toString());
// 默认保留两位小数,移除多余的0,转成非科学计数法的字符串
return bd.setScale(2, RoundingMode.HALF_UP).stripTrailingZeros().toPlainString();
} catch (ScriptException e) {
return formula;
}
}
/**
* 转换计算公式
*
* @param formula 公式
* @return 计算公式
*/
public static String transformCalculateFormula(String formula) {
// 如果次方是以上标的形式写的,需要替换成^表示次方; %用/100表示;× 用 * 表示
Set<String> powerStrSet = SPECIAL_POWER_MMAP.keySet();
for (String power : powerStrSet) {
if (formula.contains(power)) {
formula = formula.replaceAll(power, SPECIAL_POWER_MMAP.get(power));
}
}
// 替换空格
formula = formula.replaceAll(" ", "");
return formula;
}
/**
* 判断字符串是否是数学计算公式
*
* @param formula 字符串公式
* @return true or false
*/
public static boolean isCalculateFormula(String formula) {
if (StringUtils.isEmpty(formula)) {
return false;
}
Pattern pattern = Pattern.compile(CALCULATION_FORMULA);
return pattern.matcher(formula).matches();
}
/**
* 获取次方替换的字符串 除了除号,其余均替换成乘号
* (10^2替换成(10*10
* +10^2替换成+10*10
* -10^2替换成-10*10
* *10^2替换成*10*10
* /10^2替换成/10/10
*
* @return 替换结果
*/
public static String getCalculateFormula(String formula) {
// 添加一个!作为结束替换的标识
String value = StringUtils.join(formula, "!");
char[] charArray = value.toCharArray();
StringBuilder sb = new StringBuilder();
// 记录前一个运算符的位置,将其值加载替换值的最前面,用来判断次方用乘法还是除法
int preIndex = 0;
for (int i = 0 ; i < charArray.length; i++) {
if (charArray[i] == '+' || charArray[i] == '-' || charArray[i] == '*' || charArray[i] == '/'
|| charArray[i] == '(' || charArray[i] == ')' || charArray[i] == '!') {
if (sb.toString().contains("^")) {
int currentIndex = preIndex;
StringBuilder preStr = new StringBuilder();
// 如果乘方前面的符号是小数点要继续往前移动,把小数取到
while (currentIndex >= 0 && !OPERATORS.contains(Character.toString(charArray[currentIndex]))) {
preStr.insert(0, charArray[currentIndex]);
currentIndex --;
}
currentIndex = Math.max(currentIndex, 0);
// 乘方前面的计算符号
if (OPERATORS.contains(Character.toString(charArray[currentIndex]))) {
preStr.insert(0, charArray[currentIndex]);
} else {
preStr = new StringBuilder();
}
String power = preStr + sb.toString();
formula = formula.replace(power, replacePowerStr(power));
}
preIndex = i;
sb = new StringBuilder();
} else {
sb.append(charArray[i]);
}
}
return formula;
}
/**
* 替换幂
*
* @param replace 需要替换的字符串
* @return 替换结果
*/
public static String replacePowerStr(String replace) {
if (StringUtils.isEmpty(replace) || !replace.contains("^")) {
return replace;
}
String result;
String[] powers = replace.split("\\^");
if (powers.length != 2) {
return replace;
}
// 第一个字符串带指数,且第一个字符串的第一个字符是运算符
String computerStr = powers[0];
String operateStr = powers[0].substring(0, 1);
String baseNumber = powers[0].substring(1);
// 指数幂在字符串开头
if (!OPERATORS.contains(operateStr)) {
operateStr = "";
baseNumber = computerStr;
}
// 第二个字符串是幂
String power = powers[1];
List<String> replaceList = new ArrayList<>();
for (int i = 0; i < Integer.parseInt(power); i ++) {
replaceList.add(baseNumber);
}
if (replace.contains("/")) {
result = StringUtils.join(operateStr, String.join("/", replaceList));
} else {
result = StringUtils.join(operateStr, String.join("*", replaceList));
}
return result;
}
/**
* 计算括号里面的内容(小括号)
*
* @param computerStr 需要计算的字符串
* @return 没有括号的表达式
*/
public static String removeBracket(String computerStr) {
if (StringUtils.isEmpty(computerStr) || (!StringUtils.contains(computerStr, "(")
&& !StringUtils.contains(computerStr, ")"))) {
return computerStr;
}
// 将空格移除
computerStr = computerStr.replaceAll(" ", "");
Stack<String> originStrStack = new Stack<>();
Stack<String> computerStack = new Stack<>();
char[] chars = computerStr.toCharArray();
for (char c : chars) {
if (c != ')') {
originStrStack.push(Character.toString(c));
} else {
boolean computerFlag = true;
while (computerFlag) {
String originChar = originStrStack.pop();
computerStack.push(originChar);
if (originChar.equals("(")) {
computerFlag = false;
StringBuilder sb = new StringBuilder();
while (!computerStack.isEmpty()) {
sb.append(computerStack.pop());
}
String replaceFormula = sb.toString() + c; // 需要替换的公式
String computerFormula = replaceFormula.replace("(", "").replace(")", ""); // 需要计算的公式,不需要带括号
String computerResult = calculateFormula(computerFormula); // 计算括号里面的公式
originStrStack.push(computerResult); // 替换的结果反填给原始栈
}
}
}
}
StringBuilder noBracketFormula = new StringBuilder();
while (!originStrStack.isEmpty()) {
noBracketFormula.insert(0, originStrStack.pop());
}
return noBracketFormula.toString();
}
public static void main(String[] args) {
String computerStr1 = "3 + 4";
String computerStr2 = "(3 + 4) * 5";
String computerStr3 = "3^2 + 4";
String computerStr4 = "(1 / 2³ + (3 - 4.1)³ - 1 )³ * 888 / 33";
System.out.println(calculateFormula(removeBracket(computerStr1)));
System.out.println(calculateFormula(removeBracket(computerStr2)));
System.out.println(calculateFormula(removeBracket(computerStr3)));
System.out.println(calculateFormula(removeBracket(computerStr4)));
}
}
次方的字符串只处理了10次方以内的,如果有更高次的直接写成类如2^
32即可。如果实在是有需求要写成2³²,自行写一个方法处理一下转成2^
32也不是什么难事。以上代码纯个人编写,保不齐存在漏洞,如若发现,还请指正。