You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2023/01/09 13:52:33 UTC
[sis] branch geoapi-4.0 updated: Represent matrix elements as fractions when possible. This enhancement avoids rounding errors with, for example, unit conversions involving a division by 1000. It applies to creations, concatenations and inversions of `MathTransform` where performance is not the primary concern. It does not apply to the coordinate transformations executed by `MathTransform.transform(…)`, where performance matter.
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
The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
new 906f9ce512 Represent matrix elements as fractions when possible. This enhancement avoids rounding errors with, for example, unit conversions involving a division by 1000. It applies to creations, concatenations and inversions of `MathTransform` where performance is not the primary concern. It does not apply to the coordinate transformations executed by `MathTransform.transform(…)`, where performance matter.
906f9ce512 is described below
commit 906f9ce512247d87eb2053c2c78710988247679e
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Mon Jan 9 14:32:51 2023 +0100
Represent matrix elements as fractions when possible.
This enhancement avoids rounding errors with, for example, unit conversions involving a division by 1000.
It applies to creations, concatenations and inversions of `MathTransform` where performance is not the primary concern.
It does not apply to the coordinate transformations executed by `MathTransform.transform(…)`, where performance matter.
---
.../org/apache/sis/filter/ArithmeticFunction.java | 6 +-
.../sis/internal/referencing/Arithmetic.java | 95 ++++++++++++++++++----
.../referencing/ExtendedPrecisionMatrix.java | 11 ---
.../sis/internal/referencing/j2d/AffineMatrix.java | 12 ++-
.../sis/referencing/crs/DefaultTemporalCRS.java | 2 +-
.../operation/matrix/GeneralMatrix.java | 33 --------
.../referencing/operation/matrix/MatrixSIS.java | 52 ++++++++----
.../sis/referencing/operation/matrix/Solver.java | 6 +-
.../operation/matrix/UnmodifiableMatrix.java | 2 +-
.../operation/matrix/GeneralMatrixTest.java | 5 +-
.../operation/matrix/MatrixTestCase.java | 2 +-
.../org/apache/sis/internal/util/Numerics.java | 9 +-
.../main/java/org/apache/sis/math/Fraction.java | 66 +++++++++++++--
.../src/main/java/org/apache/sis/util/Numbers.java | 33 ++++----
.../test/java/org/apache/sis/util/NumbersTest.java | 8 +-
.../apache/sis/storage/aggregate/GridSlice.java | 5 +-
16 files changed, 219 insertions(+), 128 deletions(-)
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/ArithmeticFunction.java b/core/sis-feature/src/main/java/org/apache/sis/filter/ArithmeticFunction.java
index 694b2f1242..e8ec2b21ae 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/ArithmeticFunction.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/ArithmeticFunction.java
@@ -292,8 +292,8 @@ abstract class ArithmeticFunction<R> extends BinaryFunction<R,Number,Number>
if (BigInteger.ZERO.equals(r[1])) {
return r[0];
} else {
- return new Fraction(r[1].intValueExact(), right.intValueExact()).add(
- new Fraction(r[0].intValueExact(), 1));
+ return Fraction.valueOf(r[1].longValueExact(), right.longValueExact())
+ .add(new Fraction(r[0].intValueExact(), 1));
}
}
@@ -303,7 +303,7 @@ abstract class ArithmeticFunction<R> extends BinaryFunction<R,Number,Number>
if (left % right == 0) {
return r;
} else {
- return new Fraction(Math.toIntExact(left), Math.toIntExact(right));
+ return Fraction.valueOf(left, right);
}
}
}
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Arithmetic.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Arithmetic.java
index 57a56e74aa..22e5201cef 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Arithmetic.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Arithmetic.java
@@ -16,8 +16,10 @@
*/
package org.apache.sis.internal.referencing;
+import java.util.function.BiFunction;
import java.util.function.BinaryOperator;
import org.apache.sis.internal.util.DoubleDouble;
+import org.apache.sis.math.Fraction;
/**
@@ -32,37 +34,43 @@ public enum Arithmetic {
/**
* The addition operator.
*/
- ADD(DoubleDouble::add),
+ ADD(DoubleDouble::add, Fraction::add, Math::addExact),
/**
* The subtraction operator.
*/
- SUBTRACT(DoubleDouble::subtract),
+ SUBTRACT(DoubleDouble::subtract, Fraction::subtract, Math::subtractExact),
/**
* The multiplication operator.
*/
- MULTIPLY(DoubleDouble::multiply),
+ MULTIPLY(DoubleDouble::multiply, Fraction::multiply, Math::multiplyExact),
/**
* The division operator.
*/
- DIVIDE(DoubleDouble::divide),
+ DIVIDE(DoubleDouble::divide, Fraction::divide, Fraction::valueOf),
/**
* The inverse operation. Operand <var>b</var> is ignored and can be null.
*/
- INVERSE((a,b) -> a.inverse()),
+ INVERSE((a,b) -> a.inverse(),
+ (a,b) -> a.inverse(),
+ (a,b) -> new Fraction(1, Math.toIntExact(a))),
/**
* The negation operation. Operand <var>b</var> is ignored and can be null.
*/
- NEGATE((a,b) -> a.negate()),
+ NEGATE((a,b) -> a.negate(),
+ (a,b) -> a.negate(),
+ (a,b) -> Math.negateExact(a)),
/**
* The square root operation. Operand <var>b</var> is ignored and can be null.
*/
- SQRT((a,b) -> a.sqrt());
+ SQRT((a,b) -> a.sqrt(),
+ (a,b) -> DoubleDouble.of(a, false).sqrt(),
+ (a,b) -> DoubleDouble.of(a).sqrt());
/**
* Whether to assume that {@code float} and {@code double} values
@@ -75,28 +83,83 @@ public enum Arithmetic {
*/
private final BinaryOperator<DoubleDouble> onDoubleDouble;
+ /**
+ * The arithmetic operation applied on fractions.
+ * The operation may throw {@link ArithmeticException},
+ * in which case {@link #onDoubleDouble} will be used as fallback.
+ */
+ private final BiFunction<Fraction,Fraction,Number> onFraction;
+
+ /**
+ * The arithmetic operation applied on long integers.
+ * The operation may throw {@link ArithmeticException},
+ * in which case {@link #onDoubleDouble} will be used as fallback.
+ */
+ private final BiFunction<Long,Long,Number> onLong;
+
/**
* Creates a new arithmetic operator.
*/
- private Arithmetic(final BinaryOperator<DoubleDouble> onDoubleDouble) {
+ private Arithmetic(final BinaryOperator<DoubleDouble> onDoubleDouble,
+ final BiFunction<Fraction,Fraction,Number> onFraction,
+ final BiFunction<Long,Long,Number> onLong)
+ {
this.onDoubleDouble = onDoubleDouble;
+ this.onFraction = onFraction;
+ this.onLong = onLong;
+ }
+
+ /**
+ * Returns the value of the given number as a long integer if possible.
+ * If the conversion is not exact, then this method returns {@code null}.
+ *
+ * @param element the value to return as a long integer, or {@code null} if zero.
+ * @return the value as a long integer, or {@code null} if it can not be converted.
+ */
+ private static Long tryLongValue(final Number element) {
+ if (element == null || element instanceof Long) {
+ return (Long) element;
+ }
+ final long a = element.longValue();
+ return (a == element.doubleValue()) ? a : null;
}
/**
* Applies the operation on the given number.
*
- * @todo Current implementation handles only {@link DoubleDouble} values,
- * but more types will be added later.
+ * @param a the first operand. Shall not be null.
+ * @param b the second operation. May be null if it does not apply.
*/
private Number apply(final Number a, final Number b) {
- final DoubleDouble result = onDoubleDouble.apply(
- DoubleDouble.of(a, DECIMAL),
- DoubleDouble.of(b, DECIMAL));
-
- if (result.isZero()) return null;
+ Number result = null;
+ try {
+ final Long aLong = tryLongValue(a);
+ final Long bLong = tryLongValue(b);
+ if (aLong != null && (bLong != null || b == null)) {
+ result = onLong.apply(aLong, bLong);
+ } else {
+ Fraction aFrac = (a instanceof Fraction) ? (Fraction) a : null;
+ Fraction bFrac = (b instanceof Fraction) ? (Fraction) b : null;
+ if (aFrac != null || bFrac != null) {
+ if (aFrac == null && aLong != null) aFrac = new Fraction(Math.toIntExact(aLong), 1);
+ if (bFrac == null && bLong != null) bFrac = new Fraction(Math.toIntExact(bLong), 1);
+ if (aFrac != null && (bFrac != null || b == null)) {
+ result = onFraction.apply(aFrac, bFrac);
+ }
+ }
+ }
+ } catch (ArithmeticException e) {
+ // Ignore and fallback on double-double precision.
+ }
+ if (result == null) {
+ result = onDoubleDouble.apply(DoubleDouble.of(a, DECIMAL),
+ DoubleDouble.of(b, DECIMAL));
+ }
+ if (ExtendedPrecisionMatrix.isZero(result)) return null;
if (result.equals(a)) return a;
if (result.equals(b)) return b;
- return result;
+ final Number simplified = tryLongValue(result);
+ return (simplified != null) ? simplified : result;
}
/**
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ExtendedPrecisionMatrix.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ExtendedPrecisionMatrix.java
index a0b9872581..0a93024b35 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ExtendedPrecisionMatrix.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ExtendedPrecisionMatrix.java
@@ -45,17 +45,6 @@ public interface ExtendedPrecisionMatrix extends Matrix {
*/
Number[] CREATE_IDENTITY = new Number[0];
- /**
- * Returns the given matrix as an extended precision matrix if possible, or {@code other} otherwise.
- *
- * @param source the matrix to cast.
- * @param other the value to return if the source is not an instance of {@code ExtendedPrecisionMatrix}.
- * @return the given matrix or {@code other}.
- */
- static ExtendedPrecisionMatrix castOrElse(final Matrix source, final ExtendedPrecisionMatrix other) {
- return (source instanceof ExtendedPrecisionMatrix) ? (ExtendedPrecisionMatrix) source : other;
- }
-
/**
* Returns {@code true} if the given number is zero.
* This is the criterion used for identifying which {@link Number} elements shall be {@code null}.
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/j2d/AffineMatrix.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/j2d/AffineMatrix.java
index 4fa8ddbfea..2d719617bd 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/j2d/AffineMatrix.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/j2d/AffineMatrix.java
@@ -153,6 +153,7 @@ class AffineMatrix extends MatrixSIS implements Serializable, Cloneable {
*
* @param transform the transform to wrap.
* @param elements the elements used for creating the matrix.
+ * Zero values <em>shall</em> be null.
*/
ExtendedPrecision(final AffineTransform transform, final Number[] elements) {
super(transform);
@@ -162,6 +163,7 @@ class AffineMatrix extends MatrixSIS implements Serializable, Cloneable {
/**
* Returns all matrix elements in row-major order.
* Note that this is not the same order than {@link AffineTransform} constructor.
+ * Zero values <em>shall</em> be null.
*/
@Override
public Number[] getElementAsNumbers(final boolean writable) {
@@ -178,15 +180,11 @@ class AffineMatrix extends MatrixSIS implements Serializable, Cloneable {
public Number getElementOrNull(final int row, final int column) {
ArgumentChecks.ensureBetween("row", 0, AffineTransform2D.DIMENSION, row);
ArgumentChecks.ensureBetween("column", 0, AffineTransform2D.DIMENSION, column);
- if (row == AffineTransform2D.DIMENSION) {
- if (column == AffineTransform2D.DIMENSION) return 1;
+ if (row != AffineTransform2D.DIMENSION) {
+ return elements[row * SIZE + column];
} else {
- final Number value = elements[row * SIZE + column];
- if (!ExtendedPrecisionMatrix.isZero(value)) {
- return value;
- }
+ return (column == AffineTransform2D.DIMENSION) ? 1 : null;
}
- return null;
}
/**
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultTemporalCRS.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultTemporalCRS.java
index 99b7b49ad0..a8f40457f0 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultTemporalCRS.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultTemporalCRS.java
@@ -231,7 +231,7 @@ public class DefaultTemporalCRS extends AbstractCRS implements TemporalCRS {
* If it happens anyway, put the fractional amount of seconds in the converter instead of adding another
* field in this class for such very rare situation. Accuracy should be okay since the offset is small.
*/
- UnitConverter c = Units.converter(null, new Fraction((int) t, MILLIS_PER_SECOND).simplify());
+ UnitConverter c = Units.converter(null, Fraction.valueOf(t, MILLIS_PER_SECOND));
toSeconds = c.concatenate(toSeconds);
}
}
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 3ee241430c..d43410c5b9 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
@@ -20,9 +20,7 @@ import java.util.Arrays;
import org.opengis.referencing.operation.Matrix;
import org.apache.sis.util.ArgumentChecks;
import org.apache.sis.util.ArraysExt;
-import org.apache.sis.util.Numbers;
import org.apache.sis.internal.util.Numerics;
-import org.apache.sis.internal.util.DoubleDouble;
import org.apache.sis.internal.referencing.Arithmetic;
import org.apache.sis.internal.referencing.ExtendedPrecisionMatrix;
@@ -231,24 +229,6 @@ class GeneralMatrix extends MatrixSIS implements ExtendedPrecisionMatrix {
throw indexOutOfBounds(row, column);
}
- /**
- * Retrieves the value at the specified row and column of this matrix, rounded to nearest integer.
- *
- * @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()
- *
- * @since 1.3
- */
- @Override
- public final long getInteger(final int row, final int column) {
- final Number value = getElementOrNull(row, column);
- return (value != null) ? Numbers.toInteger(value) : 0;
- }
-
/**
* Retrieves the value at the specified row and column of this matrix as a {@code Number}.
*
@@ -449,19 +429,6 @@ class GeneralMatrix extends MatrixSIS implements ExtendedPrecisionMatrix {
assert isValid();
}
- /**
- * Returns the given matrix as an extended precision matrix.
- */
- static ExtendedPrecisionMatrix asExtendedPrecision(final Matrix matrix) {
- if (matrix instanceof UnmodifiableMatrix) {
- return ((UnmodifiableMatrix) matrix).asExtendePrecision();
- } else if (matrix instanceof ExtendedPrecisionMatrix) {
- return (ExtendedPrecisionMatrix) matrix;
- } else {
- return new UnmodifiableMatrix(matrix);
- }
- }
-
/**
* Returns {@code true} if the specified object is of type {@code GeneralMatrix} and
* all of the data members are equal to the corresponding data members in this matrix.
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 3571e1ae7d..89f873624e 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
@@ -27,6 +27,7 @@ import org.apache.sis.util.ArgumentChecks;
import org.apache.sis.util.ComparisonMode;
import org.apache.sis.util.LenientComparable;
import org.apache.sis.util.resources.Errors;
+import org.apache.sis.util.Numbers;
/**
@@ -142,6 +143,19 @@ public abstract class MatrixSIS implements Matrix, LenientComparable, Cloneable,
return Matrices.copy(matrix);
}
+ /**
+ * Returns the given matrix as an extended precision matrix.
+ */
+ static ExtendedPrecisionMatrix asExtendedPrecision(final Matrix matrix) {
+ if (matrix instanceof UnmodifiableMatrix) {
+ return ((UnmodifiableMatrix) matrix).asExtendePrecision();
+ } else if (matrix instanceof ExtendedPrecisionMatrix) {
+ return (ExtendedPrecisionMatrix) matrix;
+ } else {
+ return new UnmodifiableMatrix(matrix);
+ }
+ }
+
/**
* Retrieves the value at the specified row and column if different than zero.
* If the value is zero, then this method <em>shall</em> return {@code null}.
@@ -170,26 +184,34 @@ public abstract class MatrixSIS implements Matrix, LenientComparable, Cloneable,
* @throws ArithmeticException if the value is NaN or overflows integer capacity.
*
* @since 1.3
+ *
+ * @deprecated Replaced by {@code Numbers.round(getNumber(row, column))}.
+ * @see Numbers#round(Number)
*/
+ @Deprecated(since="1.4", forRemoval=true)
public long getInteger(int row, int 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));
+ return Numbers.round(getNumber(row, column));
}
/**
* Retrieves the value at the specified row and column of this matrix, wrapped in a {@code Number}.
* The {@code Number} type depends on the matrix accuracy.
*
+ * <h4>Use case</h4>
+ * This method may be more accurate than {@link #getElement(int, int)} in some implementations
+ * when the value is expected to be an integer, for example in conversions of pixel coordinates.
+ * {@link Number#longValue()} can be more accurate than {@link Number#doubleValue()} because a
+ * {@code long} may have more significant digits than what a {@code double} can contain.
+ * For safety against rounding errors and overflows,
+ * {@link Numbers#round(Number)} should be used instead of {@code Number.longValue()}.
+ *
* @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.
* @throws IndexOutOfBoundsException if the specified row or column is out of bounds.
*
* @see #getElement(int, int)
+ * @see Numbers#round(Number)
*/
public Number getNumber(int row, int column) {
return getElement(row, column);
@@ -265,21 +287,17 @@ public abstract class MatrixSIS implements Matrix, LenientComparable, Cloneable,
int dstRow, final int dstCol,
int numRow, final int numCol)
{
- final var exp = ExtendedPrecisionMatrix.castOrElse(source, null);
+ final var exp = asExtendedPrecision(source);
while (--numRow >= 0) {
for (int i=0; i<numCol; i++) {
- double value;
- if (exp != null) {
- final Number n = exp.getElementOrNull(srcRow, srcCol + i);
- if (n != null) {
- setNumber(dstRow, dstCol + i, n);
- continue;
- }
- value = 0;
+ final int s = srcCol + i;
+ final int t = dstCol + i;
+ final Number n = exp.getElementOrNull(srcRow, s);
+ if (n != null) {
+ setNumber(dstRow, t, n);
} else {
- value = source.getElement(srcRow, srcCol + i);
+ setElement(dstRow, t, source.getElement(srcRow, s)); // Preserve the sign of 0.
}
- setElement(dstRow, dstCol + i, value);
}
srcRow++;
dstRow++;
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/Solver.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/Solver.java
index 0c3f84101b..9662e1c114 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/Solver.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/Solver.java
@@ -124,7 +124,7 @@ final class Solver implements ExtendedPrecisionMatrix { // Not C
if (numCol != size) {
throw new NoninvertibleMatrixException(Resources.format(Resources.Keys.NonInvertibleMatrix_2, size, numCol));
}
- return solve(GeneralMatrix.asExtendedPrecision(X), IDENTITY, size, size);
+ return solve(MatrixSIS.asExtendedPrecision(X), IDENTITY, size, size);
}
/**
@@ -143,8 +143,8 @@ final class Solver implements ExtendedPrecisionMatrix { // Not C
}
final int innerSize = Y.getNumCol();
GeneralMatrix.ensureNumRowMatch(size, Y.getNumRow(), innerSize);
- return solve(GeneralMatrix.asExtendedPrecision(X),
- GeneralMatrix.asExtendedPrecision(Y),
+ return solve(MatrixSIS.asExtendedPrecision(X),
+ MatrixSIS.asExtendedPrecision(Y),
size, innerSize);
}
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/UnmodifiableMatrix.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/UnmodifiableMatrix.java
index 4bd0b23f31..6ea73d1e81 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/UnmodifiableMatrix.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/UnmodifiableMatrix.java
@@ -53,7 +53,7 @@ final class UnmodifiableMatrix extends MatrixSIS implements ExtendedPrecisionMat
* The returned matrix shall be considered read-only.
*/
final ExtendedPrecisionMatrix asExtendePrecision() {
- return ExtendedPrecisionMatrix.castOrElse(matrix, this);
+ return (matrix instanceof ExtendedPrecisionMatrix) ? (ExtendedPrecisionMatrix) matrix : this;
}
/**
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 eb52a5f499..89614a3865 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
@@ -60,8 +60,8 @@ public final class GeneralMatrixTest extends MatrixTestCase {
}
/**
- * Tests {@link GeneralMatrix#getNumber(int, int)} and {@link GeneralMatrix#getInteger(int, int)}
- * using a value which cannot be stored accurately in a {@code double} type.
+ * Tests {@link GeneralMatrix#getNumber(int, int)} using a value which
+ * cannot be stored accurately in a {@code double} type.
*/
@Test
public void testExtendedPrecision() {
@@ -73,7 +73,6 @@ public final class GeneralMatrixTest extends MatrixTestCase {
matrix.setNumber(0, 0, ddval);
assertEquals(value, ddval.longValue());
assertEquals(ddval, matrix.getNumber (0, 0));
- assertEquals(value, matrix.getInteger(0, 0));
validateImplementation(matrix);
}
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 86585e9a3b..7376c5c599 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
@@ -162,7 +162,7 @@ public abstract class MatrixTestCase extends TestCase {
}
final int numRow = matrix.getNumRow();
final int numCol = matrix.getNumCol();
- final var extend = GeneralMatrix.asExtendedPrecision(matrix);
+ final var extend = MatrixSIS.asExtendedPrecision(matrix);
final Number[] numbers = extend.getElementAsNumbers(false);
final double[] elements = (matrix instanceof MatrixSIS) ? ((MatrixSIS) matrix).getElements() : null;
assertEquals("getElementAsNumbers", numRow * numCol, numbers.length);
diff --git a/core/sis-utility/src/main/java/org/apache/sis/internal/util/Numerics.java b/core/sis-utility/src/main/java/org/apache/sis/internal/util/Numerics.java
index 8437a7a3db..25d2a9dc9b 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/internal/util/Numerics.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/internal/util/Numerics.java
@@ -329,12 +329,9 @@ public final class Numerics extends Static {
* @return the fraction as a {@link Fraction} or {@link Double} object.
*/
public static Number fraction(long numerator, long denominator) {
- final int simplify = Math.min(Long.numberOfTrailingZeros(numerator), Long.numberOfTrailingZeros(denominator));
- final int num = (int) (numerator >>= simplify);
- final int den = (int) (denominator >>= simplify);
- if (num == numerator && den == denominator) {
- return new Fraction(num, den).unique();
- } else {
+ try {
+ return Fraction.valueOf(numerator, denominator).unique();
+ } catch (ArithmeticException e) {
return valueOf(numerator / (double) denominator);
}
}
diff --git a/core/sis-utility/src/main/java/org/apache/sis/math/Fraction.java b/core/sis-utility/src/main/java/org/apache/sis/math/Fraction.java
index 33a78f5ed6..9c41ea3bd2 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/math/Fraction.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/math/Fraction.java
@@ -72,6 +72,22 @@ public final class Fraction extends Number implements Comparable<Fraction>, Seri
this.denominator = denominator;
}
+ /**
+ * Returns the given fraction after simplification.
+ * If the numerator or denominator is still too large for 32 bit integer after simplification,
+ * then an {@link ArithmeticException} is thrown.
+ *
+ * @param numerator numerator of the fraction to return.
+ * @param denominator denominator of the fraction to return.
+ * @return the simplified fraction.
+ * @throws ArithmeticException if the numerator and denominator can not be represented by 32 bit integers.
+ *
+ * @since 1.4
+ */
+ public static Fraction valueOf(final long numerator, final long denominator) {
+ return simplify(null, numerator, denominator);
+ }
+
/**
* Converts the given IEEE 754 double-precision value to a fraction. If successful, this method returns a fraction
* such as {@link #doubleValue()} is equal to the given value in the sense of {@link Double#equals(Object)}:
@@ -208,21 +224,22 @@ public final class Fraction extends Number implements Comparable<Fraction>, Seri
/**
* Returns a fraction equivalent to {@code num} / {@code den} after simplification.
- * If the simplified fraction is equal to {@code this}, then this method returns {@code this}.
+ * If the simplified fraction is equal to {@code f}, then this method returns {@code f}.
*
* <p>The arguments given to this method are the results of multiplications and additions of {@code int} values.
* This method fails if any argument value is {@link Long#MIN_VALUE} because that value cannot be made positive.
* However, it should never happen. Even in the worst scenario:</p>
*
- * {@prefomat java
+ * {@snippet lang="java" :
* long n = Math.multiplyFull(Integer.MIN_VALUE, Integer.MAX_VALUE);
* n += n;
- * }
+ * }
*
* Above result still slightly smaller in magnitude than {@code Long.MIN_VALUE}.
*/
private static Fraction simplify(final Fraction f, long num, long den) {
if (num == Long.MIN_VALUE || den == Long.MIN_VALUE) {
+ // TODO: replace by use of Math.absExact(…) in JDK15.
throw new ArithmeticException(Errors.format(Errors.Keys.IntegerOverflow_1, Long.SIZE));
}
if (num == 0) {
@@ -262,12 +279,30 @@ public final class Fraction extends Number implements Comparable<Fraction>, Seri
: new Fraction(Math.toIntExact(num), Math.toIntExact(den));
}
+ /**
+ * Returns the inverse value of this fraction.
+ * This method does not simplify the fraction.
+ *
+ * @return the result of {@code 1/this}.
+ * @throws ArithmeticException if the result overflows.
+ *
+ * @since 1.4
+ *
+ * @see #divide(Fraction)
+ */
+ public Fraction inverse() {
+ if (numerator == denominator) return this;
+ return new Fraction(denominator, numerator);
+ }
+
/**
* Returns the negative value of this fraction.
* This method does not simplify the fraction.
*
* @return the result of {@code -this}.
* @throws ArithmeticException if the result overflows.
+ *
+ * @see #subtract(Fraction)
*/
public Fraction negate() {
int n = numerator;
@@ -302,6 +337,8 @@ public final class Fraction extends Number implements Comparable<Fraction>, Seri
* @param other the fraction to subtract from this fraction.
* @return the simplified result of {@code this} - {@code other}.
* @throws ArithmeticException if the result overflows.
+ *
+ * @see #negate()
*/
public Fraction subtract(final Fraction other) {
// Intermediate result must be computed in a type wider that the 'numerator' and 'denominator' type.
@@ -328,6 +365,8 @@ public final class Fraction extends Number implements Comparable<Fraction>, Seri
* @param other the fraction by which to divide this fraction.
* @return the simplified result of {@code this} ∕ {@code other}.
* @throws ArithmeticException if the result overflows.
+ *
+ * @see #inverse()
*/
public Fraction divide(final Fraction other) {
return simplify(this, Math.multiplyFull(numerator, other.denominator),
@@ -433,17 +472,27 @@ public final class Fraction extends Number implements Comparable<Fraction>, Seri
/**
* Returns this fraction rounded toward zero.
+ * If the fraction value {@linkplain #isNaN() is NaN}, then this method returns 0.
+ * If the fraction value is positive or negative infinity, then this method returns
+ * {@link Long#MAX_VALUE} or {@link Long#MIN_VALUE} respectively.
*
* @return this fraction rounded toward zero.
*/
@Override
public long longValue() {
- return intValue();
+ if (denominator != 0) {
+ return numerator / denominator;
+ }
+ if (numerator < 0) return Long.MIN_VALUE;
+ if (numerator > 0) return Long.MAX_VALUE;
+ return 0;
}
/**
* Returns this fraction rounded toward zero.
- * If the fraction value is NaN, then this method returns 0.
+ * If the fraction value {@linkplain #isNaN() is NaN}, then this method returns 0.
+ * If the fraction value is positive or negative infinity, then this method returns
+ * {@link Integer#MAX_VALUE} or {@link Integer#MIN_VALUE} respectively.
*
* @return {@link #numerator} / {@link #denominator} rounded toward zero.
*
@@ -453,7 +502,12 @@ public final class Fraction extends Number implements Comparable<Fraction>, Seri
*/
@Override
public int intValue() {
- return isNaN() ? 0 : numerator / denominator;
+ if (denominator != 0) {
+ return numerator / denominator;
+ }
+ if (numerator < 0) return Integer.MIN_VALUE;
+ if (numerator > 0) return Integer.MAX_VALUE;
+ return 0;
}
/*
diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/Numbers.java b/core/sis-utility/src/main/java/org/apache/sis/util/Numbers.java
index 46979ce218..6ae0d9ebdc 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/util/Numbers.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/util/Numbers.java
@@ -179,7 +179,7 @@ public final class Numbers extends Static {
* @return {@code true} if {@code type} is an integer type.
*
* @see #isFloat(Class)
- * @see #toInteger(Number)
+ * @see #round(Number)
*/
public static boolean isInteger(final Class<?> type) {
final Numbers mapping = MAPPING.get(type);
@@ -228,41 +228,44 @@ public final class Numbers extends Static {
}
/**
- * Returns the value of the given number as a {@code long} integer.
- * This method makes the following choice based on the number type:
+ * Returns the value of the given number rounded to nearest {@code long} integer.
+ * This method is intended for calculations where fractional parts are rounding errors.
+ * An {@link ArithmeticException} is thrown in the following cases:
*
* <ul>
- * <li>If {@link BigDecimal} or {@link BigInteger}, delegate to its {@code longValueExact()} method.</li>
- * <li>If {@linkplain #isInteger(Class) an integer}, delegate to {@link Number#longValue()}.</li>
- * <li>Otherwise delegate to {@link Number#doubleValue()}, round the value and verify that
- * the result is representable as a {@code long}.</li>
+ * <li>If the floating point value is NaN or positive or negative infinity.</li>
+ * <li>If the value overflows the capacity of {@value Long#SIZE} bits integers.</li>
+ * <li>If the number is a {@link BigDecimal} with a non-zero fractional part.</li>
* </ul>
*
+ * The justification for the last case is that {@link BigDecimal} is used when calculations
+ * should be exact in base 10. In such case, a fractional part would not be a rounding error.
+ *
* @param value the value to return as a long integer.
- * @return the value as a long integer, possibly rounded to nearest integer.
+ * @return the value rounded to nearest long integer.
* @throws NullPointerException if the given number is {@code null}.
* @throws ArithmeticException if the value can not be represented as a long integer.
*
+ * @see Math#round(double)
* @see BigDecimal#longValueExact()
* @see BigInteger#longValueExact()
*
* @since 1.4
*/
- public static long toInteger(final Number value) {
+ public static long round(final Number value) {
final Numbers mapping = MAPPING.get(value.getClass());
- if (mapping != null) {
+asLong: if (mapping != null) {
switch (mapping.ordinal) {
- case DOUBLE_DOUBLE: return value.longValue(); // Does rounding.
+ case DOUBLE_DOUBLE: break;
case BIG_DECIMAL: return ((BigDecimal) value).longValueExact();
case BIG_INTEGER: return ((BigInteger) value).longValueExact();
+ default: if (!mapping.isInteger) break asLong;
}
- if (mapping.isInteger) return value.longValue();
+ return value.longValue(); // Does rounding in `DoubleDouble` case.
}
final double v = value.doubleValue();
final long n = Math.round(v);
- if (Math.abs(v - n) <= 0.5) {
- return n;
- }
+ if (Math.abs(v - n) <= 0.5) return n;
throw new ArithmeticException(Errors.format(Errors.Keys.CanNotConvertValue_2, value, Long.TYPE));
}
diff --git a/core/sis-utility/src/test/java/org/apache/sis/util/NumbersTest.java b/core/sis-utility/src/test/java/org/apache/sis/util/NumbersTest.java
index 2a094a02dc..b7618a8207 100644
--- a/core/sis-utility/src/test/java/org/apache/sis/util/NumbersTest.java
+++ b/core/sis-utility/src/test/java/org/apache/sis/util/NumbersTest.java
@@ -83,13 +83,13 @@ public final class NumbersTest extends TestCase {
}
/**
- * Tests {@link Numbers#toInteger(Number)}.
+ * Tests {@link Numbers#round(Number)}.
*/
@Test
- public void testToInteger() {
- assertEquals(123456, Numbers.toInteger(123456.2f));
+ public void testRound() {
+ assertEquals(123456, Numbers.round(123456.2f));
try {
- Numbers.toInteger(Long.MAX_VALUE * 3d);
+ Numbers.round(Long.MAX_VALUE * 3d);
fail("Expected ArithmeticException");
} catch (ArithmeticException e) {
assertNotNull(e.getMessage());
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/GridSlice.java b/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/GridSlice.java
index 5b27a0f172..489af18d74 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/GridSlice.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/aggregate/GridSlice.java
@@ -30,6 +30,7 @@ import org.apache.sis.coverage.grid.GridGeometry;
import org.apache.sis.coverage.grid.GridExtent;
import org.apache.sis.internal.util.Numerics;
import org.apache.sis.internal.util.Strings;
+import org.apache.sis.util.Numbers;
/**
@@ -79,13 +80,14 @@ final class GridSlice {
*
* @param groupToSlice conversion from source coordinates of {@link GroupByTransform#gridToCRS}
* to grid coordinates of {@link #geometry}.
+ * @throws ArithmeticException if a translation term is NaN or overflows {@code long} integer capacity.
*
* @see #getOffset(Map)
*/
private void setOffset(final MatrixSIS groupToSlice) {
final int i = groupToSlice.getNumCol() - 1;
for (int j=0; j<offset.length; j++) {
- offset[j] = groupToSlice.getInteger(j, i);
+ offset[j] = Numbers.round(groupToSlice.getNumber(j, i));
}
}
@@ -132,6 +134,7 @@ final class GridSlice {
* @param strategy algorithm to apply when more than one grid coverage can be found at the same grid index.
* @return group of objects associated to the given transform (never null).
* @throws NoninvertibleTransformException if the transform is not invertible.
+ * @throws ArithmeticException if a translation term is NaN or overflows {@code long} integer capacity.
*/
final GroupByTransform getList(final List<GroupByCRS<GroupByTransform>> groups, final MergeStrategy strategy)
throws NoninvertibleTransformException