From 695bc0ae4dbcfda3f212bac8f62739ffbfc55498 Mon Sep 17 00:00:00 2001 From: Looly Date: Mon, 1 Dec 2025 22:37:30 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D`Calculator.conversion(String?= =?UTF-8?q?=20expression)`=E6=96=B9=E6=B3=95=E8=AE=A1=E7=AE=97=E5=8C=85?= =?UTF-8?q?=E5=90=AB=E7=A7=91=E5=AD=A6=E8=AE=A1=E6=95=B0=E6=B3=95=E8=A1=A8?= =?UTF-8?q?=E8=BE=BE=E5=BC=8F=E7=9A=84=E5=80=BC=E6=97=B6=E9=80=BB=E8=BE=91?= =?UTF-8?q?=E6=9C=89=E8=AF=AF=EF=BC=8C=E7=BB=93=E6=9E=9C=E4=B8=8D=E7=AC=A6?= =?UTF-8?q?=E5=90=88=E9=A2=84=E6=9C=9F=EF=BC=88pr#4172@Github=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cn/hutool/v7/core/math/Calculator.java | 91 +++++++++++++++---- .../hutool/v7/core/math/CalculatorTest.java | 30 ++++++ .../java/cn/hutool/v7/json/JSONUtilTest.java | 8 +- 3 files changed, 110 insertions(+), 19 deletions(-) diff --git a/hutool-core/src/main/java/cn/hutool/v7/core/math/Calculator.java b/hutool-core/src/main/java/cn/hutool/v7/core/math/Calculator.java index 1e953057c..a516d618b 100644 --- a/hutool-core/src/main/java/cn/hutool/v7/core/math/Calculator.java +++ b/hutool-core/src/main/java/cn/hutool/v7/core/math/Calculator.java @@ -176,35 +176,90 @@ public class Calculator { } /** - * 将表达式中负数的符号更改 - * - * @param expression 例如-2+-1*(-3E-2)-(-1) 被转为 ~2+~1*(~3E~2)-(~1) - * @return 转换后的字符串 + * 将表达式中的一元负号转换为内部标记(~),便于后续解析。 + * 规则说明: + * - 科学计数法整体识别为数字,e/E 后的 + 或 - 属于指数符号,不参与一元符号折叠。 + * - 一元 + / - 仅在表达式开头或运算符、左括号之后生效;可折叠连续符号,如 --3、+-3 -> ~3。 + * 示例: + * - 输入:-2+-1*(-3E-2)-(-1) + * - 输出:~2+~1*(~3E~2)-(~1) */ private static String transform(String expression) { expression = StrUtil.cleanBlank(expression); expression = StrUtil.removeSuffix(expression, "="); final char[] arr = expression.toCharArray(); + + final StringBuilder out = new StringBuilder(arr.length); for (int i = 0; i < arr.length; i++) { - if (arr[i] == '-') { - if (i == 0) { - arr[i] = '~'; - } else { - final char c = arr[i - 1]; - if (c == '+' || c == '-' || c == '*' || c == '/' || c == '(' || c == 'E' || c == 'e') { - arr[i] = '~'; + final char c = arr[i]; + + // 把x或X当作 * + if (CharUtil.equals(c, 'x', true)) { + out.append('*'); + continue; + } + + // 若是'+'或'-',需要判断是指数符号、二元运算符还是一元运算符序列 + if (c == '+' || c == '-') { + // 如果前一个已写入的字符为'e'或'E',则视作科学计数法的符号 + final int outLen = out.length(); + if (outLen > 0) { + final char prevOut = out.charAt(outLen - 1); + if (prevOut == 'e' || prevOut == 'E') { + // 在e/E 后: + // '+' 可以安全丢弃(1e+3 == 1e3) + // '-' 必须保留但不能被当作二元运算符,故用'~'临时替代,后续再还原为'-' + if (c == '-') { + out.append('~'); + } + continue; } } - } else if(CharUtil.equals(arr[i], 'x', true)){ - // issue#3787 x转换为* - arr[i] = '*'; + + // 查找前一个非空字符(原串中的),用于判断是否为一元上下文 + int j = i - 1; + while (j >= 0 && Character.isWhitespace(arr[j])) j--; + final boolean unaryContext = (j < 0) || isPrevCharOperatorOrLeftParen(arr[j]); + + if (unaryContext) { + // 收集连续的一系列 + 或 -(例如 --+ - -> 合并为一个净符号) + int k = i; + int minusCount = 0; + while (k < arr.length && (arr[k] == '+' || arr[k] == '-')) { + if (arr[k] == '-') minusCount++; + k++; + } + final boolean netNegative = (minusCount % 2 == 1); + if (netNegative) { + // 用~标记一元负号(与原实现保持兼容) + out.append('~'); + } + i = k - 1; + } else { + //二元运算符,直接写入 + 或 - + out.append(c); + } + continue; } + //其它字符(包括数字、字母、括号、e、E、小数点等)直接追加 + out.append(c); } - if (arr[0] == '~' && (arr.length > 1 && arr[1] == '(')) { - arr[0] = '-'; - return "0" + new String(arr); + + // 特殊处理:如果开头为 "~(",原实现会将其转为 "0~(" 形式改为以0开始的负括号处理 + final String result = out.toString(); + final char[] resArr = result.toCharArray(); + if (resArr.length >= 2 && resArr[0] == '~' && resArr[1] == '(') { + resArr[0] = '-'; + return "0" + new String(resArr); } else { - return new String(arr); + return result; } } + + /** + * 判断给定位置前一个非空字符是否为运算符或左括号(用于判定是否为一元上下文) + */ + private static boolean isPrevCharOperatorOrLeftParen(final char c) { + return c == '+' || c == '-' || c == '*' || c == '/' || c == '('; + } } diff --git a/hutool-core/src/test/java/cn/hutool/v7/core/math/CalculatorTest.java b/hutool-core/src/test/java/cn/hutool/v7/core/math/CalculatorTest.java index 25bc873bf..a7688c6ec 100644 --- a/hutool-core/src/test/java/cn/hutool/v7/core/math/CalculatorTest.java +++ b/hutool-core/src/test/java/cn/hutool/v7/core/math/CalculatorTest.java @@ -83,4 +83,34 @@ public class CalculatorTest { result = calculator1.calculate("0+50/100X(1/0.5)"); assertEquals(1D, result); } + + @Test + public void scientificNotationPlusTest() { + // 测试科学记数法中的 + 号是否被正确处理 + final double conversion = Calculator.conversion("1e+3"); + assertEquals(1000.0, conversion, 0.001); + + // 更复杂的科学记数法表达式 + final double conversion2 = Calculator.conversion("2.5e+2 + 1.0e-1"); + assertEquals(250.1, conversion2, 0.001); + } + + @Test + public void unaryOperatorConsistencyTest() { + // 测试连续的一元运算符:双重负号--3,等同于 -( -3 ) = 3 + final double conversion = Calculator.conversion("--3"); + assertEquals(3.0, conversion, 0.001); + + // 测试连续的一元运算符:正号后跟负号,等同于 +( -3 ) = -3 + final double conversion2 = Calculator.conversion("+-3"); + assertEquals(-3.0, conversion2, 0.001); + + // 测试表达式开始的一元+运算符 + final double conversion3 = Calculator.conversion("+3"); + assertEquals(3.0, conversion3, 0.001); + + // 测试表达式开始的一元-运算符 + final double conversion4 = Calculator.conversion("-3"); + assertEquals(-3.0, conversion4, 0.001); + } } diff --git a/hutool-json/src/test/java/cn/hutool/v7/json/JSONUtilTest.java b/hutool-json/src/test/java/cn/hutool/v7/json/JSONUtilTest.java index 830e8f4b3..4f92518f3 100644 --- a/hutool-json/src/test/java/cn/hutool/v7/json/JSONUtilTest.java +++ b/hutool-json/src/test/java/cn/hutool/v7/json/JSONUtilTest.java @@ -16,7 +16,6 @@ package cn.hutool.v7.json; -import lombok.Data; import cn.hutool.v7.core.collection.ListUtil; import cn.hutool.v7.core.date.DateTime; import cn.hutool.v7.core.date.DateUtil; @@ -24,6 +23,7 @@ import cn.hutool.v7.core.map.MapUtil; import cn.hutool.v7.json.test.bean.Price; import cn.hutool.v7.json.test.bean.UserA; import cn.hutool.v7.json.test.bean.UserC; +import lombok.Data; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -382,4 +382,10 @@ public class JSONUtilTest { final String jsonStr = JSONUtil.toJsonStr(userId); assertEquals("10101010", jsonStr); } + + @Test + void parseEmptyTest(){ + final JSON parse = JSONUtil.parse(""); + assertNull(parse); + } }