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) ;