Java中如何准确计算精度达18位的字符串表达式?
高精度字符串表达式计算解决方案
问题原因
你遇到的精度丢失是因为JavaScript引擎的Number类型基于IEEE 754双精度浮点数,它只能精确表示±2^53范围内的整数。你的18位数字远超这个范围,JS解析时就已经丢失精度,后续即使转成BigDecimal也无法恢复原始精确值。
解决方案
方案一:改进ScriptEngine的表达式替换逻辑
如果坚持用ScriptEngine,必须确保BigDecimal用字符串参数构造(而非让JS先解析成数字),同时正确转换运算符为BigDecimal的方法调用。注意这个方法仅适用于简单表达式,复杂优先级场景需要额外处理。
代码示例:
import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; import java.math.BigDecimal; public class ScriptEngineBigDecimalEval { public static void main(String[] args) throws Exception { String formula = "333333333333333333+222222222222222222"; // 将表达式转换为BigDecimal方法调用链 // 先把所有数字替换为new BigDecimal('数字') String processed = formula.replaceAll("(\\d+)", "new java.math.BigDecimal('$1')"); // 替换运算符为对应的BigDecimal方法 processed = processed.replace("+", ".add(") .replace("-", ".subtract(") .replace("*", ".multiply(") .replace("/", ".divide("); // 补充闭合括号(运算符数量等于括号需要补充的数量) int operatorCount = formula.split("[+\\-*/]").length - 1; StringBuilder finalExpr = new StringBuilder(processed); for (int i = 0; i < operatorCount; i++) { finalExpr.append(")"); } ScriptEngineManager manager = new ScriptEngineManager(); ScriptEngine engine = manager.getEngineByName("JavaScript"); BigDecimal result = (BigDecimal) engine.eval(finalExpr.toString()); System.out.println(result); // 输出555555555555555555 } }
方案二:使用第三方高精度表达式解析库(推荐)
无需自己处理解析和优先级,直接用支持BigDecimal的成熟库,比如EvalEx。它原生支持高精度计算和BODMAS规则,使用简单。
- 引入Maven依赖:
<dependency> <groupId>com.udojava</groupId> <artifactId>EvalEx</artifactId> <version>2.7</version> </dependency>
- 代码示例:
import com.udojava.evalex.Expression; import java.math.BigDecimal; public class EvalExBigDecimalExample { public static void main(String[] args) { String formula = "333333333333333333+222222222222222222"; Expression expression = new Expression(formula); BigDecimal result = expression.eval(); System.out.println(result); // 输出555555555555555555 } }
方案三:手动实现支持BODMAS规则的解析器
如果不能引入第三方库,可以用调度场算法实现自己的表达式解析器,全程用BigDecimal运算保证精度。
代码示例(支持+、-、*、/和括号):
import java.math.BigDecimal; import java.math.RoundingMode; import java.util.*; public class CustomBigDecimalParser { // 定义运算符优先级 private static final Map<String, Integer> OP_PRECEDENCE = new HashMap<>(); static { OP_PRECEDENCE.put("+", 1); OP_PRECEDENCE.put("-", 1); OP_PRECEDENCE.put("*", 2); OP_PRECEDENCE.put("/", 2); } // 词法分析:拆分表达式为数字、运算符、括号 private static List<String> tokenize(String expr) { List<String> tokens = new ArrayList<>(); StringBuilder numBuilder = new StringBuilder(); for (char c : expr.toCharArray()) { if (Character.isDigit(c)) { numBuilder.append(c); } else if ("+-*/()".indexOf(c) != -1) { if (numBuilder.length() > 0) { tokens.add(numBuilder.toString()); numBuilder.setLength(0); } tokens.add(String.valueOf(c)); } } if (numBuilder.length() > 0) { tokens.add(numBuilder.toString()); } return tokens; } // 中缀表达式转后缀表达式(调度场算法) private static List<String> infixToPostfix(List<String> tokens) { List<String> postfix = new ArrayList<>(); Deque<String> opStack = new ArrayDeque<>(); for (String token : tokens) { if (token.matches("\\d+")) { postfix.add(token); } else if (token.equals("(")) { opStack.push(token); } else if (token.equals(")")) { while (!opStack.isEmpty() && !opStack.peek().equals("(")) { postfix.add(opStack.pop()); } opStack.pop(); // 弹出左括号 } else { // 处理运算符 while (!opStack.isEmpty() && !opStack.peek().equals("(") && OP_PRECEDENCE.get(opStack.peek()) >= OP_PRECEDENCE.get(token)) { postfix.add(opStack.pop()); } opStack.push(token); } } while (!opStack.isEmpty()) { postfix.add(opStack.pop()); } return postfix; } // 计算后缀表达式 private static BigDecimal calculatePostfix(List<String> postfix) { Deque<BigDecimal> numStack = new ArrayDeque<>(); for (String token : postfix) { if (token.matches("\\d+")) { numStack.push(new BigDecimal(token)); } else { BigDecimal b = numStack.pop(); BigDecimal a = numStack.pop(); switch (token) { case "+": numStack.push(a.add(b)); break; case "-": numStack.push(a.subtract(b)); break; case "*": numStack.push(a.multiply(b)); break; case "/": // 除法指定18位精度和四舍五入模式 numStack.push(a.divide(b, 18, RoundingMode.HALF_UP)); break; } } } return numStack.pop(); } public static BigDecimal evaluate(String expr) { List<String> tokens = tokenize(expr); List<String> postfix = infixToPostfix(tokens); return calculatePostfix(postfix); } public static void main(String[] args) { String formula = "333333333333333333+222222222222222222"; BigDecimal result = evaluate(formula); System.out.println(result); // 输出555555555555555555 } }
内容的提问来源于stack exchange,提问作者akaparadox
相关产品推荐
相关产品推荐

