java 字符串 表达式计算_java 通过解析字符串数学表达式简单进行计算(包括自定义函数以及带括号的数学表达式)...

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;

/**

* @Author fangzhenxun

* @Description 数学计算公式(精确)

* @Date 2020/12/30 13:30

**/

public class MathCalculatorUtil {

private static final Logger log = LoggerFactory.getLogger(MathCalculatorUtil.class);

/**

* 所支持的运算操作符集合, 两元运算符

**/

private static final Set 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;

/**

* @param formulaName

* @param formulaArgCount

* @return com.jxv.common.utils.MathCalculatorUtil.SelfMathFormulaEnum

* @Author fangzhenxun

* @Description 根据自定义公式名称,和参数数量返回匹配的枚举实体

* @Date 2020/12/31 10:14

**/

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;

}

/**

* @Author fangzhenxun

* @Description  根据名称获取函数名

* @Date 2020/12/31 17:10

* @param formulaName

* @return com.jxv.common.utils.MathCalculatorUtil.SelfMathFormulaEnum

**/

public static SelfMathFormulaEnum getSelfMathFormulaEnum(String formulaName) {

for (SelfMathFormulaEnum selfMathFormulaEnum : SelfMathFormulaEnum.values()) {

if (selfMathFormulaEnum.getFormulaName().equals(formulaName)) {

return selfMathFormulaEnum;

}

}

return null;

}

/**

* @param

* @return java.util.List

* @Author fangzhenxun

* @Description 获取自定义公式的简单名称集合

* @Date 2020/12/31 14:44

**/

public static List getSelfMathFormulaNames() {

List formulaNames = new ArrayList<>();

for (SelfMathFormulaEnum selfMathFormulaEnum : SelfMathFormulaEnum.values()) {

formulaNames.add(selfMathFormulaEnum.getFormulaName());

}

return formulaNames;

}

/**

* @Author fangzhenxun

* @Description  获取所有的自定义函数枚举

* @Date 2021/1/6 10:27

* @param

* @return java.util.List

**/

public static List getSelfMathFormulas() {

List 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;

}

}

/**

* JavaScript脚本引擎,Java SE 6开始支持

**/

private static final ScriptEngine scriptEngine = new ScriptEngineManager().getEngineByName("JavaScript");

/**

* @param str

* @return boolean

* @Author fangzhenxun

* @Description 判断字符串是否为数字(浮点类型也包括, 以及正负符号)

* @Date 2020/12/25 18:18

**/

public static boolean isNumber(String str) {

String reg = "^[-\\+]?[0-9]+(.[0-9]+)?$";

return str.matches(reg);

}

/**

* @param mathFormulaScript 数学公式字符串,如:mathFormula = (2*3-45/5+(9)+9%5 +2*(1+2) + Math.sqrt(3))/9.0

*                          注意:如果使用开根号等三角函数等一些高级计算,则使用Math中字符串来替代,如4开根号==>Math.sqrt(4)

*                          具体使用查看:https://www.w3school.com.cn/jsref/jsref_obj_math.asp

* @param retainDigit       保留几位小数

* @return java.lang.String

* @Author fangzhenxun

* @Description 简单公式计算,会有精度丢失问题

* @Date 2020/12/23 13:13

**/

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("非法数学公式!");

}

}

/**

* @param mathFormulaScript

* @return java.lang.String

* @Author fangzhenxun

* @Description 重载方法,默认进度小数保留两位

* @Date 2020/12/23 13:48

**/

public static String simpleFormulaScript(String mathFormulaScript) {

return simpleFormulaScript(mathFormulaScript, 2);

}

/**

* @param arg

* @return void

* @Author fangzhenxun

* @Description 参数检查

* @Date 2020/12/30 13:37

**/

private static void checkArg(String arg) {

checkArg(arg, true);

}

/**

* @param arg

* @return void

* @Author fangzhenxun

* @Description 参数检查

* @Date 2020/12/30 13:37

**/

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("非法计算参数!");

}

}

/**

* @param expression "1+1*2+(10-(2*(5-3)*(2-1))-4)+10/(5-0) + log(10) + log(10,12)"

* @return void

* @Author fangzhenxun

* @Description 校验公式表达式的合法性

* @Date 2020/12/30 14:23

**/

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 

//判断当前元素是否是数字

if (Character.isDigit(arr[i]) || arr[i] == '.') {

//把数字和小数点加入到集合中,为了下一步判断数字是否合法

sb.append(arr[i]);

} else if (Character.isLetter(arr[i])) {

//校验自定义的公式(自定义的数学表达式都是使用字母拼接起来)

sb0.append(arr[i]);

} else {

//如果sb中有值,取出来判断这个数字整体是否合法

if (sb.length() > 0) {

if (isNumber(sb.toString())) {

//如果合法,清空,为了判断下一个

sb.setLength(0);

} else {

throw new RuntimeException("非法数字参数");

}

}

//不是数字为字符,可能是{},[], (), +, - , * , /, % 等,再加上各种自定义的数学运算公式, 或者是其他不合法的字符,接着继续判断

if (arr[i] == '+' || arr[i] == '*' || arr[i] == '/' || arr[i] == '%') {

//判断规则(1.不能位于首位 2.不能位于末尾 3.后面不能有其他运算符,但可以有-号 4.后面不能有后括号)

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] == '-') {

//减号判断规则(1.不能位于末尾 2.后面不能有其他运算符,但可以有-号 3.后面不能有后括号)

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] == '(') {

//判断(括号前面是否有字母

//如果sb0中有字母,取出来判断这个字符串整体是否是自定义的公式字符

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++;

//判断规则(1.不能位于末尾 2.后面不能有+,*,/,%运算符和后括号 3.前面不能为数字)

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--;

//判定规则(1.不能位于首位 2.后面不能是前括号 3.括号计数不能小于0,小于0说明前面少了前括号)

if (i == 0 || (i 

log.error("非法符号 : ')' ->" + arr[i]);

throw new RuntimeException("非法符号 : ')'  ==>" + arr[i]);

}

} else if (arr[i] == ',') {

//判定规则,如果有逗号,1,匹配该逗号是否被括号()包着,且左括号当前前面是否是自定义公式

checkComma(expression, i, i);

} else {

//非数字和运算符

log.error("非数字和运算符:==>" + arr[i]);

throw new RuntimeException("非数字和运算符:==>" + arr[i]);

}

}

}

//不为0,说明括号不对等,可能多前括号

if (checkNum != 0) {

//非数字和运算符

log.error("括号个数不匹配");

throw new RuntimeException("括号个数不匹配");

}

}

/**

* @param str               待匹配的字符数组

* @param currentCommaIndex 当前逗号索引

* @param constCommaIndex   常量逗号索引

* @return void

* @Author fangzhenxun

* @Description 校验英文逗号

* @Date 2020/12/31 13:24

**/

private static void checkComma(String str, int currentCommaIndex, final int constCommaIndex) {

//从currentCommaIndex索引开始往前找最近的一个左括号(,求出索引,并求出对应的)索引

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 {

//在从beginIndex索引开始往前找出连续字母的字符,然后拼接

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;

}

}

//将sb带入自定义公式进行校验

List selfMathFormulaNames = getSelfMathFormulaNames();

if (!selfMathFormulaNames.contains(sb.reverse().toString())) {

throw new RuntimeException("非法逗号!");

}

}

}

/**

* @param str      目标字符串 如 abcd((a),(b,bb))dada

* @param endIndex

* @return int

* @Author fangzhenxun

* @Description 返回从结束索引结束,

* @Date 2020/12/31 13:36

**/

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 

index0 = len;

}

for (int i = index0 - 1; i >= 0; i--) {

if (chars[i] == dest) {

return i;

}

}

//没有找到返回-1

return -1;

}

/**

* @param mathArgStr 参数括号字符串 如:log((math(1,6)+2),(8*9)) 中的 ((math(1,6)+2),(8*9))

* @param regex      分隔符 默认一般英文逗号,

* @return void

* @Author fangzhenxun

* @Description 获取自定义公式的参数个说

* @Date 2020/12/31 10:30

**/

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;

}

}

/**

* @param mathStr  log  sin

* @param argCount 2  , 1

* @return boolean

* @Author fangzhenxun

* @Description 校验数字字符数组

* @Date 2020/12/30 15:09

**/

private static void checkSelfMathMark(String mathStr, int argCount) {

SelfMathFormulaEnum selfMathFormulaEnum = getSelfMathFormulaEnum(mathStr, argCount);

if (selfMathFormulaEnum == null) {

throw new RuntimeException("自定义数学公式不匹配!");

}

}

/**

* @param s         待匹配的字符串

* @param fromIndex 开始索引

* @param leftDest  左括号

* @return int

* @Author fangzhenxun

* @Description 通过当前左括号(的索引 , 找到与之匹配对应的右括号) 的索引

* @Date 2020/12/30 17:23

**/

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) {

//1、申明一个stack

Stack stack = new Stack<>();

//遍历s String本质上是char[]

for (int i = index0; 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;

}

/**

* @param v1

* @param v2

* @return java.lang.String

* @Author fangzhenxun

* @Description 两个数相加

* @Date 2020/12/30 13:42

**/

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();

}

/**

* @param v1

* @param v2

* @return java.lang.String

* @Author fangzhenxun

* @Description 两个数相减

* @Date 2020/12/30 13:47

**/

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();

}

/**

* @param v1

* @param v2

* @return java.lang.String

* @Author fangzhenxun

* @Description 两个数向乘

* @Date 2020/12/30 13:49

**/

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();

}

/**

* @param v1

* @param v2

* @return java.lang.String

* @Author fangzhenxun

* @Description 两个数相除

* @Date 2020/12/30 13:50

**/

private static String div(String v1, String v2) {

//校验参数

checkArg(v1);

//除数不能为0

checkArg(v2, false);

BigDecimal v1Bd = new BigDecimal(v1);

BigDecimal v2Bd = new BigDecimal(v2);

return v1Bd.divide(v2Bd, 2, RoundingMode.HALF_UP).toString();

}

/**

* @Author fangzhenxun

* @Description  v1%v2 取余

* @Date 2021/1/4 22:14

* @param v1

* @param v2

* @return java.lang.String

**/

private static String mod(String v1, String v2) {

//校验参数

checkArg(v1);

//除数不能为0

checkArg(v2, false);

BigDecimal v1Bd = new BigDecimal(v1);

BigDecimal v2Bd = new BigDecimal(v2);

return v1Bd.remainder(v2Bd).toString();

}

/**

* @param mathFormula 公式字符串, 如:"1+1*2+(10-(2*(5-3)*(2-1))-4)+10/(5-0) + log(10) + log(10,12)"

*                    注意log(10) ==> ln(10) ==> log(e,10) 表示以自然数e为底的对数

* @return java.lang.String

* @Author fangzhenxun

* @Description 使用递归计算,判断表达式是否有括号,有括号,则先计算括号内的表达式,无则直接运算结果。

* @Date 2020/12/30 14:07

**/

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);

//==================================================开始计算=============================================

//计算思路:以下是计算顺序

// 1,如果有自定义的数学公式,则先计算自定义的公式

// 2, 如果有括号,则先计算括号内的(去括号)

// 3, 没有括号直接计算

String result0 = calculatorSelfMathFormula(mathFormula);

//结果保留八位小数

return new BigDecimal(result0).setScale(8,BigDecimal.ROUND_HALF_UP).toString();

}

//"1+1*2+(10-(2*(5-3)*(2-1))-4)+10/(5-0) + log(10) + max(sin(2)*6,pow(2,3))"

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);

//        Object eval = scriptEngine.eval(ss23);

//        System.out.println(eval);

System.out.println("cost时间:" + (System.currentTimeMillis() - beginTime) + "ms");

}

/**

* @Author fangzhenxun

* @Description  去除自定义公式,todo 待优化

* @Date 2020/12/31 16:49

* @param mathFormula

* @return java.lang.String

**/

private static String calculatorSelfMathFormula(String mathFormula) {

if (StringUtils.isEmpty(mathFormula)) {

throw new RuntimeException("非法参数错误!");

}

mathFormula = mathFormula.replaceAll(" ", "");

//去除自定义公式

List 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 

boolean repeat = false;

SelfMathFormulaEnum mathFormulaEnum = selfMathFormulaEnums.get(i);

//如果该公式表达式包含自定义数学公式  "1+1*2+(10-(2*(5-3)*(2-1))-4)+10/(5-0) + log(10) + 2*log(30)+ max(sin(2*(5+2))*6,pow(2*(2+8),3+2)) * max(1+3,(3-5)) + min(sin(10), sin(20))"

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);

//计算括号中的值,如果该字符串又包含其他自定义公式,则递归继续计算

//left + result0 + right

mathFormula =  left + selfMathCalculation(mathFormulaEnum.getFormulaName(), calculatorSelfMathFormula(bracketsContent)) + right;

repeat = true;

}

if (repeat) {

i = i;

}else {

i++;

}

}

}

//直接进行计算

return standardCalculation(mathFormula);

}

/**

* @Author fangzhenxun

* @Description  自定义公式计算

* @Date 2020/12/31 17:07

* @param mathFormulaName 自定义公式名称

* @param digitStr 一个具体的数值

* @return java.lang.String

**/

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);

}

/**

* @Author fangzhenxun

* @Description  标准计算,不包含自定义函数, 但包含括号与其他符号表达式

* @Date 2020/12/31 17:02

* @param str

* @return java.lang.String

**/

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 argResult = new ArrayList<>();

for (String arg : args) {

//每一个arg 都是一个算式(带上括号的)

//判断是公式表达是是否存在小括号(优先级)

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("非法算式参数!");

}

/**

* DESC:计算表达式,判断是否存在乘除运算,存在则先执行乘除运算,然后执行加减运算,返回运算结果;

* 不存在,直接运行加减运算,返回运算结果。

*

* @param str  -2.8+8*3/2+0.9 或 -2.8*6

* @return 运算结果

*/

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("数学计算公式错误!");

}

}

/**

* @Author fangzhenxun

* @Description 求给定可变参数中不等于noNum 的最小值

* @Date 2021/1/5 10:49

* @param noNum

* @param a 可变参数

* @return int

**/

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 

if (min ==noNum || (min > a[i] && a[i] != noNum)) {

min = a[i];

}

}

if (min == noNum) {

throw new RuntimeException("非法可变参数!");

}

return min;

}

/**

* @Author fangzhenxun

* @Description 获得表达式的最后连续合法数字

* @date 2021/1/6 17:49

* @param str

* @return java.lang.String

*/

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();

}

/**

* @Author fangzhenxun

* @Description 获得表达式的最后连续合法数字

* @date 2021/1/6 17:49

* @param str

* @return java.lang.String

*/

private static String firstNumber(String str) {

StringBuilder sb = new StringBuilder();

for (int i = 0; i 

char c = str.charAt(i);

//包含首字母为-

if (Character.isDigit(c) || (i != 0 && c == '.') || (i == 0 && c == '-')) {

sb.append(c);

}else {

break;

}

}

return sb.toString();

}

/**

* @Author fangzhenxun

* @Description  只用加减操作

* @Date 2021/1/4 20:02

* @param mathStr 只有加减操作的数学运算字符串: 如2.98-5-6+9-0.2-8  或  -2.0-9-5+9  或 -9-2

* @return java.lang.String

**/

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 

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;

}}

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值