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

desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git

commit 4f11c0244de664dce82d95aebebd3eac72b9489a
Author: Martin Desruisseaux <martin.desruisse...@geomatys.com>
AuthorDate: Fri Dec 30 14:14:38 2022 +0100

    Add more verifications of `GeneralMatrix` validity.
    Fix a bug in `GeneralMatrix.setElements(double[])`
    identified by above-cited verifications.
---
 .../operation/matrix/GeneralMatrix.java            | 62 ++++++++++++++++++----
 .../sis/referencing/operation/matrix/Matrices.java |  5 +-
 .../referencing/operation/matrix/MatrixSIS.java    | 10 +++-
 .../referencing/operation/matrix/package-info.java |  2 +-
 .../operation/matrix/GeneralMatrixTest.java        |  5 +-
 .../operation/matrix/MatrixTestCase.java           | 27 +++++-----
 .../org/apache/sis/internal/util/DoubleDouble.java | 14 +++--
 .../apache/sis/internal/util/DoubleDoubleTest.java |  8 +--
 8 files changed, 93 insertions(+), 40 deletions(-)

diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/GeneralMatrix.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/GeneralMatrix.java
index 90a4fcc596..0fba129a0c 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/GeneralMatrix.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/GeneralMatrix.java
@@ -36,7 +36,7 @@ import 
org.apache.sis.internal.referencing.ExtendedPrecisionMatrix;
  * the {@link DoubleDouble#error}.
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 1.3
+ * @version 1.4
  *
  * @see Matrices#createDiagonal(int, int)
  *
@@ -125,6 +125,7 @@ class GeneralMatrix extends MatrixSIS implements 
ExtendedPrecisionMatrix {
         this.numRow = (short) numRow;
         this.numCol = (short) numCol;
         this.elements = elements.clone();
+        assert isValid();
     }
 
     /**
@@ -145,6 +146,7 @@ class GeneralMatrix extends MatrixSIS implements 
ExtendedPrecisionMatrix {
             elements = new double[numRow * numCol];
             getElements(matrix, numRow, numCol, elements);
         }
+        assert isValid();
     }
 
     /**
@@ -154,6 +156,34 @@ class GeneralMatrix extends MatrixSIS implements 
ExtendedPrecisionMatrix {
         numRow   = matrix.numRow;
         numCol   = matrix.numCol;
         elements = matrix.elements.clone();
+        assert isValid();
+    }
+
+    /**
+     * Verifies that this matrix is well-formed. This method verifies that the 
matrix size is valid,
+     * that the {@link #elements} array is non-null and has a valid length, 
and that all error terms
+     * are smaller than 1 ULP of the corresponding matrix element value.
+     *
+     * @return whether this matrix is well-formed.
+     * @throws NullPointerException if the {@link #elements} array is null.
+     * @throws AssertionError (thrown by {@link 
DoubleDouble#DoubleDouble(double, double)} constructor)
+     *         if an error term is not smaller than the corresponding matrix 
element value.
+     */
+    private boolean isValid() {
+        final int numRow = this.numRow;
+        final int numCol = this.numCol;
+        final int length = elements.length;
+        int i = numRow * numCol;                // Cannot overflow.
+        if ((numRow |  numCol) < 0 || (length != i && length != i*2) ||
+            (numRow != numCol) != (this instanceof NonSquareMatrix))
+        {
+            return false;
+        }
+        boolean isValid = true;
+        while (--i >= 0) {
+            isValid &= Numerics.equals(getNumber(i / numCol, i % 
numCol).doubleValue(), elements[i]);
+        }
+        return isValid;
     }
 
     /**
@@ -267,6 +297,7 @@ class GeneralMatrix extends MatrixSIS implements 
ExtendedPrecisionMatrix {
      * @param  row     the row index, from 0 inclusive to {@link #getNumRow()} 
exclusive.
      * @param  column  the column index, from 0 inclusive to {@link 
#getNumCol()} exclusive.
      * @return the current value at the given row and column, rounded to 
nearest integer.
+     * @throws ArithmeticException if the value is NaN or overflows integer 
capacity.
      *
      * @see DoubleDouble#longValue()
      *
@@ -276,15 +307,18 @@ class GeneralMatrix extends MatrixSIS implements 
ExtendedPrecisionMatrix {
     public long getInteger(int row, int column) {
         if (row >= 0 && row < numRow && column >= 0 && column < numCol) {
             int i = row * numCol + column;
-            long value = Math.round(elements[i]);
+            final double value = elements[i];
+            long r = Math.round(value);
             i += numRow * numCol;
             if (i < elements.length) {
-                value += (long) elements[i];        // Really want rounding 
toward zero.
+                r += (long) elements[i];            // Really want rounding 
toward zero.
             }
-            return value;
-        } else {
-            throw indexOutOfBounds(row, column);
+            if (Math.abs(r - value) <= 0.5) {
+                return r;
+            }
+            throw new 
ArithmeticException(Errors.format(Errors.Keys.CanNotConvertValue_2, value, 
Long.TYPE));
         }
+        throw indexOutOfBounds(row, column);
     }
 
     /**
@@ -395,7 +429,8 @@ class GeneralMatrix extends MatrixSIS implements 
ExtendedPrecisionMatrix {
     }
 
     /**
-     * {@inheritDoc}
+     * Returns a copy of all matrix elements in a flat, row-major (column 
indices vary fastest) array.
+     * The returned array does <em>not</em> include error terms used in 
double-double arithmetic.
      */
     @Override
     public final double[] getElements() {
@@ -412,15 +447,17 @@ class GeneralMatrix extends MatrixSIS implements 
ExtendedPrecisionMatrix {
     }
 
     /**
-     * {@inheritDoc}
+     * Sets all matrix elements from a flat, row-major (column indices vary 
fastest) array.
+     * The given array shall not contain error terms. The error terms will be 
set to default values.
      */
     @Override
     public final void setElements(final double[] newValues) {
         ensureLengthMatch(numRow*numCol, newValues);
         System.arraycopy(newValues, 0, elements, 0, newValues.length);
         if (elements.length != newValues.length) {
-            inferErrors(newValues);
+            inferErrors(elements);
         }
+        assert isValid();
     }
 
     /**
@@ -436,7 +473,7 @@ class GeneralMatrix extends MatrixSIS implements 
ExtendedPrecisionMatrix {
      * </ul>
      *
      * @param  newValues  the new matrix elements in a row-major array.
-     * @return {@code true} if at leat one {@link DoubleDouble} instance has 
been found, in which case all
+     * @return {@code true} if at least one {@link DoubleDouble} instance has 
been found, in which case all
      *         errors terms have been initialized, or {@code false} otherwise, 
in which case no error term
      *         has been initialized (this is a <cite>all or nothing</cite> 
operation).
      * @throws IllegalArgumentException if the given array does not have the 
expected length.
@@ -486,6 +523,9 @@ class GeneralMatrix extends MatrixSIS implements 
ExtendedPrecisionMatrix {
             }
             elements[i + length] = error;
         }
+        if (isExtended) {
+            assert isValid();
+        }
         return isExtended;
     }
 
@@ -513,6 +553,7 @@ class GeneralMatrix extends MatrixSIS implements 
ExtendedPrecisionMatrix {
         } else {
             super.setMatrix(matrix);
         }
+        assert isValid();
     }
 
     /**
@@ -662,6 +703,7 @@ class GeneralMatrix extends MatrixSIS implements 
ExtendedPrecisionMatrix {
                 sum.storeTo(elements, k++, errorOffset);
             }
         }
+        assert isValid();
     }
 
     /**
diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/Matrices.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/Matrices.java
index 6a74205416..c1f50e882c 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/Matrices.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/Matrices.java
@@ -212,8 +212,9 @@ public final class Matrices extends Static {
          * Below is an intantionally undocumented feature. We use those 
sentinel values as a way to create
          * matrices with extended precision without exposing our double-double 
arithmetic in public API.
          */
-        if (elements == ExtendedPrecisionMatrix.IDENTITY || elements == 
ExtendedPrecisionMatrix.ZERO) {
-            return GeneralMatrix.createExtendedPrecision(numRow, numCol, 
elements == ExtendedPrecisionMatrix.IDENTITY);
+        final boolean setToIdentity = (elements == 
ExtendedPrecisionMatrix.IDENTITY);
+        if (setToIdentity || elements == ExtendedPrecisionMatrix.ZERO) {
+            return GeneralMatrix.createExtendedPrecision(numRow, numCol, 
setToIdentity);
         }
         final GeneralMatrix matrix = 
GeneralMatrix.createExtendedPrecision(numRow, numCol, false);
         if (matrix.setElements(elements)) {
diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/MatrixSIS.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/MatrixSIS.java
index c3d9f19e49..ab9afb41ec 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/MatrixSIS.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/MatrixSIS.java
@@ -55,7 +55,7 @@ import org.apache.sis.util.resources.Errors;
  * </ul>
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 1.3
+ * @version 1.4
  *
  * @see Matrices
  *
@@ -170,11 +170,17 @@ public abstract class MatrixSIS implements Matrix, 
LenientComparable, Cloneable,
      * @param  row     the row index, from 0 inclusive to {@link #getNumRow()} 
exclusive.
      * @param  column  the column index, from 0 inclusive to {@link 
#getNumCol()} exclusive.
      * @return the current value at the given row and column, rounded to 
nearest integer.
+     * @throws ArithmeticException if the value is NaN or overflows integer 
capacity.
      *
      * @since 1.3
      */
     public long getInteger(int row, int column) {
-        return Math.round(getElement(row, column));
+        final double value = getElement(row, column);
+        final long r = Math.round(value);
+        if (Math.abs(r - value) <= 0.5) {
+            return r;
+        }
+        throw new 
ArithmeticException(Errors.format(Errors.Keys.CanNotConvertValue_2, value, 
Long.TYPE));
     }
 
     /**
diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/package-info.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/package-info.java
index b424dae044..ed8b445699 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/package-info.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/package-info.java
@@ -69,7 +69,7 @@
  * Like SIS, Vecmath is optimized for small matrices of interest for 2D and 3D 
graphics.</p>
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 1.3
+ * @version 1.4
  * @since   0.4
  */
 package org.apache.sis.referencing.operation.matrix;
diff --git 
a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/matrix/GeneralMatrixTest.java
 
b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/matrix/GeneralMatrixTest.java
index 7321fae51e..2febade1b6 100644
--- 
a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/matrix/GeneralMatrixTest.java
+++ 
b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/matrix/GeneralMatrixTest.java
@@ -66,7 +66,7 @@ public final class GeneralMatrixTest extends MatrixTestCase {
     @Test
     public void testExtendedPrecision() {
         final long value = 1000000000000010000L;
-        assertNotEquals(value, Math.round((double) value));
+        assertNotEquals(value, StrictMath.round((double) value));
         final GeneralMatrix m = new GeneralMatrix(1, 1, false, 2);
         final DoubleDouble ddval = new DoubleDouble((Long) value);
         m.setNumber(0, 0, ddval);
@@ -155,7 +155,6 @@ public final class GeneralMatrixTest extends MatrixTestCase 
{
     @Test
     public void testTranslateVector() {
         testTranslateVector(new GeneralMatrix(3, 3, true, 1));    // Double 
precision
-//      testTranslateVector(new GeneralMatrix(3, 3, true, 2));    // 
Double-double precision
-        // TODO: revisit commented-out test after using Math.fma with JDK9.
+        testTranslateVector(new GeneralMatrix(3, 3, true, 2));    // 
Double-double precision
     }
 }
diff --git 
a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/matrix/MatrixTestCase.java
 
b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/matrix/MatrixTestCase.java
index c46f9dd025..120667f6c3 100644
--- 
a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/matrix/MatrixTestCase.java
+++ 
b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/matrix/MatrixTestCase.java
@@ -26,6 +26,7 @@ import org.apache.sis.test.TestUtilities;
 import org.apache.sis.test.DependsOnMethod;
 import org.junit.Test;
 
+import static java.lang.StrictMath.*;
 import static org.apache.sis.test.Assert.*;
 
 
@@ -168,7 +169,7 @@ public abstract class MatrixTestCase extends TestCase {
                 assertEquals(name, e, actual.getNumber(j,i).doubleValue(), 
tolerance);
                 if (tolerance != STRICT && statistics != null) {
                     synchronized (statistics) {
-                        statistics.accept(StrictMath.abs(e - a));
+                        statistics.accept(abs(e - a));
                     }
                 }
             }
@@ -194,7 +195,7 @@ public abstract class MatrixTestCase extends TestCase {
     private static void assertEqualsRelative(final String message, final 
double expected,
             final MatrixSIS matrix, final int row, final int column)
     {
-        assertEquals(message, expected, matrix.getElement(row, column), 
StrictMath.abs(expected) * 1E-12);
+        assertEquals(message, expected, matrix.getElement(row, column), 
abs(expected) * 1E-12);
     }
 
     /**
@@ -204,7 +205,7 @@ public abstract class MatrixTestCase extends TestCase {
      */
     private double nextNonZeroRandom() {
         double value = random.nextDouble() * 200 - 100;
-        value += StrictMath.copySign(0.001, value);
+        value += copySign(0.001, value);
         if (random.nextBoolean()) {
             value = 1 / value;
         }
@@ -399,7 +400,7 @@ public abstract class MatrixTestCase extends TestCase {
             final double e = matrix.getElement(j, i);
             sum += e*e;
         }
-        return StrictMath.sqrt(sum);
+        return sqrt(sum);
     }
 
     /**
@@ -525,8 +526,8 @@ public abstract class MatrixTestCase extends TestCase {
             if ((n % 10) == 0) {
                 setRandomValues(at, matrix);
             }
-            at.translate(vector[0] = Math.fma(random.nextDouble(), 50, -25),
-                         vector[1] = Math.fma(random.nextDouble(), 50, -25));
+            at.translate(vector[0] = fma(random.nextDouble(), 50, -25),
+                         vector[1] = fma(random.nextDouble(), 50, -25));
             matrix.translate(vector);
             assertMatrixEquals("translate", AffineTransforms2D.toMatrix(at), 
matrix, TOLERANCE);
         }
@@ -536,10 +537,10 @@ public abstract class MatrixTestCase extends TestCase {
      * Sets random values in the given affine transform and a copy of those 
values in the given matrix.
      */
     private void setRandomValues(final AffineTransform at, final MatrixSIS 
matrix) {
-        at.setToRotation(random.nextDouble() * StrictMath.PI);
+        at.setToRotation(random.nextDouble() * PI);
         at.scale(nextNonZeroRandom(), nextNonZeroRandom());
-        at.translate(Math.fma(random.nextDouble(), 100, - 50),
-                     Math.fma(random.nextDouble(), 100, - 50));
+        at.translate(fma(random.nextDouble(), 100, - 50),
+                     fma(random.nextDouble(), 100, - 50));
         matrix.setElements(new double[] {
             at.getScaleX(), at.getShearX(), at.getTranslateX(),
             at.getShearY(), at.getScaleY(), at.getTranslateY(),
@@ -564,8 +565,8 @@ public abstract class MatrixTestCase extends TestCase {
             if ((n % 10) == 0) {
                 setRandomValues(at, matrix);
             }
-            vector[0] = Math.fma(random.nextDouble(), 50, -25);
-            vector[1] = Math.fma(random.nextDouble(), 50, -25);
+            vector[0] = fma(random.nextDouble(), 50, -25);
+            vector[1] = fma(random.nextDouble(), 50, -25);
             vector[2] = 1;
             final double[] result = matrix.multiply(vector);        // The 
result to verify.
             at.transform(vector, 0, vector, 0, 1);                  // The 
expected result.
@@ -596,7 +597,7 @@ public abstract class MatrixTestCase extends TestCase {
             final int nx = random.nextInt(8) + 1;
             elements = new double[numCol * nx];
             for (int k=0; k<elements.length; k++) {
-                elements[k] = Math.fma(random.nextDouble(), -10, 8);
+                elements[k] = fma(random.nextDouble(), -10, 8);
             }
             final Matrix referenceArg = new Matrix(elements, nx).transpose();
             final MatrixSIS matrixArg = Matrices.create(numCol, nx, elements);
@@ -636,7 +637,7 @@ public abstract class MatrixTestCase extends TestCase {
             final int nx = random.nextInt(8) + 1;
             elements = new double[numCol * nx];
             for (int k=0; k<elements.length; k++) {
-                elements[k] = Math.fma(random.nextDouble(), -10, 8);
+                elements[k] = fma(random.nextDouble(), -10, 8);
             }
             final Matrix referenceArg = new Matrix(elements, nx).transpose();
             final MatrixSIS matrixArg = Matrices.create(numCol, nx, elements);
diff --git 
a/core/sis-utility/src/main/java/org/apache/sis/internal/util/DoubleDouble.java 
b/core/sis-utility/src/main/java/org/apache/sis/internal/util/DoubleDouble.java
index af78f73076..6cf925f1ba 100644
--- 
a/core/sis-utility/src/main/java/org/apache/sis/internal/util/DoubleDouble.java
+++ 
b/core/sis-utility/src/main/java/org/apache/sis/internal/util/DoubleDouble.java
@@ -347,11 +347,15 @@ public final class DoubleDouble extends Number {
     public static double errorForWellKnownValue(final double value) {
         if (DISABLED) return 0;
         final int i = Arrays.binarySearch(VALUES, Math.abs(value));
+        final double error;
         if (i >= 0) {
-            return MathFunctions.xorSign(ERRORS[i], value);
+            error = MathFunctions.xorSign(ERRORS[i], value);
+        } else {
+            final double delta = 
DecimalFunctions.deltaForDoubleToDecimal(value);
+            error = Double.isNaN(delta) ? 0 : delta;
         }
-        final double delta = DecimalFunctions.deltaForDoubleToDecimal(value);
-        return Double.isNaN(delta) ? 0 : delta;
+        assert !(Math.abs(error) >= Math.ulp(value)) : value;       // Use ! 
for being tolerant to NaN.
+        return error;
     }
 
     /**
@@ -834,8 +838,8 @@ public final class DoubleDouble extends Number {
         final double thisValue = this.value;
         final double thisError = this.error;
         setToProduct(thisValue, otherValue);
-        error += otherError * thisValue;
-        error += otherValue * thisError;
+        error = Math.fma(otherError, thisValue, error);
+        error = Math.fma(otherValue, thisError, error);
         normalize();
     }
 
diff --git 
a/core/sis-utility/src/test/java/org/apache/sis/internal/util/DoubleDoubleTest.java
 
b/core/sis-utility/src/test/java/org/apache/sis/internal/util/DoubleDoubleTest.java
index 9b15b57586..f54ab22923 100644
--- 
a/core/sis-utility/src/test/java/org/apache/sis/internal/util/DoubleDoubleTest.java
+++ 
b/core/sis-utility/src/test/java/org/apache/sis/internal/util/DoubleDoubleTest.java
@@ -71,7 +71,7 @@ public final class DoubleDoubleTest extends TestCase {
      * in order to change only the exponent part of the IEEE representation.
      */
     private double nextRandom() {
-        return random.nextDouble() * 2048 - 1024;
+        return fma(random.nextDouble(), 2048, -1024);
     }
 
     /**
@@ -224,7 +224,7 @@ public final class DoubleDoubleTest extends TestCase {
             nextRandom(dd);
             nextRandom(op);
             final BigDecimal expected = toBigDecimal(dd).add(toBigDecimal(op));
-            dd.add(op);     // Must be after 'expected' computation.
+            dd.add(op);     // Must be after `expected` computation.
             assertExtendedEquals(expected, dd, ADD_TOLERANCE_FACTOR);
         }
     }
@@ -241,7 +241,7 @@ public final class DoubleDoubleTest extends TestCase {
             nextRandom(dd);
             nextRandom(op);
             final BigDecimal expected = 
toBigDecimal(dd).multiply(toBigDecimal(op), MathContext.DECIMAL128);
-            dd.multiply(op);    // Must be after 'expected' computation.
+            dd.multiply(op);    // Must be after `expected` computation.
             assertExtendedEquals(expected, dd, PRODUCT_TOLERANCE_FACTOR);
         }
     }
@@ -258,7 +258,7 @@ public final class DoubleDoubleTest extends TestCase {
             nextRandom(dd);
             nextRandom(op);
             final BigDecimal expected = 
toBigDecimal(dd).divide(toBigDecimal(op), MathContext.DECIMAL128);
-            dd.divide(op);      // Must be after 'expected' computation.
+            dd.divide(op);      // Must be after `expected` computation.
             assertExtendedEquals(expected, dd, PRODUCT_TOLERANCE_FACTOR);
         }
     }

Reply via email to