You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jena.apache.org by an...@apache.org on 2020/08/16 21:39:24 UTC
[jena] branch master updated: JENA-1943: Decimal divide: retain
exact; inexact, precision limited
This is an automated email from the ASF dual-hosted git repository.
andy pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/jena.git
The following commit(s) were added to refs/heads/master by this push:
new c0f3727 JENA-1943: Decimal divide: retain exact; inexact, precision limited
new 0074442 Merge pull request #782 from afs/jena1943-decimal-divide
c0f3727 is described below
commit c0f3727721cbebdd58a653c3ac2ee99aa26fc494
Author: Andy Seaborne <an...@apache.org>
AuthorDate: Sun Aug 16 10:38:28 2020 +0100
JENA-1943: Decimal divide: retain exact; inexact, precision limited
---
.../jena/sparql/expr/nodevalue/XSDFuncOp.java | 28 ++++-----
.../org/apache/jena/sparql/expr/TestXSDFuncOp.java | 71 +++++++++++++++++++---
2 files changed, 77 insertions(+), 22 deletions(-)
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 310a4fd..ad61e13 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
@@ -18,12 +18,7 @@
package org.apache.jena.sparql.expr.nodevalue;
-import static javax.xml.datatype.DatatypeConstants.DAYS ;
-import static javax.xml.datatype.DatatypeConstants.HOURS ;
-import static javax.xml.datatype.DatatypeConstants.MINUTES ;
-import static javax.xml.datatype.DatatypeConstants.MONTHS ;
-import static javax.xml.datatype.DatatypeConstants.SECONDS ;
-import static javax.xml.datatype.DatatypeConstants.YEARS ;
+import static javax.xml.datatype.DatatypeConstants.*;
import static org.apache.jena.sparql.expr.nodevalue.NodeFunctions.checkAndGetStringLiteral ;
import static org.apache.jena.sparql.expr.nodevalue.NodeFunctions.checkTwoArgumentStringLiterals ;
import static org.apache.jena.sparql.expr.nodevalue.NumericType.OP_DECIMAL ;
@@ -33,6 +28,8 @@ import static org.apache.jena.sparql.expr.nodevalue.NumericType.OP_INTEGER ;
import java.math.BigDecimal ;
import java.math.BigInteger ;
+import java.math.MathContext;
+import java.math.RoundingMode;
import java.text.DecimalFormat ;
import java.text.DecimalFormatSymbols ;
import java.text.Normalizer;
@@ -49,7 +46,6 @@ import javax.xml.datatype.XMLGregorianCalendar ;
import org.apache.jena.atlas.lib.IRILib ;
import org.apache.jena.atlas.lib.StrUtils ;
-import org.apache.jena.atlas.logging.Log ;
import org.apache.jena.datatypes.RDFDatatype ;
import org.apache.jena.datatypes.xsd.XSDDatatype ;
import org.apache.jena.datatypes.xsd.XSDDateTime ;
@@ -68,7 +64,9 @@ public class XSDFuncOp
{
private XSDFuncOp() {}
- // The choice of "24" is arbitrary but more than 18 as required by F&O
+ // The choice of "24" is arbitrary but more than 18 (XSD 1.0) or 16 XSD 1.1) as required by F&O
+ // F&O 3.1: section 4.2 (end intro)
+ // https://www.w3.org/TR/xpath-functions/#op.numeric section 4.2
private static final int DIVIDE_PRECISION = 24 ;
// --------------------------------
// Numeric operations
@@ -139,8 +137,6 @@ public class XSDFuncOp
return decimalDivide(d1, d2) ;
}
case OP_DECIMAL : {
- if ( nv2.getDecimal().compareTo(BigDecimal.ZERO) == 0 )
- throw new ExprEvalException("Divide by zero in decimal divide") ;
BigDecimal d1 = nv1.getDecimal() ;
BigDecimal d2 = nv2.getDecimal() ;
return decimalDivide(d1, d2) ;
@@ -157,14 +153,16 @@ public class XSDFuncOp
}
private static NodeValue decimalDivide(BigDecimal d1, BigDecimal d2) {
+ if ( d2.equals(BigDecimal.ZERO) )
+ throw new ExprEvalException("Divide by zero in decimal divide") ;
+ BigDecimal d3;
try {
- BigDecimal d3 = d1.divide(d2, DIVIDE_PRECISION, BigDecimal.ROUND_FLOOR) ;
- return canonicalDecimalNV(d3) ;
+ d3 = d1.divide(d2, MathContext.UNLIMITED);
} catch (ArithmeticException ex) {
- Log.warn(XSDFuncOp.class, "ArithmeticException in decimal divide - attempting to treat as doubles") ;
- BigDecimal d3 = new BigDecimal(d1.doubleValue() / d2.doubleValue()) ;
- return NodeValue.makeDecimal(d3) ;
+ // Does not throw ArithmeticException
+ d3 = d1.divide(d2, DIVIDE_PRECISION, RoundingMode.HALF_EVEN);
}
+ return canonicalDecimalNV(d3) ;
}
public static NodeValue canonicalDecimalNV(BigDecimal d) {
diff --git a/jena-arq/src/test/java/org/apache/jena/sparql/expr/TestXSDFuncOp.java b/jena-arq/src/test/java/org/apache/jena/sparql/expr/TestXSDFuncOp.java
index b6ec14f..5524950 100644
--- a/jena-arq/src/test/java/org/apache/jena/sparql/expr/TestXSDFuncOp.java
+++ b/jena-arq/src/test/java/org/apache/jena/sparql/expr/TestXSDFuncOp.java
@@ -262,16 +262,57 @@ public class TestXSDFuncOp
assertEquals("Wrong result", 12.5, r.getDecimal().doubleValue(), accuracyExact_D) ;
}
+ private static String divideDecimal(String v1, String v2, String v3) {
+ NodeValue nv1 = NodeValue.makeDecimal(v1) ;
+ NodeValue nv2 = NodeValue.makeDecimal(v2) ;
+ NodeValue nv3 = NodeValue.makeDecimal(v3) ;
+ NodeValue r = XSDFuncOp.numDivide(nv1, nv2) ;
+ assertTrue("Not a decimal: "+r, r.isDecimal()) ;
+ // sameAv (value) test : does not test lexical form or datatype.
+ assertTrue("Wrong result : expected="+r+" : got="+nv3, NodeValue.sameAs(r, nv3));
+ return r.getNode().getLiteralLexicalForm();
+ }
+
+ @Test public void testDivideDecimal1() {
+ divideDecimal("1", "10", "0.1");
+ }
+
+ @Test public void testDivideDecimal2() {
+ divideDecimal("1", "2", "0.5");
+ }
+
+ @Test public void testDivideDecimal3() {
+ // Depends on XSDFuncOp.DIVIDE_PRECISION = 24
+ String x = divideDecimal("1", "3", "0.333333333333333333333333");
+ assertEquals("Wrong lexical form length", 26, x.length());
+ }
+
+ @Test public void testDivideDecimal4() {
+ String x = divideDecimal("0", "3", "0");
+ assertEquals("Wrong lexical form", "0.0", x);
+ }
+
+ // JENA-1943 : If exact, return more than DIVIDE_PRECISION
+ @Test public void testDivideDecimal5() {
+ String x = divideDecimal("1", "10000000000000000000000000", "0.0000000000000000000000001");
+ // More than 2 (for the "0.") plus XSDFuncOp.DIVIDE_PRECISION = 24
+ assertEquals("Wrong length lexical form", 27, x.length());
+ }
+
+ // JENA-1943
+ @Test public void testDivideDecimal6() {
+ String x = divideDecimal("1", "10000000000000000000000000000", "0.0000000000000000000000000001");
+ // Exact
+ assertEquals("Wrong length lexical form", 30, x.length());
+ }
+
// divide errors
- @Test public void testDivideByZero1()
- {
+ @Test(expected=ExprEvalException.class)
+ public void testDivideByZero1()
+ {
NodeValue nv1 = NodeValue.makeInteger(1) ;
NodeValue nv2 = NodeValue.makeInteger(0) ;
- try {
- NodeValue r = XSDFuncOp.numDivide(nv1, nv2) ;
- fail("No expection from .divide") ;
- } catch (ExprEvalException ex)
- { }
+ NodeValue r = XSDFuncOp.numDivide(nv1, nv2) ;
}
@Test public void testDivideByZero2()
@@ -292,6 +333,22 @@ public class TestXSDFuncOp
assertTrue("Not a -INF: "+r, r.getDouble()==Double.NEGATIVE_INFINITY) ;
}
+ @Test(expected=ExprEvalException.class)
+ public void testDivideByZero5()
+ {
+ NodeValue nv1 = NodeValue.makeInteger(1) ;
+ NodeValue nv2 = NodeValue.makeDecimal(0) ;
+ NodeValue r = XSDFuncOp.numDivide(nv1, nv2) ;
+ }
+
+ @Test(expected=ExprEvalException.class)
+ public void testDivideByZero6()
+ {
+ NodeValue nv1 = NodeValue.makeDecimal(1) ;
+ NodeValue nv2 = NodeValue.makeDecimal(0) ;
+ NodeValue r = XSDFuncOp.numDivide(nv1, nv2) ;
+ }
+
@Test public void testSubtractDoubleDecimal()
{
NodeValue nv1 = NodeValue.makeDouble(4.5) ;