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 11eaff770e13b1c311fa033062ce3a91a8dd3ccf Author: Martin Desruisseaux <martin.desruisse...@geomatys.com> AuthorDate: Tue Aug 23 19:25:06 2022 +0200 Add overflow-safe `GridExtent.getMedian(int)` method. Leverage existing methods (minor update). --- .../org/apache/sis/coverage/grid/GridExtent.java | 33 ++++++++++++++++++++++ .../apache/sis/coverage/grid/GridExtentTest.java | 7 +++-- .../org/apache/sis/internal/util/Numerics.java | 2 ++ .../java/org/apache/sis/math/MathFunctions.java | 2 +- .../main/java/org/apache/sis/math/Statistics.java | 17 +++++------ 5 files changed, 50 insertions(+), 11 deletions(-) diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtent.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtent.java index af34fe7c5a..1fdc1aed7f 100644 --- a/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtent.java +++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/grid/GridExtent.java @@ -747,6 +747,39 @@ public class GridExtent implements GridEnvelope, LenientComparable, Serializable return coordinates[index + dimension]; } + /** + * Returns the average of low and high coordinates, rounded toward positive infinity. + * This method is equivalent to computing any of the following, + * except that this method does not overflow even if the sum would overflow: + * + * <ul> + * <li>(<var>low</var> + <var>high</var>) / 2 rounded toward positive infinity, or</li> + * <li>(<var>low</var> + <var>high</var> + 1) / 2 rounded toward negative infinity.</li> + * </ul> + * + * The two above formulas are equivalent, so the result does not depend + * on whether the high coordinate should be inclusive or exclusive. + * + * @param index the dimension for which to obtain the coordinate value. + * @return the median coordinate value at the given dimension. + * @throws IndexOutOfBoundsException if the given index is negative or is equal or greater + * than the {@linkplain #getDimension() grid dimension}. + * + * @since 1.3 + */ + public long getMedian(final int index) { + final int dimension = getDimension(); + ArgumentChecks.ensureValidIndex(dimension, index); + final long low = coordinates[index]; + final long high = coordinates[index + dimension]; + /* + * Use `>> 1` instead of `/2` because the two operations differ in their rounding mode for negative values. + * The former rounds toward negative infinity (which is intended here) while the latter rounds toward zero. + * If at least one value is odd, add +1 to the result. + */ + return (low >> 1) + (high >> 1) + ((low | high) & 1); + } + /** * Returns the number of integer grid coordinates along the specified dimension. * This is equal to {@code getHigh(dimension) - getLow(dimension) + 1}. diff --git a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridExtentTest.java b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridExtentTest.java index 843d330b46..65cec4bcf4 100644 --- a/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridExtentTest.java +++ b/core/sis-feature/src/test/java/org/apache/sis/coverage/grid/GridExtentTest.java @@ -34,6 +34,7 @@ import org.apache.sis.referencing.operation.matrix.Matrices; import org.apache.sis.referencing.operation.matrix.Matrix3; import org.apache.sis.referencing.crs.HardCodedCRS; import org.apache.sis.util.resources.Vocabulary; +import org.apache.sis.internal.util.Numerics; import org.apache.sis.internal.jdk9.JDK9; import org.apache.sis.test.TestCase; import org.junit.Test; @@ -64,8 +65,10 @@ public final strictfp class GridExtentTest extends TestCase { * Verifies the low and high values in the specified dimension of the given extent */ static void assertExtentEquals(final GridExtent extent, final int dimension, final int low, final int high) { - assertEquals("low", low, extent.getLow (dimension)); - assertEquals("high", high, extent.getHigh(dimension)); + assertEquals("low", low, extent.getLow (dimension)); + assertEquals("high", high, extent.getHigh(dimension)); + assertEquals("size", high - low + 1, extent.getSize(dimension)); + assertEquals("median", Numerics.ceilDiv(high + low, 2), extent.getMedian(dimension)); } /** 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 47be423a21..fce0e2b2fc 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 @@ -215,6 +215,8 @@ public final class Numerics extends Static { * @return x/y rounded toward positive infinity. * * @see Math#floorDiv(int, int) + * + * @todo Replace by {@link Math#ceilDiv(int, int)} in JDK18. */ public static int ceilDiv(final int x, final int y) { int r = x / y; diff --git a/core/sis-utility/src/main/java/org/apache/sis/math/MathFunctions.java b/core/sis-utility/src/main/java/org/apache/sis/math/MathFunctions.java index 0cdee86f93..ac0b663322 100644 --- a/core/sis-utility/src/main/java/org/apache/sis/math/MathFunctions.java +++ b/core/sis-utility/src/main/java/org/apache/sis/math/MathFunctions.java @@ -775,7 +775,7 @@ public final class MathFunctions extends Static { */ return Double.NaN; } - exp -= (16383 - 1023); // Change from 15 bias to 11 bias. + exp -= (16383 - Double.MAX_EXPONENT); // Change from 15 bias to 11 bias. // Check cases where mantissa excess what double can support. if (exp < 0) return Double.NEGATIVE_INFINITY; if (exp > 2046) return Double.POSITIVE_INFINITY; diff --git a/core/sis-utility/src/main/java/org/apache/sis/math/Statistics.java b/core/sis-utility/src/main/java/org/apache/sis/math/Statistics.java index b89e6b50b1..0b1efbbc7a 100644 --- a/core/sis-utility/src/main/java/org/apache/sis/math/Statistics.java +++ b/core/sis-utility/src/main/java/org/apache/sis/math/Statistics.java @@ -24,6 +24,7 @@ import org.opengis.util.InternationalString; import org.apache.sis.util.SimpleInternationalString; import org.apache.sis.util.ArgumentChecks; import org.apache.sis.util.resources.Errors; +import org.apache.sis.internal.util.Numerics; import org.apache.sis.internal.util.DoubleDouble; import static java.lang.Math.*; @@ -612,7 +613,7 @@ public class Statistics implements DoubleConsumer, LongConsumer, Cloneable, Seri 31 * (doubleToLongBits(maximum) + 31 * (doubleToLongBits(sum) + 31 * (doubleToLongBits(squareSum))))); - return (int) code ^ (int) (code >>> 32) ^ count; + return Long.hashCode(code) ^ count; } /** @@ -624,13 +625,13 @@ public class Statistics implements DoubleConsumer, LongConsumer, Cloneable, Seri @Override public boolean equals(final Object object) { if (object != null && getClass() == object.getClass()) { - final Statistics cast = (Statistics) object; - return count == cast.count && countNaN == cast.countNaN - && doubleToLongBits(minimum) == doubleToLongBits(cast.minimum) - && doubleToLongBits(maximum) == doubleToLongBits(cast.maximum) - && doubleToLongBits(sum) == doubleToLongBits(cast.sum) - && doubleToLongBits(squareSum) == doubleToLongBits(cast.squareSum) - && Objects.equals(name, cast.name); + final Statistics other = (Statistics) object; + return count == other.count && countNaN == other.countNaN + && Numerics.equals(minimum, other.minimum) + && Numerics.equals(maximum, other.maximum) + && Numerics.equals(sum, other.sum) + && Numerics.equals(squareSum, other.squareSum) + && Objects .equals(name, other.name); } return false; }