diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/expr/nodevalue/XSDFuncOp.java b/jena-arq/src/main/java/org/apache/jena/sparql/expr/nodevalue/XSDFuncOp.java index dad5316addc..e64e3ed016d 100644 --- a/jena-arq/src/main/java/org/apache/jena/sparql/expr/nodevalue/XSDFuncOp.java +++ b/jena-arq/src/main/java/org/apache/jena/sparql/expr/nodevalue/XSDFuncOp.java @@ -1127,10 +1127,21 @@ public static int compareNumeric(NodeValue nv1, NodeValue nv2) { return calcReturn(nv1.getInteger().compareTo(nv2.getInteger())); case OP_DECIMAL : return calcReturn(nv1.getDecimal().compareTo(nv2.getDecimal())); - case OP_FLOAT : - return calcReturn(Float.compare(nv1.getFloat(), nv2.getFloat())); - case OP_DOUBLE : - return calcReturn(Double.compare(nv1.getDouble(), nv2.getDouble())); + case OP_FLOAT : { + float x = nv1.getFloat(); + float y = nv2.getFloat(); + // Positive and negative zero have the same numeric value. + if ( x == y ) + return Expr.CMP_EQUAL; + return calcReturn(Float.compare(x, y)); + } + case OP_DOUBLE : { + double x = nv1.getDouble(); + double y = nv2.getDouble(); + if ( x == y ) + return Expr.CMP_EQUAL; + return calcReturn(Double.compare(x, y)); + } default : throw new ARQInternalErrorException("Unrecognized numeric operation : (" + nv1 + " ," + nv2 + ")"); } diff --git a/jena-arq/src/test/java/org/apache/jena/sparql/expr/TestExpressions2.java b/jena-arq/src/test/java/org/apache/jena/sparql/expr/TestExpressions2.java index 6d637c278d2..d8682f37b59 100644 --- a/jena-arq/src/test/java/org/apache/jena/sparql/expr/TestExpressions2.java +++ b/jena-arq/src/test/java/org/apache/jena/sparql/expr/TestExpressions2.java @@ -81,6 +81,23 @@ public static void afterAll() { @Test public void gregorian_cast_01() { eval("xsd:gYear('2010-03-22'^^xsd:date) = '2010'^^xsd:gYear", true ); } + // GH-4297: Signed zero compares equal by value for float, double, and promoted numbers. + @Test public void signed_zero_01() { eval("'0e0'^^xsd:double = '-0e0'^^xsd:double", true); } + @Test public void signed_zero_02() { eval("'-0e0'^^xsd:double != '0e0'^^xsd:double", false); } + @Test public void signed_zero_03() { eval("'-0e0'^^xsd:double < '0e0'^^xsd:double", false); } + @Test public void signed_zero_04() { eval("'0e0'^^xsd:double > '-0e0'^^xsd:double", false); } + @Test public void signed_zero_05() { eval("'-0e0'^^xsd:double <= '0e0'^^xsd:double", true); } + @Test public void signed_zero_06() { eval("'0e0'^^xsd:double >= '-0e0'^^xsd:double", true); } + @Test public void signed_zero_07() { eval("'-0'^^xsd:float = '0'^^xsd:float", true); } + @Test public void signed_zero_08() { eval("'-0'^^xsd:float < '0'^^xsd:float", false); } + @Test public void signed_zero_09() { eval("'-0'^^xsd:float = '0e0'^^xsd:double", true); } + @Test public void signed_zero_10() { eval("'-0'^^xsd:float = 0", true); } + @Test public void signed_zero_11() { eval("'-0e0'^^xsd:double = 0.0", true); } + @Test public void signed_zero_12() { eval("'0e0'^^xsd:double IN ('-0e0'^^xsd:double)", true); } + @Test public void signed_zero_13() { eval("'-1e0'^^xsd:double < '0e0'^^xsd:double", true); } + @Test public void signed_zero_14() { eval("'1e0'^^xsd:double > '-0e0'^^xsd:double", true); } + @Test public void signed_zero_15() { eval("'INF'^^xsd:double > '-0e0'^^xsd:double", true); } + @Test public void coalesce_01() { assertThrows(ExprEvalException.class, ()-> eval("COALESCE()") ); } @Test public void coalesce_02() { eval("COALESCE(1) = 1", true); } @Test public void coalesce_03() { eval("COALESCE(?x,1) = 1", true); }