This is an automated email from the ASF dual-hosted git repository.

rvesse pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/jena.git

commit 1424185059e563504d6d615f06788f6661cdc63f
Author: Rob Vesse <[email protected]>
AuthorDate: Mon Oct 5 10:20:06 2026 +0100

    Exponentation special cases rejected by pre-validate
    
    Some exponentiation special cases (involving powers of zero and 1,
    infinities etc) where incorrectly rejected by the pre-validation.
    Instead these special cases are now properly handled and expression test
    coverage is expanded to include those.
    
    Some Leviathan functions that are equivalent to XPath math functions
    were refactored to simply extend the corresponding XPath implementations
    so they also benefit from the special case handling.
---
 .../apache/jena/sparql/function/MathLimits.java    | 11 ++++++--
 .../jena/sparql/function/library/Math_exp.java     | 16 +++++++++--
 .../jena/sparql/function/library/Math_pow.java     | 22 ++++++++++++++-
 .../jena/sparql/function/library/leviathan/e.java  | 12 ++------
 .../sparql/function/library/leviathan/pow.java     | 13 +++++----
 .../sparql/function/library/leviathan/ten.java     | 12 ++------
 .../jena/sparql/expr/TestExpressionsMath.java      |  8 ++++++
 .../jena/sparql/expr/TestLeviathanFunctions.java   | 32 ++++++++++++++++------
 8 files changed, 86 insertions(+), 40 deletions(-)

diff --git 
a/jena-arq/src/main/java/org/apache/jena/sparql/function/MathLimits.java 
b/jena-arq/src/main/java/org/apache/jena/sparql/function/MathLimits.java
index 4f7608873a..07edcc67a5 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/function/MathLimits.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/function/MathLimits.java
@@ -77,7 +77,7 @@ public class MathLimits {
     }
 
     private static ExprEvalException exponentOutOfRange() {
-        return new ExprEvalException("Cannot calculate exponent when base is 
outside double range");
+        return new ExprEvalException("Cannot calculate exponent when exponent 
is outside double range");
     }
 
     private static boolean infiniteOrNaN(double aDbl) {
@@ -97,11 +97,16 @@ public class MathLimits {
      * @throws ExprEvalException Thrown if the calculation would require too 
many digits
      */
     public static void preValidateExponentCalculation(double a, double b) {
-        double logA = Math.log10(a);
+        // Raising zero to the power anything will always be zero so is always 
safe
+        if (a == 0) {
+            return;
+        }
+
+        double logA = Math.log10(Math.abs(a));
         if (infiniteOrNaN(logA)) {
             throw exponentOutOfRange();
         }
-        double numDigits = Math.floor(b * Math.log10(a)) + 1.0d;
+        double numDigits = Math.floor(b * logA) + 1.0d;
         if (numDigits > MAX_EXPONENT_DIGITS) {
             throw tooManyExponentDigits();
         }
diff --git 
a/jena-arq/src/main/java/org/apache/jena/sparql/function/library/Math_exp.java 
b/jena-arq/src/main/java/org/apache/jena/sparql/function/library/Math_exp.java
index b996b65f39..9352c754da 100644
--- 
a/jena-arq/src/main/java/org/apache/jena/sparql/function/library/Math_exp.java
+++ 
b/jena-arq/src/main/java/org/apache/jena/sparql/function/library/Math_exp.java
@@ -30,8 +30,20 @@ public class Math_exp extends FunctionBase1 {
 
     @Override
     public NodeValue exec(NodeValue v) {
-        MathLimits.preValidateExponentCalculation(Math.E, v.getDouble());
-        return NodeValue.makeDouble(Math.exp(v.getDouble()));
+        double d = v.getDouble();
+
+        // Special case, raising e to power INF is either 0 for negative 
infinity or INF for positive infinity
+        if (Double.isInfinite(d)) {
+            if (d < 0) {
+                return NodeValue.makeDouble(0);
+            } else {
+                return NodeValue.makeDouble(d);
+            }
+        }
+
+        // Otherwise pre-validate exponent calculation before proceeding to 
calculate it
+        MathLimits.preValidateExponentCalculation(Math.E, d);
+        return NodeValue.makeDouble(Math.exp(d));
     }
 
 }
diff --git 
a/jena-arq/src/main/java/org/apache/jena/sparql/function/library/Math_pow.java 
b/jena-arq/src/main/java/org/apache/jena/sparql/function/library/Math_pow.java
index e549aaf5f8..95b3ca44e8 100644
--- 
a/jena-arq/src/main/java/org/apache/jena/sparql/function/library/Math_pow.java
+++ 
b/jena-arq/src/main/java/org/apache/jena/sparql/function/library/Math_pow.java
@@ -50,13 +50,33 @@ public class Math_pow extends FunctionBase2 {
             case OP_DOUBLE:
                 double d1 = v1.getDouble();
                 double d2 = v2.getDouble();
-                if (d1 == 1 && d2 == Double.POSITIVE_INFINITY) {
+
+                // Various special cases short circuit actual evaluation
+                if (Double.isNaN(d1)) {
+                    // NaN raised to a power is either 1 if power 0 or NaN 
otherwise
+                    if (d2 == 0) {
+                        return NodeValue.makeDouble(1);
+                    } else {
+                        return NodeValue.nvDoubleNaN;
+                    }
+                } else if (Math.abs(d1) == 1 && Double.isInfinite(d2)) {
+                    // 1 raised to INF is always 1
                     if (v1.isInteger()) {
                         return NodeValue.nvONE;
                     } else {
                         return NodeValue.makeDouble(1);
                     }
+                } else if (Double.isInfinite(d1)) {
+                    // INF raised to a power is 1 if the power is zero or 
itself if the power is 1
+                    if (d2 == 0) {
+                        return NodeValue.makeDouble(1);
+                    } else if (d2 == 1) {
+                        return NodeValue.makeDouble(d1);
+                    }
                 }
+
+                // If we get this far pre-validate that calculating the power 
won't require too many digits and if not
+                // actually calculate
                 MathLimits.preValidateExponentCalculation(d1, d2);
                 return NodeValue.makeDouble(Math.pow(v1.getDouble(), 
v2.getDouble()));
             default:
diff --git 
a/jena-arq/src/main/java/org/apache/jena/sparql/function/library/leviathan/e.java
 
b/jena-arq/src/main/java/org/apache/jena/sparql/function/library/leviathan/e.java
index 18ca67e3f9..b37e0e1526 100644
--- 
a/jena-arq/src/main/java/org/apache/jena/sparql/function/library/leviathan/e.java
+++ 
b/jena-arq/src/main/java/org/apache/jena/sparql/function/library/leviathan/e.java
@@ -21,16 +21,8 @@
 
 package org.apache.jena.sparql.function.library.leviathan;
 
-import org.apache.jena.sparql.expr.NodeValue;
-import org.apache.jena.sparql.function.FunctionBase1;
-import org.apache.jena.sparql.function.MathLimits;
+import org.apache.jena.sparql.function.library.Math_exp;
 
-public class e extends FunctionBase1 {
-
-    @Override
-    public NodeValue exec(NodeValue v) {
-        MathLimits.preValidateExponentCalculation(Math.E, v.getDouble());
-        return NodeValue.makeDouble(Math.exp(v.getDouble()));
-    }
+public class e extends Math_exp {
 
 }
diff --git 
a/jena-arq/src/main/java/org/apache/jena/sparql/function/library/leviathan/pow.java
 
b/jena-arq/src/main/java/org/apache/jena/sparql/function/library/leviathan/pow.java
index 38d84a818c..d420865302 100644
--- 
a/jena-arq/src/main/java/org/apache/jena/sparql/function/library/leviathan/pow.java
+++ 
b/jena-arq/src/main/java/org/apache/jena/sparql/function/library/leviathan/pow.java
@@ -21,6 +21,7 @@
 
 package org.apache.jena.sparql.function.library.leviathan;
 
+import java.math.BigDecimal;
 import java.math.BigInteger;
 
 import org.apache.jena.sparql.ARQInternalErrorException;
@@ -28,25 +29,25 @@ import org.apache.jena.sparql.expr.NodeValue;
 import org.apache.jena.sparql.expr.nodevalue.XSDFuncOp;
 import org.apache.jena.sparql.function.FunctionBase2;
 import org.apache.jena.sparql.function.MathLimits;
+import org.apache.jena.sparql.function.library.Math_pow;
 
 public class pow extends FunctionBase2 {
 
+    private final Math_pow mathPow = new Math_pow();
+
     @Override
     public NodeValue exec(NodeValue v1, NodeValue v2) {
         switch (XSDFuncOp.classifyNumeric("pow", v1))
         {
-            case OP_INTEGER:
-                BigInteger i = v1.getInteger();
-                MathLimits.preValidateExponentCalculation(i, v2.getInteger());
-                return NodeValue.makeInteger( 
i.pow(v2.getInteger().intValue()) );
             case OP_DECIMAL:
                 double dec = v1.getDecimal().doubleValue();
                 MathLimits.preValidateExponentCalculation(dec, v2.getDouble());
                 return NodeValue.makeDecimal( Math.pow(dec, v2.getDouble()));
+            case OP_INTEGER:
             case OP_FLOAT:
             case OP_DOUBLE:
-                MathLimits.preValidateExponentCalculation(v1.getDouble(), 
v2.getDouble());
-                return NodeValue.makeDouble( Math.pow(v1.getDouble(), 
v2.getDouble()) );
+                // Defer to Math_pow as that handles all the special cases
+                return this.mathPow.exec(v1, v2);
             default:
                 throw new ARQInternalErrorException("Unrecognized numeric 
operation : "+ v1);
         }
diff --git 
a/jena-arq/src/main/java/org/apache/jena/sparql/function/library/leviathan/ten.java
 
b/jena-arq/src/main/java/org/apache/jena/sparql/function/library/leviathan/ten.java
index bf88885922..3e5ab573de 100644
--- 
a/jena-arq/src/main/java/org/apache/jena/sparql/function/library/leviathan/ten.java
+++ 
b/jena-arq/src/main/java/org/apache/jena/sparql/function/library/leviathan/ten.java
@@ -21,16 +21,8 @@
 
 package org.apache.jena.sparql.function.library.leviathan;
 
-import org.apache.jena.sparql.expr.NodeValue;
-import org.apache.jena.sparql.function.FunctionBase1;
-import org.apache.jena.sparql.function.MathLimits;
+import org.apache.jena.sparql.function.library.Math_exp10;
 
-public class ten extends FunctionBase1 {
-
-    @Override
-    public NodeValue exec(NodeValue v) {
-        MathLimits.preValidateExponentCalculation(10, v.getDouble());
-        return NodeValue.makeDouble(Math.pow(10d, v.getDouble()));
-    }
+public class ten extends Math_exp10 {
 
 }
diff --git 
a/jena-arq/src/test/java/org/apache/jena/sparql/expr/TestExpressionsMath.java 
b/jena-arq/src/test/java/org/apache/jena/sparql/expr/TestExpressionsMath.java
index 7d7553a00d..fb5a8f3ab4 100644
--- 
a/jena-arq/src/test/java/org/apache/jena/sparql/expr/TestExpressionsMath.java
+++ 
b/jena-arq/src/test/java/org/apache/jena/sparql/expr/TestExpressionsMath.java
@@ -73,6 +73,14 @@ public class TestExpressionsMath
     @Test public void pow_15()          { 
testDoubleIsNaN("math:pow('NaN'^^xsd:double, 1)"); }
     @Test public void pow_16()          { testDoubleIsNaN("math:pow(1, 
'NaN'^^xsd:double)"); }
 
+    @Test public void pow_17()          { test("math:pow(0e0, -3)", 
"'INF'^^xsd:double"); }
+    @Test public void pow_18()          { test("math:pow(-0e0, -3)", 
"'-INF'^^xsd:double"); }
+    @Test public void pow_19()          { test("math:pow(01, 
'INF'^^xsd:double)", "1"); }
+    @Test public void pow_20()          { test("math:pow(-1e0, 
'-INF'^^xsd:double)", "'1.0e0'^^xsd:double"); }
+    @Test public void pow_21()          { testDoubleIsNaN("math:pow(-2.5e0, 
2.00000001e0)"); }
+    @Test public void pow_22()          { test("math:pow(0e0, 3.0e0)", "0e0"); 
}
+    @Test public void pow_23()          { test("math:pow(-0e0, 3.0e0)", 
"-0e0"); }
+
     @Test public void sqrt_01()         { test("math:sqrt(1)", 
"'1.0e0'^^xsd:double"); }
     @Test public void sqrt_02()         { testDouble("math:sqrt(2)", 
Math.sqrt(2), 0.000001); }
     @Test public void sqrt_03()         { testDoubleIsNaN("math:sqrt(-2)"); }
diff --git 
a/jena-arq/src/test/java/org/apache/jena/sparql/expr/TestLeviathanFunctions.java
 
b/jena-arq/src/test/java/org/apache/jena/sparql/expr/TestLeviathanFunctions.java
index e0efa3665c..41dc0b9c8e 100644
--- 
a/jena-arq/src/test/java/org/apache/jena/sparql/expr/TestLeviathanFunctions.java
+++ 
b/jena-arq/src/test/java/org/apache/jena/sparql/expr/TestLeviathanFunctions.java
@@ -21,9 +21,8 @@
 
 package org.apache.jena.sparql.expr;
 
-import static org.apache.jena.sparql.expr.LibTestExpr.test;
-import static org.apache.jena.sparql.expr.LibTestExpr.testDouble;
-import static org.apache.jena.sparql.expr.LibTestExpr.testError;
+import static org.apache.jena.sparql.expr.LibTestExpr.*;
+import static org.apache.jena.sparql.expr.LibTestExpr.testDoubleIsNaN;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
@@ -100,6 +99,23 @@ public class TestLeviathanFunctions {
     public void pow_02() {
         LibTestExpr.test("lfn:pow(0.5, 3)", "0.125");
     }
+    
+    @Test public void pow_10()          { test("lfn:pow('INF'^^xsd:double, 
1)", "'INF'^^xsd:double"); }
+    @Test public void pow_11()          { test("lfn:pow(1, 
'INF'^^xsd:double)", "1"); }
+    @Test public void pow_12()          { test("lfn:pow(1e0, 
'INF'^^xsd:double)", "'1.0e0'^^xsd:double"); }
+
+    @Test public void pow_13()          { test("lfn:pow('INF'^^xsd:double,0)", 
"'1.0e0'^^xsd:double"); }
+    @Test public void pow_14()          { test("lfn:pow('-INF'^^xsd:double, 
0)", "'1.0e0'^^xsd:double"); }
+    @Test public void pow_15()          { 
testDoubleIsNaN("lfn:pow('NaN'^^xsd:double, 1)"); }
+    @Test public void pow_16()          { testDoubleIsNaN("lfn:pow(1, 
'NaN'^^xsd:double)"); }
+
+    @Test public void pow_17()          { test("lfn:pow(0e0, -3)", 
"'INF'^^xsd:double"); }
+    @Test public void pow_18()          { test("lfn:pow(-0e0, -3)", 
"'-INF'^^xsd:double"); }
+    @Test public void pow_19()          { test("lfn:pow(01, 
'INF'^^xsd:double)", "1"); }
+    @Test public void pow_20()          { test("lfn:pow(-1e0, 
'-INF'^^xsd:double)", "'1.0e0'^^xsd:double"); }
+    @Test public void pow_21()          { testDoubleIsNaN("lfn:pow(-2.5e0, 
2.00000001e0)"); }
+    @Test public void pow_22()          { test("lfn:pow(0e0, 3.0e0)", "0e0"); }
+    @Test public void pow_23()          { test("lfn:pow(-0e0, 3.0e0)", 
"-0e0"); }
 
     @Test
     public void factorial_01() {
@@ -244,11 +260,11 @@ public class TestLeviathanFunctions {
                          Arguments.of("lfn:sq(100.0e100)"),
                          Arguments.of("lfn:cube(100.0e100)"),
                          Arguments.of("lfn:ten(2000000000)"),
-                         Arguments.of("lfn:cartesian(0, 0, 1, 2000000000)"),
-                         Arguments.of("lfn:cartesian(0, 0, 2000000000, 1)"),
-                         Arguments.of("lfn:cartesian(0, 0, 0, 1, 1, 
2000000000)"),
-                         Arguments.of("lfn:cartesian(0, 0, 0, 1, 2000000000, 
1)"),
-                         Arguments.of("lfn:cartesian(0, 0, 0, 2000000000, 1, 
1)"),
+                         Arguments.of("lfn:cartesian(0, 0, 1, 100.0e100)"),
+                         Arguments.of("lfn:cartesian(0, 0, 100.0e100, 1)"),
+                         Arguments.of("lfn:cartesian(0, 0, 0, 1, 1, 
100.0e100)"),
+                         Arguments.of("lfn:cartesian(0, 0, 0, 1, 100.0e100, 
1)"),
+                         Arguments.of("lfn:cartesian(0, 0, 0, 100.0e100, 1, 
1)"),
                          Arguments.of("lfn:pythagoras(2.0e128, 2)"),
                          Arguments.of("lfn:root(2" + StringUtils.repeat('0', 
64) + ", 1)"),
                          Arguments.of("lfn:e(2000000000)")

Reply via email to