package com.jxv.common.utils;
import com.alibaba.fastjson.JSON;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import static com.jxv.common.utils.MathCalculatorUtil.SelfMathFormulaEnum.getSelfMathFormulaEnum;
import static com.jxv.common.utils.MathCalculatorUtil.SelfMathFormulaEnum.getSelfMathFormulaNames;
public class MathCalculatorUtil {
private static final Logger log = LoggerFactory.getLogger(MathCalculatorUtil.class);
private static final Set<Character> operateSet = new HashSet<>();
static {
operateSet.add('+');
operateSet.add('-');
operateSet.add('*');
operateSet.add('/');
operateSet.add('%');
}
enum SelfMathFormulaEnum {
abs("abs", 1, 3, "abs(x)", "返回数的绝对值"),
acos("acos", 1, 4,"acos(x)", "返回数的反余弦值"),
asin("asin", 1, 4,"asin(x)", "返回数的反正弦值"),
atan("atan", 1, 4,"atan(x)", "以介于 -PI/2 与 PI/2 弧度之间的数值来返回 x 的反正切值"),
ceil("ceil", 1, 4,"ceil(x)", "对数进行上舍入"),
cos("cos", 1, 3,"cos(x)", "返回数的余弦"),
exp("exp", 1, 3,"exp(x)", "返回 e 的指数"),
floor("floor", 1, 5,"floor(x)", "对数进行下舍入"),
log("log", 1, 3,"log(x)", "返回数的自然对数(底为e)"),
max("max", 2, 3,"max(x,y)", "返回 x 和 y 中的最高值"),
min("min", 2, 3,"min(x,y)", "返回 x 和 y 中的最低值"),
pow("pow", 2, 3,"pow(x,y)", "返回 x 的 y 次幂"),
round("round", 1, 5,"round(x)", "把数四舍五入为最接近的整数"),
sin("sin", 1, 3,"sin(x)", "返回数的正弦"),
sqrt("sqrt", 1, 4,"sqrt(x)", "返回数的平方根"),
tan("tan", 1, 3,"tan(x)", "返回角的正切");
private String formulaName;
private Integer formulaArgCount;
private Integer formulaNameLength;
private String formulaExpresion;
private String description;
public static SelfMathFormulaEnum getSelfMathFormulaEnum(String formulaName, Integer formulaArgCount) {
for (SelfMathFormulaEnum selfMathFormulaEnum : SelfMathFormulaEnum.values()) {
if (selfMathFormulaEnum.getFormulaName().equals(formulaName) && selfMathFormulaEnum.getFormulaArgCount().equals(formulaArgCount)) {
return selfMathFormulaEnum;
}
}
return null;
}
public static SelfMathFormulaEnum getSelfMathFormulaEnum(String formulaName) {
for (SelfMathFormulaEnum selfMathFormulaEnum : SelfMathFormulaEnum.values()) {
if (selfMathFormulaEnum.getFormulaName().equals(formulaName)) {
return selfMathFormulaEnum;
}
}
return null;
}
public static List<String> getSelfMathFormulaNames() {
List<String> formulaNames = new ArrayList<>();
for (SelfMathFormulaEnum selfMathFormulaEnum : SelfMathFormulaEnum.values()) {
formulaNames.add(selfMathFormulaEnum.getFormulaName());
}
return formulaNames;
}
public static List<SelfMathFormulaEnum> getSelfMathFormulas() {
List<SelfMathFormulaEnum> formulaNames = new ArrayList<>();
for (SelfMathFormulaEnum selfMathFormulaEnum : SelfMathFormulaEnum.values()) {
formulaNames.add(selfMathFormulaEnum);
}
return formulaNames;
}
SelfMathFormulaEnum(String formulaName, Integer formulaArgCount, Integer formulaNameLength, String formulaExpresion, String description) {
this.formulaName = formulaName;
this.formulaArgCount = formulaArgCount;
this.formulaNameLength = formulaNameLength;
this.formulaExpresion = formulaExpresion;
this.description = description;
}
public Integer getFormulaNameLength() {
return formulaNameLength;
}
public void setFormulaNameLength(Integer formulaNameLength) {
this.formulaNameLength = formulaNameLength;
}
public String getFormulaName() {
return formulaName;
}
public void setFormulaName(String formulaName) {
this.formulaName = formulaName;
}
public Integer getFormulaArgCount() {
return formulaArgCount;
}
public void setFormulaArgCount(Integer formulaArgCount) {
this.formulaArgCount = formulaArgCount;
}
public String getFormulaExpresion() {
return formulaExpresion;
}
public void setFormulaExpresion(String formulaExpresion) {
this.formulaExpresion = formulaExpresion;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
private static final ScriptEngine scriptEngine = new ScriptEngineManager().getEngineByName("JavaScript");
public static boolean isNumber(String str) {
String reg = "^[-\\+]?[0-9]+(.[0-9]+)?$";
return str.matches(reg);
}
public static String simpleFormulaScript(String mathFormulaScript, int retainDigit) {
try {
if (StringUtils.isNotEmpty(mathFormulaScript)) {
mathFormulaScript = "(" + mathFormulaScript + ").toFixed(" + retainDigit + ")";
}
return JSON.toJSONString(scriptEngine.eval(mathFormulaScript));
} catch (ScriptException e) {
log.error("非法数学公式!");
e.printStackTrace();
throw new RuntimeException("非法数学公式!");
}
}
public static String simpleFormulaScript(String mathFormulaScript) {
return simpleFormulaScript(mathFormulaScript, 2);
}
private static void checkArg(String arg) {
checkArg(arg, true);
}
private static void checkArg(String arg, boolean isZero) {
if (StringUtils.isEmpty(arg) || !isNumber(arg) || (!isZero && (new BigDecimal("0").compareTo(new BigDecimal(arg)) == 0))) {
throw new RuntimeException("非法计算参数!");
}
}
private static void checkFormulaExpression(String expression) {
expression = expression.replaceAll(" ", "");
char[] arr = expression.toCharArray();
int len = arr.length;
int checkNum = 0;
StringBuffer sb = new StringBuffer();
StringBuffer sb0 = new StringBuffer();
for (int i = 0; i < len; i++) {
if (Character.isDigit(arr[i]) || arr[i] == '.') {
sb.append(arr[i]);
} else if (Character.isLetter(arr[i])) {
sb0.append(arr[i]);
} else {
if (sb.length() > 0) {
if (isNumber(sb.toString())) {
sb.setLength(0);
} else {
throw new RuntimeException("非法数字参数");
}
}
if (arr[i] == '+' || arr[i] == '*' || arr[i] == '/' || arr[i] == '%') {
if (i == 0 || i == (len - 1) || arr[i + 1] == '+' || arr[i + 1] == '*' || arr[i + 1] == '/' || arr[i + 1] == '%' || arr[i + 1] == ')') {
log.error("非法符号 : '+' or '*' or '/' ->" + arr[i]);
throw new RuntimeException("非法符号 : '+' or '*' or '/' ==>" + arr[i]);
}
} else if (arr[i] == '-') {
if (i == (len - 1) || arr[i + 1] == '+' || arr[i + 1] == '*' || arr[i + 1] == '/' || arr[i + 1] == '%' || arr[i + 1] == ')') {
log.error("非法符号 : '-' ->" + arr[i]);
throw new RuntimeException("非法符号 : '-' ==>" + arr[i]);
}
} else if (arr[i] == '(') {
if (sb0.length() > 0) {
int beginIndex = expression.indexOf(arr[i], i);
int endIndex = matchBracketIndex(expression, i, arr[i]);
if (endIndex == -1) {
log.error("非法数学公式符号:==>" + sb0.length());
throw new RuntimeException("非法数学公式符号: ==>" + sb0.length());
}
String selfMathBracketContentStr = expression.substring(beginIndex + 1, endIndex);
if (StringUtils.isEmpty(selfMathBracketContentStr)) {
log.error("非法自定义数学公式符号:==>" + sb0.length());
throw new RuntimeException("非法自定义数学公式符号: ==>" + sb0.length());
}
StringBuilder selfMathBracketContentSb = new StringBuilder(selfMathBracketContentStr);
int argCounts = getSelfMathMarkArgCounts(selfMathBracketContentSb, ",");
checkSelfMathMark(sb0.toString(), argCounts);
sb0.setLength(0);
}
checkNum++;
if (i == (len - 1) || arr[i + 1] == '+' || arr[i + 1] == '*' || arr[i + 1] == '/' || arr[i + 1] == '%' || arr[i + 1] == ')' || (i != 0 && Character.isDigit(arr[i - 1]))) {
log.error("非法符号 : '(' ->" + arr[i]);
throw new RuntimeException("非法符号 : '(' ==>" + arr[i]);
}
} else if (arr[i] == ')') {
checkNum--;
if (i == 0 || (i < (len - 1) && arr[i + 1] == '(') || checkNum < 0) {
log.error("非法符号 : ')' ->" + arr[i]);
throw new RuntimeException("非法符号 : ')' ==>" + arr[i]);
}
} else if (arr[i] == ',') {
checkComma(expression, i, i);
} else {
log.error("非数字和运算符:==>" + arr[i]);
throw new RuntimeException("非数字和运算符:==>" + arr[i]);
}
}
}
if (checkNum != 0) {
log.error("括号个数不匹配");
throw new RuntimeException("括号个数不匹配");
}
}
private static void checkComma(String str, int currentCommaIndex, final int constCommaIndex) {
int beginIndex = indexOfBefore(str, currentCommaIndex, '(');
if (beginIndex == -1) {
log.error("非法逗号!");
throw new RuntimeException("非法逗号!");
}
int endIndex = matchBracketIndex(str, beginIndex, '(');
if (endIndex == -1) {
log.error("非法逗号!");
throw new RuntimeException("非法逗号!");
}
if (endIndex <= constCommaIndex) {
checkComma(str, beginIndex, constCommaIndex);
} else {
char[] chars = str.toCharArray();
StringBuilder sb = new StringBuilder();
for (int i = beginIndex - 1; i >= 0; i--) {
if (Character.isLetter(chars[i]) || Character.isDigit(chars[i])) {
sb.append(chars[i]);
} else {
break;
}
}
List<String> selfMathFormulaNames = getSelfMathFormulaNames();
if (!selfMathFormulaNames.contains(sb.reverse().toString())) {
throw new RuntimeException("非法逗号!");
}
}
}
private static int indexOfBefore(String str, int endIndex, char dest) {
char[] chars = str.trim().toCharArray();
int len = chars.length;
int index0 = endIndex;
if (len - 1 < endIndex) {
index0 = len;
}
for (int i = index0 - 1; i >= 0; i--) {
if (chars[i] == dest) {
return i;
}
}
return -1;
}
private static int getSelfMathMarkArgCounts(StringBuilder mathArgStr, String regex) {
int beginIndex = mathArgStr.indexOf("(");
if (beginIndex != -1) {
int endIndex = matchBracketIndex(mathArgStr.toString(), beginIndex, '(');
if (endIndex == -1) {
throw new RuntimeException("非法括号匹配!");
}
mathArgStr.replace(beginIndex, endIndex + 1, "");
return getSelfMathMarkArgCounts(mathArgStr, regex);
} else {
String[] argsArr = mathArgStr.toString().split(regex, -1);
return argsArr.length;
}
}
private static void checkSelfMathMark(String mathStr, int argCount) {
SelfMathFormulaEnum selfMathFormulaEnum = getSelfMathFormulaEnum(mathStr, argCount);
if (selfMathFormulaEnum == null) {
throw new RuntimeException("自定义数学公式不匹配!");
}
}
private static int matchBracketIndex(String s, int fromIndex, char leftDest) {
if (StringUtils.isEmpty(s)) {
return -1;
}
int index0 = s.indexOf(leftDest, fromIndex);
if (index0 != -1) {
Stack<Character> stack = new Stack<>();
for (int i = index0; i < s.length(); i++) {
char c = s.charAt(i);
if (c == '{' || c == '[' || c == '(') {
stack.push(c);
} else if (c == '}' || c == ']' || c == ')') {
if (stack.isEmpty()) {
return -1;
}
char topChar = stack.pop();
if ((topChar == '[' && c == ']') || (topChar == '(' && c == ')') || (topChar == '{') && c == '}') {
if (stack.isEmpty()) {
return i;
} else {
continue;
}
}
} else {
continue;
}
}
}
return -1;
}
private static String add(String v1, String v2) {
checkArg(v1);
checkArg(v2);
BigDecimal v1Bd = new BigDecimal(v1);
BigDecimal v2Bd = new BigDecimal(v2);
return v1Bd.add(v2Bd).toString();
}
private static String sub(String v1, String v2) {
checkArg(v1);
checkArg(v2);
BigDecimal v1Bd = new BigDecimal(v1);
BigDecimal v2Bd = new BigDecimal(v2);
return v1Bd.subtract(v2Bd).toString();
}
private static String mul(String v1, String v2) {
checkArg(v1);
checkArg(v2);
BigDecimal v1Bd = new BigDecimal(v1);
BigDecimal v2Bd = new BigDecimal(v2);
return v1Bd.multiply(v2Bd).toString();
}
private static String div(String v1, String v2) {
checkArg(v1);
checkArg(v2, false);
BigDecimal v1Bd = new BigDecimal(v1);
BigDecimal v2Bd = new BigDecimal(v2);
return v1Bd.divide(v2Bd, 2, RoundingMode.HALF_UP).toString();
}
private static String mod(String v1, String v2) {
checkArg(v1);
checkArg(v2, false);
BigDecimal v1Bd = new BigDecimal(v1);
BigDecimal v2Bd = new BigDecimal(v2);
return v1Bd.remainder(v2Bd).toString();
}
public static String calculator(String mathFormula) {
if (StringUtils.isEmpty(mathFormula)) {
throw new RuntimeException("非法计算公式!");
}
mathFormula = mathFormula.replaceAll(" ", "");
int bracket = mathFormula.indexOf("[");
int brace = mathFormula.indexOf("{");
if (bracket != -1 || brace != -1) {
log.info("计算公式:{}", mathFormula);
mathFormula = mathFormula.replaceAll("[\\[\\{]", "(").replaceAll("[\\]\\}]", ")");
log.info("标准数学计算公式 '{,[':" + mathFormula);
}
checkFormulaExpression(mathFormula);
String result0 = calculatorSelfMathFormula(mathFormula);
return new BigDecimal(result0).setScale(8,BigDecimal.ROUND_HALF_UP).toString();
}
public static void main(String args[]) throws ScriptException {
long beginTime = System.currentTimeMillis();
String calculator2 = MathCalculatorUtil.standardCalculation("3*-2");
String calculator3 = MathCalculatorUtil.standardCalculation("3--2");
String k ="-(2.5)*(-1)+(-1)*2";
String k1 ="-2.5*(2)+(-1)*(-2)";
String k2 ="(-2.5)*(-2)+(-1)*(-2)-2*3";
String re2 = calculator(k2);
String ss22 = "1+1*2+(10-(2*(5-3)*(2-1))-4)+10/(5-0) + log(10)/9 + 2*log(30)+ max(sin(2*(5+2))*6,pow(2*(2+8),3+2)) * max(-1+3,(5-4)) + min(sin(10), sin(20))";
String re0 = calculator(k);
String re1 = calculator(k1);
System.out.println(re0);
System.out.println("cost时间:" + (System.currentTimeMillis() - beginTime) + "ms");
}
private static String calculatorSelfMathFormula(String mathFormula) {
if (StringUtils.isEmpty(mathFormula)) {
throw new RuntimeException("非法参数错误!");
}
mathFormula = mathFormula.replaceAll(" ", "");
List<SelfMathFormulaEnum> selfMathFormulaEnums = SelfMathFormulaEnum.getSelfMathFormulas();
boolean flag = false;
for (SelfMathFormulaEnum mathFormulaEnum : selfMathFormulaEnums) {
if (mathFormula.contains(mathFormulaEnum.getFormulaName())) {
flag = true;
break;
}
}
if (flag) {
for (int i = 0; i < selfMathFormulaEnums.size();) {
boolean repeat = false;
SelfMathFormulaEnum mathFormulaEnum = selfMathFormulaEnums.get(i);
if (mathFormula.contains(mathFormulaEnum.getFormulaName())) {
int index0 = mathFormula.indexOf(mathFormulaEnum.getFormulaName());
String left = mathFormula.substring(0, index0);
int index1 = matchBracketIndex(mathFormula, index0, '(');
String right = mathFormula.substring(index1 +1);
String bracketsContent = mathFormula.substring(index0 + mathFormulaEnum.getFormulaNameLength() + 1, index1);
mathFormula = left + selfMathCalculation(mathFormulaEnum.getFormulaName(), calculatorSelfMathFormula(bracketsContent)) + right;
repeat = true;
}
if (repeat) {
i = i;
}else {
i++;
}
}
}
return standardCalculation(mathFormula);
}
private static String selfMathCalculation(String mathFormulaName, String digitStr) {
double result;
if (StringUtils.isEmpty(digitStr)) {
throw new RuntimeException("非法计算公式参数!");
}
String[] args = digitStr.split(",", -1);
SelfMathFormulaEnum selfMathFormulaEnum = getSelfMathFormulaEnum(mathFormulaName);
if (selfMathFormulaEnum == null) {
throw new RuntimeException("非法数学公式名称");
}
switch (selfMathFormulaEnum) {
case abs:
result = Math.abs(Double.parseDouble(args[0]));
break;
case acos:
result = Math.acos(Double.parseDouble(args[0]));
break;
case asin:
result = Math.asin(Double.parseDouble(args[0]));
break;
case atan:
result = Math.atan(Double.parseDouble(args[0]));
break;
case ceil:
result = Math.ceil(Double.parseDouble(args[0]));
break;
case cos:
result = Math.cos(Double.parseDouble(args[0]));
break;
case exp:
result = Math.exp(Double.parseDouble(args[0]));
break;
case floor:
result = Math.floor(Double.parseDouble(args[0]));
break;
case log:
result = Math.log(Double.parseDouble(args[0]));
break;
case max:
result = Math.max(Double.parseDouble(args[0]), Double.parseDouble(args[1]));
break;
case min:
result = Math.min(Double.parseDouble(args[0]), Double.parseDouble(args[1]));
break;
case pow:
result = Math.pow(Double.parseDouble(args[0]), Double.parseDouble(args[1]));
break;
case round:
result = Math.round(Double.parseDouble(args[0]));
break;
case sin:
result = Math.sin(Double.parseDouble(args[0]));
break;
case sqrt:
result = Math.sqrt(Double.parseDouble(args[0]));
break;
case tan:
result = Math.tan(Double.parseDouble(args[0]));
break;
default:
throw new RuntimeException("找不到匹配的计算公式!");
}
return String.valueOf(result);
}
private static String standardCalculation(String str) {
if (StringUtils.isEmpty(str)) {
log.error("非法计算公式!");
throw new RuntimeException("非法计算公式!");
}
String[] args = str.split(",", -1);
if (args != null && args.length > 0) {
List<String> argResult = new ArrayList<>();
for (String arg : args) {
int hasBrackets = arg.lastIndexOf('(');
if (hasBrackets == -1) {
argResult.add(cac(arg));
}else {
int cr = arg.indexOf(')', hasBrackets);
String left = arg.substring(0, hasBrackets);
String right = arg.substring(cr + 1);
String middle = arg.substring(hasBrackets + 1, cr);
argResult.add(standardCalculation(left + cac(middle) + right));
}
}
return StringUtils.join(argResult, ",");
}
throw new RuntimeException("非法算式参数!");
}
private static String cac(String str) {
int mulIndex = str.indexOf('*');
int divIndex = str.indexOf('/');
int modIndex = str.indexOf('%');
if (mulIndex == -1 && divIndex == -1 && modIndex == -1) {
return AASOperation(str);
}
String result0 = "0";
int index0 = getMin(-1,mulIndex, divIndex, modIndex);
try {
String left = str.substring(0, index0);
String v1 = lastNumber(left);
left = left.substring(0, left.length() - v1.length());
String right = str.substring(index0 + 1);
String v2 = firstNumber(right);
right = right.substring(v2.length());
if (index0 == mulIndex) {
result0 = mul(v1, v2);
} else if(index0 == divIndex) {
result0 = div(v1, v2);
} else if(index0 == modIndex) {
result0 = mod(v1, v2);
}
String s = left + result0 + right;
return cac(left + result0 + right);
}catch (Exception e) {
log.error("数学计算公式错误"+ e.getMessage());
throw new RuntimeException("数学计算公式错误!");
}
}
private static int getMin(int noNum, int... a){
if (a == null || a.length == 0) {
throw new RuntimeException("非法参数!");
}
int min = a[0];
for (int i = 1; i < a.length; i++) {
if (min ==noNum || (min > a[i] && a[i] != noNum)) {
min = a[i];
}
}
if (min == noNum) {
throw new RuntimeException("非法可变参数!");
}
return min;
}
private static String lastNumber(String str) {
StringBuilder sb = new StringBuilder();
for (int i = str.length() - 1; i >= 0; i--) {
char c = str.charAt(i);
if (Character.isDigit(c) || (i != 0 && c == '.') || ((i == 0 || operateSet.contains(str.charAt(i -1))) && c == '-')) {
sb.append(c);
}else {
break;
}
}
return sb.reverse().toString();
}
private static String firstNumber(String str) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < str.length(); i++) {
char c = str.charAt(i);
if (Character.isDigit(c) || (i != 0 && c == '.') || (i == 0 && c == '-')) {
sb.append(c);
}else {
break;
}
}
return sb.toString();
}
private static String AASOperation(String mathStr) {
if (StringUtils.isEmpty(mathStr)) {
throw new RuntimeException("非法计算参数");
}
char[] options = (mathStr + "+").replaceAll(" ", "").toCharArray();
String result0 = "0";
StringBuilder sb = new StringBuilder();
char sign = '+';
for (int i = 0; i < options.length; i++) {
if (Character.isDigit(options[i]) || options[i] == '.') {
sb.append(options[i]);
} else {
if ((i == 0 && options[i] == '-') || (i>1 && operateSet.contains(options[i-1]))) {
sb.append(options[i]);
}else {
if (sb.length() > 0){
if (sign == '+') {
result0 = add(result0, sb.toString());
} else {
result0 = sub(result0, sb.toString());
}
sb.setLength(0);
sign = options[i];
} else {
throw new RuntimeException("非法数学公式错误!");
}
}
}
}
return result0;
}
}