package jnpf.elnFormula; import ch.obermuhlner.math.big.BigDecimalMath; import java.math.BigDecimal; import java.math.MathContext; import java.math.RoundingMode; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; public final class FormulaEvaluator { private static final MathContext MATH_CONTEXT = MathContext.DECIMAL128; private static final BigDecimal TWO = new BigDecimal("2"); private static final BigDecimal ONE_HUNDRED = new BigDecimal("100"); private static final BigDecimal MAX_EXP_INPUT = new BigDecimal("1000"); private static final int MAX_EXPONENT = 999; private static final int MAX_SCALE = 12; private static final int MAX_SIGNIFICANT_DIGITS = 34; private static final int MAX_NEWTON_ITERATIONS = 256; private static final Map UNITS = units(); private final FormulaFunctionRegistry functions; private final FormulaLimits limits; public FormulaEvaluator(FormulaFunctionRegistry functions) { this(functions, FormulaLimits.DEFAULTS); } public FormulaEvaluator(FormulaFunctionRegistry functions, FormulaLimits limits) { if (functions == null || limits == null) { throw new IllegalArgumentException("functions and limits must not be null"); } this.functions = functions; this.limits = limits; } public FormulaValue evaluate(FormulaAst.Node node, Map parameters) { if (node == null || parameters == null) { throw new IllegalArgumentException("node and parameters must not be null"); } for (FormulaValue value : parameters.values()) { if (value != null && value.getType() == FormulaType.DECIMAL_LIST && value.getDecimalList().size() > limits.getMaxListItems()) { throw error("LIST_ITEM_LIMIT_EXCEEDED", "数值列表项数超过限制", node); } } return evaluate(node, parameters, new EvaluationContext(limits.getMaxEvaluationNodes())); } public FormulaValue round(FormulaValue value, int scale, RoundingMode mode) { if (value == null || mode == null) { throw new IllegalArgumentException("value and mode must not be null"); } requirePresent(value, null); if (value.getType() != FormulaType.DECIMAL) { throw new FormulaException("TYPE_MISMATCH", "只有 DECIMAL 结果可以修约"); } if (mode != RoundingMode.HALF_UP && mode != RoundingMode.HALF_EVEN && mode != RoundingMode.UP && mode != RoundingMode.DOWN) { throw new FormulaException("UNSUPPORTED_ROUNDING_MODE", "不支持的修约模式"); } validateScale(scale, null); return FormulaValue.decimal(value.getDecimal().setScale(scale, mode)); } private FormulaValue evaluate(FormulaAst.Node node, Map parameters, EvaluationContext context) { context.visit(node); if (node instanceof FormulaAst.Literal) { return literal((FormulaAst.Literal) node); } if (node instanceof FormulaAst.Parameter) { FormulaAst.Parameter parameter = (FormulaAst.Parameter) node; FormulaValue value = parameters.get(parameter.getCode()); if (value == null) { throw error("MISSING_INPUT", "缺少参数值: " + parameter.getCode(), node); } return value; } if (node instanceof FormulaAst.Unary) { return unary((FormulaAst.Unary) node, parameters, context); } if (node instanceof FormulaAst.Binary) { return binary((FormulaAst.Binary) node, parameters, context); } if (node instanceof FormulaAst.FunctionCall) { return function((FormulaAst.FunctionCall) node, parameters, context); } throw error("TYPE_MISMATCH", "不支持的公式节点", node); } private FormulaValue unary(FormulaAst.Unary unary, Map parameters, EvaluationContext context) { FormulaValue operand = evaluate(unary.getOperand(), parameters, context); if ("!".equals(unary.getOperator())) { return FormulaValue.bool(!booleanValue(operand, unary)); } BigDecimal value = decimalValue(operand, unary); if ("+".equals(unary.getOperator())) { return FormulaValue.decimal(value.round(MATH_CONTEXT)); } if ("-".equals(unary.getOperator())) { return FormulaValue.decimal(value.negate(MATH_CONTEXT)); } throw error("TYPE_MISMATCH", "不支持的一元运算符", unary); } private FormulaValue binary(FormulaAst.Binary binary, Map parameters, EvaluationContext context) { String operator = binary.getOperator(); FormulaValue left = evaluate(binary.getLeft(), parameters, context); if ("&&".equals(operator)) { boolean leftValue = booleanValue(left, binary); return leftValue ? FormulaValue.bool(booleanValue(evaluate(binary.getRight(), parameters, context), binary)) : FormulaValue.bool(false); } if ("||".equals(operator)) { boolean leftValue = booleanValue(left, binary); return leftValue ? FormulaValue.bool(true) : FormulaValue.bool(booleanValue(evaluate(binary.getRight(), parameters, context), binary)); } FormulaValue right = evaluate(binary.getRight(), parameters, context); if ("==".equals(operator) || "!=".equals(operator)) { boolean equal = equalValues(left, right, binary); return FormulaValue.bool("==".equals(operator) ? equal : !equal); } BigDecimal leftDecimal = decimalValue(left, binary); BigDecimal rightDecimal = decimalValue(right, binary); if ("+".equals(operator)) { return FormulaValue.decimal(leftDecimal.add(rightDecimal, MATH_CONTEXT)); } if ("-".equals(operator)) { return FormulaValue.decimal(leftDecimal.subtract(rightDecimal, MATH_CONTEXT)); } if ("*".equals(operator)) { return FormulaValue.decimal(leftDecimal.multiply(rightDecimal, MATH_CONTEXT)); } if ("/".equals(operator)) { requireNonZero(rightDecimal, binary); return FormulaValue.decimal(leftDecimal.divide(rightDecimal, MATH_CONTEXT)); } if ("%".equals(operator)) { requireNonZero(rightDecimal, binary); try { return FormulaValue.decimal(leftDecimal.remainder(rightDecimal) .round(MATH_CONTEXT)); } catch (ArithmeticException ex) { throw error("NUMERIC_ERROR", "余数运算失败", binary); } } if ("^".equals(operator)) { return FormulaValue.decimal(power(leftDecimal, rightDecimal, binary)); } int comparison = leftDecimal.compareTo(rightDecimal); if (">".equals(operator)) { return FormulaValue.bool(comparison > 0); } if (">=".equals(operator)) { return FormulaValue.bool(comparison >= 0); } if ("<".equals(operator)) { return FormulaValue.bool(comparison < 0); } if ("<=".equals(operator)) { return FormulaValue.bool(comparison <= 0); } throw error("TYPE_MISMATCH", "不支持的二元运算符", binary); } private FormulaValue function(FormulaAst.FunctionCall call, Map parameters, EvaluationContext context) { String name = call.getName(); if (!functions.contains(name)) { throw error("UNKNOWN_FUNCTION", "未知函数: " + name, call); } List arguments = call.getArguments(); if (arguments.size() > limits.getMaxFunctionArguments()) { throw error("FUNCTION_ARGUMENT_LIMIT_EXCEEDED", "函数参数数量超过限制", call); } if ("IF".equals(name)) { count(arguments, 3, 3, call); boolean condition = booleanValue(evaluate(arguments.get(0), parameters, context), call); return evaluate(arguments.get(condition ? 1 : 2), parameters, context); } if ("COALESCE".equals(name)) { count(arguments, 1, limits.getMaxFunctionArguments(), call); FormulaValue result = FormulaValue.nullValue(); for (FormulaAst.Node argument : arguments) { result = evaluate(argument, parameters, context); if (!result.isNull()) { return result; } } return result; } if ("IS_BLANK".equals(name)) { count(arguments, 1, 1, call); return FormulaValue.bool(isBlank(evaluate(arguments.get(0), parameters, context))); } List values = evaluateAll(arguments, parameters, context); if ("ABS".equals(name)) { count(values, 1, 1, call); return FormulaValue.decimal(decimalValue(values.get(0), call).abs(MATH_CONTEXT)); } if ("SQRT".equals(name)) { count(values, 1, 1, call); return FormulaValue.decimal(squareRoot(decimalValue(values.get(0), call), call)); } if ("POW".equals(name)) { count(values, 2, 2, call); return FormulaValue.decimal(power(decimalValue(values.get(0), call), decimalValue(values.get(1), call), call)); } if ("LN".equals(name) || "LOG10".equals(name)) { count(values, 1, 1, call); BigDecimal value = decimalValue(values.get(0), call); requirePositiveLogValue(value, call); try { return FormulaValue.decimal("LN".equals(name) ? BigDecimalMath.log(value, MATH_CONTEXT) : BigDecimalMath.log10(value, MATH_CONTEXT)); } catch (ArithmeticException ex) { throw error("NUMERIC_ERROR", "对数计算失败", call); } } if ("LOG".equals(name)) { count(values, 2, 2, call); BigDecimal value = decimalValue(values.get(0), call); BigDecimal base = decimalValue(values.get(1), call); requirePositiveLogValue(value, call); if (base.signum() <= 0 || base.compareTo(BigDecimal.ONE) == 0) { throw error("INVALID_LOG_BASE", "对数底数必须大于 0 且不能等于 1", call); } try { return FormulaValue.decimal(BigDecimalMath.log(value, MATH_CONTEXT) .divide(BigDecimalMath.log(base, MATH_CONTEXT), MATH_CONTEXT)); } catch (ArithmeticException ex) { throw error("NUMERIC_ERROR", "对数计算失败", call); } } if ("EXP".equals(name)) { count(values, 1, 1, call); BigDecimal value = decimalValue(values.get(0), call); if (value.abs().compareTo(MAX_EXP_INPUT) > 0) { throw error("EXPONENT_OUT_OF_RANGE", "指数参数必须在 -1000 到 1000 之间", call); } try { return FormulaValue.decimal(BigDecimalMath.exp(value, MATH_CONTEXT)); } catch (ArithmeticException ex) { throw error("NUMERIC_ERROR", "指数计算失败", call); } } if ("NTH_ROOT".equals(name)) { count(values, 2, 2, call); BigDecimal value = decimalValue(values.get(0), call); int degree = rootDegree(decimalValue(values.get(1), call), call); if (value.signum() < 0 && degree % 2 == 0) { throw error("NEGATIVE_EVEN_ROOT", "负数不能执行偶次方根", call); } try { BigDecimal root = BigDecimalMath.root(value.abs(), BigDecimal.valueOf(degree), MATH_CONTEXT); return FormulaValue.decimal(value.signum() < 0 ? root.negate() : root); } catch (ArithmeticException ex) { throw error("NUMERIC_ERROR", "方根计算失败", call); } } if ("MIN".equals(name) || "MAX".equals(name)) { count(values, 1, limits.getMaxFunctionArguments(), call); BigDecimal result = decimalValue(values.get(0), call); for (int i = 1; i < values.size(); i++) { BigDecimal candidate = decimalValue(values.get(i), call); if (("MIN".equals(name) && candidate.compareTo(result) < 0) || ("MAX".equals(name) && candidate.compareTo(result) > 0)) { result = candidate; } } return FormulaValue.decimal(result.round(MATH_CONTEXT)); } if ("SUM".equals(name) || "MEAN".equals(name) || "STDDEV_S".equals(name) || "STDDEV_P".equals(name) || "VAR_S".equals(name) || "VAR_P".equals(name) || "RSD".equals(name) || "COUNT".equals(name) || "MEDIAN".equals(name) || "GEOMEAN".equals(name)) { count(values, 1, 1, call); List items = decimalList(values.get(0), call); return statistic(name, items, call); } if ("WEIGHTED_MEAN".equals(name)) { count(values, 2, 2, call); return FormulaValue.decimal(weightedMean(decimalList(values.get(0), call), decimalList(values.get(1), call), call)); } if ("SLOPE".equals(name) || "INTERCEPT".equals(name) || "CORREL".equals(name)) { count(values, 2, 2, call); RegressionStats stats = regression(decimalList(values.get(0), call), decimalList(values.get(1), call), call); if ("SLOPE".equals(name)) { return FormulaValue.decimal(stats.slope); } if ("INTERCEPT".equals(name)) { return FormulaValue.decimal(stats.intercept); } if (stats.sumYY.signum() == 0) { throw error("ZERO_VARIANCE", "相关系数的 y 值方差不能为 0", call); } BigDecimal varianceProduct = stats.sumXX.multiply(stats.sumYY); if (stats.sumXY.multiply(stats.sumXY).compareTo(varianceProduct) == 0) { return FormulaValue.decimal(stats.sumXY.signum() < 0 ? BigDecimal.ONE.negate() : BigDecimal.ONE); } BigDecimal denominator = squareRoot(varianceProduct, call); return FormulaValue.decimal(clampCorrelation( stats.sumXY.divide(denominator, MATH_CONTEXT))); } if ("LINEAR_PREDICT".equals(name)) { count(values, 3, 3, call); BigDecimal value = decimalValue(values.get(0), call); RegressionStats stats = regression(decimalList(values.get(1), call), decimalList(values.get(2), call), call); return FormulaValue.decimal(stats.predict(value)); } if ("ROUND".equals(name) || "ROUND_UP".equals(name) || "ROUND_DOWN".equals(name) || "ROUND_HALF_EVEN".equals(name)) { count(values, 2, 2, call); int scale = scale(decimalValue(values.get(1), call), call); RoundingMode mode = "ROUND".equals(name) ? RoundingMode.HALF_UP : ("ROUND_UP".equals(name) ? RoundingMode.UP : ("ROUND_DOWN".equals(name) ? RoundingMode.DOWN : RoundingMode.HALF_EVEN)); return round(FormulaValue.decimal(decimalValue(values.get(0), call)), scale, mode); } if ("ROUND_SIG".equals(name) || "ROUND_SIG_HALF_EVEN".equals(name)) { count(values, 2, 2, call); BigDecimal value = decimalValue(values.get(0), call); int digits = significantDigits(decimalValue(values.get(1), call), call); RoundingMode mode = "ROUND_SIG".equals(name) ? RoundingMode.HALF_UP : RoundingMode.HALF_EVEN; return FormulaValue.decimal(value.round(new MathContext(digits, mode))); } if ("CONVERT".equals(name)) { count(values, 3, 3, call); return convert(values, call); } if ("LOOKUP_EXACT".equals(name)) { lookupCount(values, 3, call); return lookupExact(values, call); } if ("LOOKUP_LINEAR".equals(name)) { lookupCount(values, 5, call); return lookupLinear(values, call); } throw error("UNKNOWN_FUNCTION", "未知函数: " + name, call); } private FormulaValue statistic(String name, List items, FormulaAst.Node node) { if ("COUNT".equals(name)) { return FormulaValue.decimal(BigDecimal.valueOf(items.size())); } BigDecimal sum = BigDecimal.ZERO; for (FormulaValue item : items) { sum = sum.add(item.getDecimal()); } if ("SUM".equals(name)) { return FormulaValue.decimal(sum.round(MATH_CONTEXT)); } if (items.isEmpty()) { throw error("INSUFFICIENT_SAMPLE", "数值列表不能为空", node); } if ("MEDIAN".equals(name)) { List sorted = new ArrayList<>(items.size()); for (FormulaValue item : items) { sorted.add(item.getDecimal()); } Collections.sort(sorted); int middle = sorted.size() / 2; if (sorted.size() % 2 == 1) { return FormulaValue.decimal(sorted.get(middle)); } return FormulaValue.decimal(sorted.get(middle - 1).add(sorted.get(middle)) .divide(TWO, MATH_CONTEXT)); } if ("GEOMEAN".equals(name)) { boolean containsZero = false; for (FormulaValue item : items) { BigDecimal value = item.getDecimal(); if (value.signum() < 0) { throw error("GEOMEAN_DOMAIN_ERROR", "几何平均值不接受负数", node); } if (value.signum() == 0) { containsZero = true; } } if (containsZero) { return FormulaValue.decimal(BigDecimal.ZERO); } BigDecimal product = BigDecimal.ONE; for (FormulaValue item : items) { BigDecimal value = item.getDecimal(); product = product.multiply(value, MATH_CONTEXT); } try { return FormulaValue.decimal(BigDecimalMath.root(product, BigDecimal.valueOf(items.size()), MATH_CONTEXT)); } catch (ArithmeticException ex) { throw error("NUMERIC_ERROR", "几何平均值计算失败", node); } } BigDecimal mean = sum.divide(BigDecimal.valueOf(items.size()), MATH_CONTEXT); if ("MEAN".equals(name)) { return FormulaValue.decimal(mean); } boolean sample = "STDDEV_S".equals(name) || "VAR_S".equals(name) || "RSD".equals(name); if (sample && items.size() < 2) { throw error("INSUFFICIENT_SAMPLE", "样本数至少为 2", node); } BigDecimal variance = variance(items, sample); if ("VAR_S".equals(name) || "VAR_P".equals(name)) { return FormulaValue.decimal(variance); } BigDecimal standardDeviation = squareRoot(variance, node); if ("STDDEV_S".equals(name) || "STDDEV_P".equals(name)) { return FormulaValue.decimal(standardDeviation); } if (mean.signum() == 0) { throw error("ZERO_MEAN", "RSD 的均值不能为 0", node); } return FormulaValue.decimal(standardDeviation.divide(mean.abs(), MATH_CONTEXT) .multiply(ONE_HUNDRED, MATH_CONTEXT)); } private static BigDecimal variance(List items, boolean sample) { BigDecimal anchor = items.get(0).getDecimal(); BigDecimal deltaSum = BigDecimal.ZERO; BigDecimal deltaSquaredSum = BigDecimal.ZERO; for (FormulaValue item : items) { BigDecimal delta = item.getDecimal().subtract(anchor); deltaSum = deltaSum.add(delta); deltaSquaredSum = deltaSquaredSum.add(delta.multiply(delta)); } BigDecimal count = BigDecimal.valueOf(items.size()); BigDecimal numerator = count.multiply(deltaSquaredSum) .subtract(deltaSum.multiply(deltaSum)); BigDecimal divisor = count.multiply(BigDecimal.valueOf( sample ? items.size() - 1L : items.size())); return numerator.divide(divisor, MATH_CONTEXT); } private static BigDecimal weightedMean(List items, List weights, FormulaAst.Node node) { requireSameNonEmptyLength(items, weights, node); BigDecimal weightedSum = BigDecimal.ZERO; BigDecimal weightSum = BigDecimal.ZERO; for (int i = 0; i < items.size(); i++) { BigDecimal weight = weights.get(i).getDecimal(); if (weight.signum() < 0) { throw error("INVALID_WEIGHT", "权重不能为负数", node); } weightedSum = weightedSum.add(items.get(i).getDecimal() .multiply(weight)); weightSum = weightSum.add(weight); } if (weightSum.signum() <= 0) { throw error("INVALID_WEIGHT", "权重和必须大于 0", node); } return weightedSum.divide(weightSum, MATH_CONTEXT); } private static RegressionStats regression(List xValues, List yValues, FormulaAst.Node node) { requireSameNonEmptyLength(xValues, yValues, node); if (xValues.size() < 2) { throw error("INSUFFICIENT_SAMPLE", "线性回归至少需要 2 组数据", node); } BigDecimal count = BigDecimal.valueOf(xValues.size()); BigDecimal sumX = BigDecimal.ZERO; BigDecimal sumY = BigDecimal.ZERO; BigDecimal anchorX = xValues.get(0).getDecimal(); BigDecimal anchorY = yValues.get(0).getDecimal(); BigDecimal deltaXSum = BigDecimal.ZERO; BigDecimal deltaYSum = BigDecimal.ZERO; BigDecimal deltaXXSum = BigDecimal.ZERO; BigDecimal deltaYYSum = BigDecimal.ZERO; BigDecimal deltaXYSum = BigDecimal.ZERO; for (int i = 0; i < xValues.size(); i++) { BigDecimal x = xValues.get(i).getDecimal(); BigDecimal y = yValues.get(i).getDecimal(); BigDecimal deltaX = x.subtract(anchorX); BigDecimal deltaY = y.subtract(anchorY); sumX = sumX.add(x); sumY = sumY.add(y); deltaXSum = deltaXSum.add(deltaX); deltaYSum = deltaYSum.add(deltaY); deltaXXSum = deltaXXSum.add(deltaX.multiply(deltaX)); deltaYYSum = deltaYYSum.add(deltaY.multiply(deltaY)); deltaXYSum = deltaXYSum.add(deltaX.multiply(deltaY)); } BigDecimal sumXX = count.multiply(deltaXXSum) .subtract(deltaXSum.multiply(deltaXSum)); BigDecimal sumYY = count.multiply(deltaYYSum) .subtract(deltaYSum.multiply(deltaYSum)); BigDecimal sumXY = count.multiply(deltaXYSum) .subtract(deltaXSum.multiply(deltaYSum)); if (sumXX.signum() == 0) { throw error("ZERO_VARIANCE", "线性回归的 x 值方差不能为 0", node); } BigDecimal slope = sumXY.divide(sumXX, MATH_CONTEXT); BigDecimal commonDenominator = count.multiply(sumXX); BigDecimal interceptNumerator = sumY.multiply(sumXX) .subtract(sumXY.multiply(sumX)); BigDecimal intercept = interceptNumerator.divide(commonDenominator, MATH_CONTEXT); return new RegressionStats(sumXX, sumYY, sumXY, slope, intercept, count.multiply(sumXY), interceptNumerator, commonDenominator); } private static BigDecimal clampCorrelation(BigDecimal value) { if (value.compareTo(BigDecimal.ONE) > 0) { return BigDecimal.ONE; } if (value.compareTo(BigDecimal.ONE.negate()) < 0) { return BigDecimal.ONE.negate(); } return value; } private static void requireSameNonEmptyLength(List left, List right, FormulaAst.Node node) { if (left.size() != right.size()) { throw error("LIST_LENGTH_MISMATCH", "数值列表长度必须一致", node); } if (left.isEmpty()) { throw error("INSUFFICIENT_SAMPLE", "数值列表不能为空", node); } } private FormulaValue convert(List values, FormulaAst.Node node) { BigDecimal value = decimalValue(values.get(0), node); String fromCode = textValue(values.get(1), node); String toCode = textValue(values.get(2), node); Unit from = UNITS.get(fromCode); Unit to = UNITS.get(toCode); if (from == null || to == null) { throw error("UNKNOWN_UNIT", "不支持的单位", node); } if (!from.dimension.equals(to.dimension)) { throw error("UNIT_DIMENSION_MISMATCH", "单位维度不一致", node); } return FormulaValue.decimal(value.multiply(from.baseFactor, MATH_CONTEXT) .divide(to.baseFactor, MATH_CONTEXT)); } private FormulaValue lookupExact(List values, FormulaAst.Node node) { BigDecimal key = decimalValue(values.get(0), node); for (int i = 1; i < values.size(); i += 2) { if (key.compareTo(decimalValue(values.get(i), node)) == 0) { return FormulaValue.decimal(decimalValue(values.get(i + 1), node)); } } throw error("LOOKUP_NOT_FOUND", "没有匹配的查值点", node); } private FormulaValue lookupLinear(List values, FormulaAst.Node node) { BigDecimal key = decimalValue(values.get(0), node); List xs = new ArrayList<>((values.size() - 1) / 2); List ys = new ArrayList<>((values.size() - 1) / 2); for (int i = 1; i < values.size(); i += 2) { BigDecimal x = decimalValue(values.get(i), node); if (!xs.isEmpty() && x.compareTo(xs.get(xs.size() - 1)) <= 0) { throw error("LOOKUP_POINTS_NOT_INCREASING", "线性插值点必须严格递增", node); } xs.add(x); ys.add(decimalValue(values.get(i + 1), node)); } if (key.compareTo(xs.get(0)) < 0 || key.compareTo(xs.get(xs.size() - 1)) > 0) { throw error("OUT_OF_RANGE", "查值超出插值范围", node); } for (int i = 0; i < xs.size(); i++) { int comparison = key.compareTo(xs.get(i)); if (comparison == 0) { return FormulaValue.decimal(ys.get(i)); } if (comparison < 0) { BigDecimal ratio = key.subtract(xs.get(i - 1), MATH_CONTEXT) .divide(xs.get(i).subtract(xs.get(i - 1), MATH_CONTEXT), MATH_CONTEXT); BigDecimal result = ys.get(i - 1).add(ys.get(i).subtract(ys.get(i - 1), MATH_CONTEXT).multiply(ratio, MATH_CONTEXT), MATH_CONTEXT); return FormulaValue.decimal(result); } } return FormulaValue.decimal(ys.get(ys.size() - 1)); } private static BigDecimal power(BigDecimal base, BigDecimal exponent, FormulaAst.Node node) { BigDecimal normalized = exponent.stripTrailingZeros(); if (normalized.scale() > 0 || normalized.abs().compareTo( BigDecimal.valueOf(MAX_EXPONENT)) > 0) { throw error("INVALID_EXPONENT", "指数必须是绝对值不超过 999 的整数", node); } int value; try { value = normalized.intValueExact(); } catch (ArithmeticException ex) { throw error("INVALID_EXPONENT", "指数必须是绝对值不超过 999 的整数", node); } if (value < 0) { if (base.signum() == 0) { throw error("DIVIDE_BY_ZERO", "0 不能取负指数", node); } return BigDecimal.ONE.divide(base.pow(-value, MATH_CONTEXT), MATH_CONTEXT); } return base.pow(value, MATH_CONTEXT); } private static BigDecimal squareRoot(BigDecimal value, FormulaAst.Node node) { if (value.signum() < 0) { throw error("NEGATIVE_SQUARE_ROOT", "负数不能开平方", node); } if (value.signum() == 0) { return BigDecimal.ZERO; } int adjustedExponent = value.precision() - value.scale() - 1; BigDecimal current = BigDecimal.ONE.scaleByPowerOfTen(Math.floorDiv(adjustedExponent, 2)); BigDecimal previous = null; for (int i = 0; i < MAX_NEWTON_ITERATIONS; i++) { BigDecimal next = current.add(value.divide(current, MATH_CONTEXT), MATH_CONTEXT) .divide(TWO, MATH_CONTEXT); if (next.compareTo(current) == 0 || (previous != null && next.compareTo(previous) == 0)) { return next.round(MATH_CONTEXT); } previous = current; current = next; } throw error("NUMERIC_DID_NOT_CONVERGE", "平方根迭代未收敛", node); } private List evaluateAll(List arguments, Map parameters, EvaluationContext context) { List result = new ArrayList<>(arguments.size()); for (FormulaAst.Node argument : arguments) { result.add(evaluate(argument, parameters, context)); } return result; } private static FormulaValue literal(FormulaAst.Literal literal) { switch (literal.getKind()) { case DECIMAL: return FormulaValue.decimal(literal.getDecimalValue()); case TEXT: return FormulaValue.text((String) literal.getValue()); case BOOLEAN: return FormulaValue.bool((Boolean) literal.getValue()); case NULL: return FormulaValue.nullValue(); default: throw new IllegalStateException("unknown literal kind"); } } private static boolean equalValues(FormulaValue left, FormulaValue right, FormulaAst.Node node) { requirePresent(left, node); requirePresent(right, node); if (left.getType() != right.getType() || (left.getType() != FormulaType.DECIMAL && left.getType() != FormulaType.TEXT && left.getType() != FormulaType.BOOLEAN)) { throw error("TYPE_MISMATCH", "相等比较仅支持相同的标量类型", node); } return left.equals(right); } private static boolean isBlank(FormulaValue value) { return value.isNull() || (value.getType() == FormulaType.TEXT && isBlankText(value.getText())) || (value.getType() == FormulaType.DECIMAL_LIST && value.getDecimalList().isEmpty()); } private static boolean isBlankText(String value) { for (int offset = 0; offset < value.length(); ) { int codePoint = value.codePointAt(offset); if (!Character.isWhitespace(codePoint) && !Character.isSpaceChar(codePoint)) { return false; } offset += Character.charCount(codePoint); } return true; } private static BigDecimal decimalValue(FormulaValue value, FormulaAst.Node node) { requirePresent(value, node); if (value.getType() != FormulaType.DECIMAL) { throw error("TYPE_MISMATCH", "需要 DECIMAL,实际为 " + value.getType(), node); } return value.getDecimal(); } private static List decimalList(FormulaValue value, FormulaAst.Node node) { requirePresent(value, node); if (value.getType() != FormulaType.DECIMAL_LIST) { throw error("TYPE_MISMATCH", "需要 DECIMAL_LIST,实际为 " + value.getType(), node); } return value.getDecimalList(); } private static String textValue(FormulaValue value, FormulaAst.Node node) { requirePresent(value, node); if (value.getType() != FormulaType.TEXT) { throw error("TYPE_MISMATCH", "需要 TEXT,实际为 " + value.getType(), node); } return value.getText(); } private static boolean booleanValue(FormulaValue value, FormulaAst.Node node) { requirePresent(value, node); if (value.getType() != FormulaType.BOOLEAN) { throw error("TYPE_MISMATCH", "需要 BOOLEAN,实际为 " + value.getType(), node); } return value.getBoolean(); } private static void requirePresent(FormulaValue value, FormulaAst.Node node) { String missingSlotCode = value.getMissingSlotCode(); if (missingSlotCode == null) { return; } String message = "缺少参数值: " + missingSlotCode; if (node == null) { throw new FormulaException("MISSING_INPUT", message); } throw error("MISSING_INPUT", message, node); } private static int scale(BigDecimal value, FormulaAst.Node node) { BigDecimal normalized = value.stripTrailingZeros(); if (normalized.scale() > 0) { throw error("INVALID_SCALE", "修约位数必须是 0 到 12 的整数", node); } int scale; try { scale = normalized.intValueExact(); } catch (ArithmeticException ex) { throw error("INVALID_SCALE", "修约位数必须是 0 到 12 的整数", node); } validateScale(scale, node); return scale; } private static int significantDigits(BigDecimal value, FormulaAst.Node node) { BigDecimal normalized = value.stripTrailingZeros(); int digits; try { if (normalized.scale() > 0) { throw new ArithmeticException("not an integer"); } digits = normalized.intValueExact(); } catch (ArithmeticException ex) { throw error("INVALID_SIGNIFICANT_DIGITS", "有效数字必须是 1 到 34 的整数", node); } if (digits < 1 || digits > MAX_SIGNIFICANT_DIGITS) { throw error("INVALID_SIGNIFICANT_DIGITS", "有效数字必须是 1 到 34 的整数", node); } return digits; } private static int rootDegree(BigDecimal value, FormulaAst.Node node) { BigDecimal normalized = value.stripTrailingZeros(); int degree; try { if (normalized.scale() > 0) { throw new ArithmeticException("not an integer"); } degree = normalized.intValueExact(); } catch (ArithmeticException ex) { throw error("INVALID_ROOT_DEGREE", "方根次数必须是 2 到 999 的整数", node); } if (degree < 2 || degree > MAX_EXPONENT) { throw error("INVALID_ROOT_DEGREE", "方根次数必须是 2 到 999 的整数", node); } return degree; } private static void requirePositiveLogValue(BigDecimal value, FormulaAst.Node node) { if (value.signum() <= 0) { throw error("LOG_DOMAIN_ERROR", "对数真数必须大于 0", node); } } private static void validateScale(int scale, FormulaAst.Node node) { if (scale < 0 || scale > MAX_SCALE) { if (node == null) { throw new FormulaException("INVALID_SCALE", "修约位数必须在 0 到 12 之间"); } throw error("INVALID_SCALE", "修约位数必须在 0 到 12 之间", node); } } private static void requireNonZero(BigDecimal value, FormulaAst.Node node) { if (value.signum() == 0) { throw error("DIVIDE_BY_ZERO", "除数不能为 0", node); } } private static void lookupCount(List arguments, int minimum, FormulaAst.Node node) { if (arguments.size() < minimum || arguments.size() % 2 == 0) { throw error("FUNCTION_ARGUMENT_COUNT_MISMATCH", "查值函数参数必须为 key 和数值点对", node); } } private static void count(List arguments, int minimum, int maximum, FormulaAst.Node node) { if (arguments.size() < minimum || arguments.size() > maximum) { throw error("FUNCTION_ARGUMENT_COUNT_MISMATCH", "函数参数数量不正确", node); } } private static FormulaException error(String code, String message, FormulaAst.Node node) { return new FormulaException(code, message, node.getStartOffset(), node.getEndOffset()); } private static Map units() { Map values = new HashMap<>(); values.put("kg", new Unit("MASS", new BigDecimal("1"))); values.put("g", new Unit("MASS", new BigDecimal("0.001"))); values.put("mg", new Unit("MASS", new BigDecimal("0.000001"))); values.put("ug", new Unit("MASS", new BigDecimal("0.000000001"))); values.put("L", new Unit("VOLUME", new BigDecimal("1"))); values.put("mL", new Unit("VOLUME", new BigDecimal("0.001"))); values.put("uL", new Unit("VOLUME", new BigDecimal("0.000001"))); return Collections.unmodifiableMap(values); } private static final class Unit { private final String dimension; private final BigDecimal baseFactor; private Unit(String dimension, BigDecimal baseFactor) { this.dimension = dimension; this.baseFactor = baseFactor; } } private static final class RegressionStats { private final BigDecimal sumXX; private final BigDecimal sumYY; private final BigDecimal sumXY; private final BigDecimal slope; private final BigDecimal intercept; private final BigDecimal predictionSlopeNumerator; private final BigDecimal interceptNumerator; private final BigDecimal commonDenominator; private RegressionStats(BigDecimal sumXX, BigDecimal sumYY, BigDecimal sumXY, BigDecimal slope, BigDecimal intercept, BigDecimal predictionSlopeNumerator, BigDecimal interceptNumerator, BigDecimal commonDenominator) { this.sumXX = sumXX; this.sumYY = sumYY; this.sumXY = sumXY; this.slope = slope; this.intercept = intercept; this.predictionSlopeNumerator = predictionSlopeNumerator; this.interceptNumerator = interceptNumerator; this.commonDenominator = commonDenominator; } private BigDecimal predict(BigDecimal value) { return predictionSlopeNumerator.multiply(value).add(interceptNumerator) .divide(commonDenominator, MATH_CONTEXT); } } private static final class EvaluationContext { private final int maximum; private int visited; private EvaluationContext(int maximum) { this.maximum = maximum; } private void visit(FormulaAst.Node node) { visited++; if (visited > maximum) { throw error("EVALUATION_NODE_LIMIT_EXCEEDED", "求值节点数量超过限制", node); } } } }