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 d8cb52a4ca Resolve more cases about FMA identified by "JDK9" comment. d8cb52a4ca is described below commit d8cb52a4ca653c9f32b9ec83181b81e05f5c9c5f Author: Martin Desruisseaux <martin.desruisse...@geomatys.com> AuthorDate: Thu Dec 29 18:21:41 2022 +0100 Resolve more cases about FMA identified by "JDK9" comment. https://issues.apache.org/jira/browse/SIS-136 --- .../operation/projection/MeridianArcBased.java | 58 +++++++++++++++------- .../operation/projection/package-info.java | 2 +- .../operation/transform/LinearInterpolator1D.java | 2 +- .../operation/matrix/MatrixTestCase.java | 20 ++++---- .../operation/projection/SinusoidalTest.java | 4 +- .../org/apache/sis/internal/util/DoubleDouble.java | 56 ++++----------------- .../org/apache/sis/internal/util/Numerics.java | 8 +++ .../java/org/apache/sis/math/MathFunctions.java | 37 ++++++++------ .../java/org/apache/sis/math/package-info.java | 2 +- .../org/apache/sis/measure/LinearConverter.java | 9 ++-- .../apache/sis/measure/LinearConverterTest.java | 1 - 11 files changed, 98 insertions(+), 101 deletions(-) diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/MeridianArcBased.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/MeridianArcBased.java index 993181b3e7..b9c784e698 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/MeridianArcBased.java +++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/MeridianArcBased.java @@ -16,6 +16,7 @@ */ package org.apache.sis.referencing.operation.projection; +import org.apache.sis.internal.util.Numerics; import org.apache.sis.internal.util.DoubleDouble; import static java.lang.Math.*; @@ -27,7 +28,7 @@ import static java.lang.Math.*; * * @author Martin Desruisseaux (MPO, IRD, Geomatys) * @author Rémi Maréchal (Geomatys) - * @version 1.0 + * @version 1.4 * * @see <a href="https://en.wikipedia.org/wiki/Meridian_arc">Meridian arc on Wikipedia</a> * @@ -113,12 +114,12 @@ abstract class MeridianArcBased extends NormalizedProjection { * of 2, which result in exact representations in IEEE 754 double type. Derivation from Kawase formula can be * viewed at: https://svn.apache.org/repos/asf/sis/analysis/Map%20projection%20formulas.ods */ - cf0 = -441./0x10000 * e10 + -175./0x4000 * e8 + -5./0x100 * e6 + -3./0x40 * e4 + -1./4 * e2 + 1; - cf1 = 1575./0x20000 * e10 + 175./0x4000 * e8 + 5./0x100 * e6 + 3./0x40 * e4 + -3./4 * e2; - cf2 = -2625./0x8000 * e10 + 175./0x6000 * e8 + 5120./0x60000 * e6 + -15./0x20 * e4; - cf3 = 735./0x800 * e10 + 2240./0x60000 * e8 + -35./0x60 * e6; - cf4 = -2205./0x1000 * e10 + -315./0x400 * e8; - // cf5 = 693./0x20000 * e10 omitted for now (not yet used). + cf0 = fma(e10, -441./0x10000, fma(e8, -175./0x4000, fma(e6, -5./0x100, fma(e4, -3./0x40, fma(e2, -1./4, 1))))); + cf1 = fma(e10, 1575./0x20000, fma(e8, 175./0x4000, fma(e6, 5./0x100, fma(e4, 3./0x40, e2*(-3./4))))); + cf2 = fma(e10, -2625./0x8000, fma(e8, 175./0x6000, fma(e6, 5120./0x60000, e4*(-15./0x20)))); + cf3 = fma(e10, 735./0x800, fma(e8, 2240./0x60000, e6* (-35./0x60))); + cf4 = fma(e10, -2205./0x1000, e8*(-315./0x400)); + // cf5 = e10* (693./0x20000) omitted for now (not yet used). /* * Coefficients for inverse transform derived from Snyder 3-26 and EPSG guidance notes: * @@ -134,17 +135,19 @@ abstract class MeridianArcBased extends NormalizedProjection { * µ is the rectifying latitude. * Derivation of coefficients used by this class are provided in the above-cited spreadsheet. */ - rµ = (1 - 1./4*e2 - 3./64*e4 - 5./256*e6); // Part of Snyder 7-19 for computing rectifying latitude. + rµ = fma(e6, -5./256, + fma(e4, -3./64, + fma(e2, -1./4, 1))); // Part of Snyder 7-19 for computing rectifying latitude. DoubleDouble e1 = initializer.axisLengthRatio(); e1.ratio_1m_1p(); final double ei = e1.doubleValue(); // Equivalent to [1 - √(1 - ℯ²)] / [1 + √(1 - ℯ²)] (Snyder 3-24). final double ei2 = ei*ei; final double ei3 = ei*ei2; final double ei4 = ei2*ei2; - ci1 = 657./0x40 * ei4 + 31./4 * ei3 + 21./4 * ei2 + 3./1 * ei; - ci2 = -5045./0x20 * ei4 + -151./3 * ei3 + -21./2 * ei2; - ci3 = 3291./0x08 * ei4 + 151./3 * ei3; - ci4 = -1097./0x04 * ei4; + ci1 = fma(ei4, 657./0x40, fma(ei3, 31./4, fma(ei2, 21./4, ei*(3./1)))); + ci2 = fma(ei4, -5045./0x20, fma(ei3, -151./3, ei2*(-21./2))); + ci3 = fma(ei4, 3291./0x08, ei3* (151./3)); + ci4 = ei4* (-1097./0x04); } /** @@ -183,7 +186,11 @@ abstract class MeridianArcBased extends NormalizedProjection { */ final double distance(final double φ, final double sinφ, final double cosφ) { final double sinφ2 = sinφ * sinφ; - return cf0*φ + cosφ*sinφ*(cf1 + sinφ2*(cf2 + sinφ2*(cf3 + sinφ2*cf4))); // TODO: use Math.fma with JDK9. + if (Numerics.USE_FMA) { + return fma(cosφ*sinφ, fma(sinφ2, fma(sinφ2, fma(sinφ2, cf4, cf3), cf2), cf1), cf0*φ); + } else { + return cf0*φ + cosφ*sinφ*(cf1 + sinφ2*(cf2 + sinφ2*(cf3 + sinφ2*cf4))); + } } /** @@ -192,11 +199,19 @@ abstract class MeridianArcBased extends NormalizedProjection { * @return the derivative at the specified latitude. */ final double dM_dφ(final double sinφ2) { - return ((((7 - 8*sinφ2)*cf4 - 6*cf3) * sinφ2 - + 5*cf3 - 4*cf2) * sinφ2 - + 3*cf2 - 2*cf1) * sinφ2 - + cf1 - + cf0; + if (Numerics.USE_FMA) { + return fma(fma(fma(fma(fma(-8, sinφ2, 7), + cf4, -6*cf3), sinφ2, + 5*cf3 - 4*cf2), sinφ2, + 3*cf2 - 2*cf1), sinφ2, + cf1) + cf0; + } else { + return ((((7 + -8*sinφ2)*cf4 - 6*cf3) * sinφ2 + + 5*cf3 - 4*cf2) * sinφ2 + + 3*cf2 - 2*cf1) * sinφ2 + + cf1 + + cf0; + } } /** @@ -207,9 +222,14 @@ abstract class MeridianArcBased extends NormalizedProjection { */ final double latitude(final double distance) { double φ = distance / rµ; // Rectifying latitude µ used as first approximation. + double cosφ = cos(φ); double sinφ = sin(φ); double sinφ2 = sinφ * sinφ; - φ += cos(φ)*sinφ*(ci1 + sinφ2*(ci2 + sinφ2*(ci3 + sinφ2*ci4))); // Snyder 3-26. + if (Numerics.USE_FMA) { + φ = fma(cosφ*sinφ, fma(sinφ2, fma(sinφ2, fma(sinφ2, ci4, ci3), ci2), ci1), φ); + } else { + φ += cosφ*sinφ*(ci1 + sinφ2*(ci2 + sinφ2*(ci3 + sinφ2*ci4))); // Snyder 3-26. + } /* * We could improve accuracy by continuing from here with Newton's iterative method * (see MeridianArcTest.inverse(…) for implementation). However, those iterations requires diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/package-info.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/package-info.java index 28a6bcbb26..0fc98bd16b 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/package-info.java +++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/package-info.java @@ -160,7 +160,7 @@ * @author Rémi Maréchal (Geomatys) * @author Adrian Custer (Geomatys) * @author Matthieu Bastianelli (Geomatys) - * @version 1.3 + * @version 1.4 * * @see <a href="https://mathworld.wolfram.com/MapProjection.html">Map projections on MathWorld</a> * diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/LinearInterpolator1D.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/LinearInterpolator1D.java index 642f55ee41..b18873bf7d 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/LinearInterpolator1D.java +++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/LinearInterpolator1D.java @@ -123,7 +123,7 @@ final class LinearInterpolator1D extends AbstractMathTransform1D implements Seri return LinearTransform1D.create(slope, offset); } value = values[i]; - } while (Numerics.epsilonEqual(value, offset + slope*i, // TODO: use Math.fma with JDK9. + } while (Numerics.epsilonEqual(value, Math.fma(i, slope, offset), Math.max(Math.abs(value), as) * Numerics.COMPARISON_THRESHOLD)); /* * If the values are in decreasing order, reverse their sign so we get increasing order. 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 9637f91165..c46f9dd025 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 @@ -45,7 +45,7 @@ import static org.apache.sis.test.Assert.*; * <p>This class uses <a href="http://math.nist.gov/javanumerics/jama">JAMA</a> as the reference implementation.</p> * * @author Martin Desruisseaux (Geomatys) - * @version 1.1 + * @version 1.4 * @since 0.4 */ public abstract class MatrixTestCase extends TestCase { @@ -184,7 +184,7 @@ public abstract class MatrixTestCase extends TestCase { { assertEquals("numRow", numRow, actual.getNumRow()); assertEquals("numCol", numCol, actual.getNumCol()); - assertArrayEquals(expected, actual.getElements(), tolerance); // First because more informative in case of failure. + assertArrayEquals(expected, actual.getElements(), tolerance); // First because more informative in case of failure. assertTrue(Matrices.create(numRow, numCol, expected).equals(actual, tolerance)); } @@ -525,8 +525,8 @@ public abstract class MatrixTestCase extends TestCase { if ((n % 10) == 0) { setRandomValues(at, matrix); } - at.translate(vector[0] = random.nextDouble() * 50 - 25, - vector[1] = random.nextDouble() * 50 - 25); + at.translate(vector[0] = Math.fma(random.nextDouble(), 50, -25), + vector[1] = Math.fma(random.nextDouble(), 50, -25)); matrix.translate(vector); assertMatrixEquals("translate", AffineTransforms2D.toMatrix(at), matrix, TOLERANCE); } @@ -538,8 +538,8 @@ public abstract class MatrixTestCase extends TestCase { private void setRandomValues(final AffineTransform at, final MatrixSIS matrix) { at.setToRotation(random.nextDouble() * StrictMath.PI); at.scale(nextNonZeroRandom(), nextNonZeroRandom()); - at.translate(random.nextDouble() * 100 - 50, - random.nextDouble() * 100 - 50); + at.translate(Math.fma(random.nextDouble(), 100, - 50), + Math.fma(random.nextDouble(), 100, - 50)); matrix.setElements(new double[] { at.getScaleX(), at.getShearX(), at.getTranslateX(), at.getShearY(), at.getScaleY(), at.getTranslateY(), @@ -564,8 +564,8 @@ public abstract class MatrixTestCase extends TestCase { if ((n % 10) == 0) { setRandomValues(at, matrix); } - vector[0] = random.nextDouble() * 50 - 25; - vector[1] = random.nextDouble() * 50 - 25; + vector[0] = Math.fma(random.nextDouble(), 50, -25); + vector[1] = Math.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 +596,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] = 8 - random.nextDouble() * 10; + elements[k] = Math.fma(random.nextDouble(), -10, 8); } final Matrix referenceArg = new Matrix(elements, nx).transpose(); final MatrixSIS matrixArg = Matrices.create(numCol, nx, elements); @@ -636,7 +636,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] = 8 - random.nextDouble() * 10; + elements[k] = Math.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-referencing/src/test/java/org/apache/sis/referencing/operation/projection/SinusoidalTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/SinusoidalTest.java index 4a283b6fc4..9d487a3617 100644 --- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/SinusoidalTest.java +++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/SinusoidalTest.java @@ -28,7 +28,7 @@ import org.junit.Test; * Tests the {@link Sinusoidal} projection. * * @author Martin Desruisseaux (Geomatys) - * @version 1.0 + * @version 1.4 * @since 1.0 */ @DependsOn(MeridianArcTest.class) @@ -123,7 +123,7 @@ public final class SinusoidalTest extends MapProjectionTestCase { createProjection(true); final double delta = (1.0 / 60) / 1852; // Approximately 1 metre. derivativeDeltas = new double[] {delta, delta}; - tolerance = Formulas.LINEAR_TOLERANCE / 10000; + tolerance = Formulas.LINEAR_TOLERANCE / 10; verifyDerivative(105, 30); verifyDerivative(100, -60); } 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 ad06dc676b..af78f73076 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 @@ -46,17 +46,8 @@ import org.apache.sis.math.DecimalFunctions; * BigDecimal decimal = new BigDecimal(dd.value).add(new BigDecimal(dd.error)); * } * - * <h2>Impact of availability of FMA instructions</h2> - * When allowed to use <cite>fused multiply-add</cite> (FMA) instruction added in JDK9 - * (see <a href="https://issues.apache.org/jira/browse/SIS-136">SIS-136</a> on Apache SIS JIRA), - * then the following methods should be revisited: - * - * <ul> - * <li>{@link #setToProduct(double, double)} - revisit with [Hida & al.] algorithm 7.</li> - * </ul> - * * @author Martin Desruisseaux (Geomatys) - * @version 1.3 + * @version 1.4 * * @see <a href="https://en.wikipedia.org/wiki/Double-double_%28arithmetic%29#Double-double_arithmetic">Wikipedia: Double-double arithmetic</a> * @@ -92,29 +83,6 @@ public final class DoubleDouble extends Number { */ private static final int ZERO_THRESHOLD = 2; - /** - * The split constant used as part of multiplication algorithms. The split algorithm is as below - * (we have to inline it in multiplication methods because Java cannot return multi-values): - * - * {@snippet lang="java" : - * private void split(double a) { - * double t = SPLIT * a; - * double ahi = t - (t - a); - * double alo = a - ahi; - * } - * } - * - * <p>Source: [Hida & al.] page 4 algorithm 5, itself reproduced from [Shewchuk] page 325.</p> - */ - private static final double SPLIT = (1 << 27) + 1; - - /** - * Maximal value that can be handled by {@link #multiply(double, double)}. - * If a multiplication is using a value greater than {@code MAX_VALUE}, - * then the result will be infinity or NaN. - */ - public static final double MAX_VALUE = Double.MAX_VALUE / SPLIT; - /** * Pre-defined constants frequently used in SIS, sorted in increasing order. This table contains only * constants that cannot be inferred by {@link DecimalFunctions#deltaForDoubleToDecimal(double)}, @@ -484,20 +452,16 @@ public final class DoubleDouble extends Number { * Sets this {@code DoubleDouble} to the product of the given numbers. * The given numbers shall not be greater than {@value #MAX_VALUE} in magnitude. * - * <p>Source: [Hida & al.] page 4 algorithm 6, itself reproduced from [Shewchuk] page 326.</p> + * <p>Source: [Hida & al.] page 5 algorithm 7, itself reproduced from [Shewchuk] page 326. + * This is the algorithm used when FMA instruction is available. For an algorithm without FMA, + * see [Hida & al.] page 4 algorithm 6 (it is more complicated).</p> * * @param a the first number to multiply. * @param b the second number to multiply. */ public void setToProduct(final double a, final double b) { value = a * b; - double t = SPLIT * a; - final double ahi = t - (t - a); - final double alo = a - ahi; - t = SPLIT * b; - final double bhi = t - (t - b); - final double blo = b - bhi; - error = ((ahi*bhi - value) + ahi*blo + alo*bhi) + alo*blo; + error = Math.fma(a, b, -value); // Really needs the `fma(…)` precision here. if (DISABLED) error = 0; } @@ -656,9 +620,9 @@ public final class DoubleDouble extends Number { if (value == 0 && error != 0) { /* * The two values almost cancelled, only their error terms are different. - * The number of significand bits (mantissa) in the IEEE 'double' representation is 52, + * The number of significand bits (mantissa) in the IEEE `double` representation is 52, * not counting the hidden bit. So estimate the accuracy of the double-double number as - * the accuracy of the 'double' value (which is 1 ULP) scaled as if we had 52 additional + * the accuracy of the `double` value (which is 1 ULP) scaled as if we had 52 additional * significand bits (we ignore some more bits if ZERO_THRESHOLD is greater than 0). * If the error is not greater than that value, then assume that it is not significant. */ @@ -1073,19 +1037,19 @@ public final class DoubleDouble extends Number { } final double denominatorValue = value; /* - * The 'b * (a.value / b.value)' part in the method javadoc. + * The `b * (a.value / b.value)` part in the method javadoc. */ final double quotient = numeratorValue / denominatorValue; multiply(quotient); /* - * Compute 'remainder' as 'a - above_product'. + * Compute `remainder` as `a - above_product`. */ final double productError = error; setToSum(numeratorValue, -value); error -= productError; // Complete the above subtraction error += numeratorError; /* - * Adds the 'remainder / b' term, using 'remainder / b.value' as an approximation + * Adds the `remainder / b` term, using `remainder / b.value` as an approximation * (otherwise we would have to invoke this method recursively). The approximation * is assumed okay since the second term is small compared to the first one. */ 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 7c2e038a4c..f4f2c9d68a 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 @@ -74,6 +74,14 @@ public final class Numerics extends Static { boxed = -value; CACHE.put(boxed, boxed); } + /** + * Whether to use {@link Math#fma(double, double, double)} for performance reasons. + * We do not use this flag when the goal is to get better accuracy rather than performance. + * Use of FMA brings performance benefits on machines having hardware support, + * but come at a high cost on older machines without hardware support. + */ + public static final boolean USE_FMA = true; + /** * Maximum number of rows or columns in Apache SIS matrices. We define a maximum because SIS is expected to work * mostly with small matrices, because their sizes are related to the number of dimensions in coordinate systems. 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 90b830745a..f3da6bd283 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 @@ -23,9 +23,16 @@ import org.apache.sis.util.ArgumentChecks; import org.apache.sis.util.resources.Errors; import org.apache.sis.internal.util.DoubleDouble; +import static java.lang.Math.PI; +import static java.lang.Math.min; import static java.lang.Math.abs; import static java.lang.Math.sqrt; +import static java.lang.Math.cbrt; +import static java.lang.Math.fma; +import static java.lang.Math.cos; +import static java.lang.Math.copySign; import static java.lang.Math.multiplyFull; +import static java.lang.Math.multiplyExact; import static java.lang.Float.intBitsToFloat; import static java.lang.Float.floatToIntBits; import static java.lang.Float.floatToRawIntBits; @@ -62,7 +69,7 @@ import static org.apache.sis.internal.util.Numerics.SIGNIFICAND_SIZE; * * @author Martin Desruisseaux (MPO, IRD, Geomatys) * @author Johann Sorel (Geomatys) - * @version 1.2 + * @version 1.4 * * @see DecimalFunctions * @see org.apache.sis.util.Numbers @@ -360,9 +367,9 @@ public final class MathFunctions extends Static { result = base; } while ((exponent >>>= 1) != 0) { - base = Math.multiplyExact(base, base); + base = multiplyExact(base, base); if ((exponent & 1) != 0) { - result = Math.multiplyExact(result, base); + result = multiplyExact(result, base); } } } else if (exponent < 0) { @@ -801,7 +808,7 @@ public final class MathFunctions extends Static { int i = primes.length; int n = Short.toUnsignedInt(primes[i - 1]); // Compute by block of 16 values, for reducing the amount of array resize. - primes = Arrays.copyOf(primes, Math.min((index | 0xF) + 1, PRIMES_LENGTH_16_BITS)); + primes = Arrays.copyOf(primes, min((index | 0xF) + 1, PRIMES_LENGTH_16_BITS)); do { testNextNumber: while (true) { // Simulate a "goto" statement (usually not recommanded...) final int stopAt = (int) sqrt(n += 2); @@ -839,7 +846,7 @@ testNextNumber: while (true) { // Simulate a "goto" statement (usua ArgumentChecks.ensureBetween("number", 2, HIGHEST_SUPPORTED_PRIME_NUMBER, number); final short[] primes = MathFunctions.primes; int lower = 0; - int upper = Math.min(PRIMES_LENGTH_15_BITS, primes.length); + int upper = min(PRIMES_LENGTH_15_BITS, primes.length); if (number > Short.MAX_VALUE) { lower = upper; upper = primes.length; @@ -1038,7 +1045,7 @@ testNextNumber: while (true) { // Simulate a "goto" statement (usua */ case 2: { final double b = coefficients[lower + 1]; - final double q = -0.5 * (b + Math.copySign(sqrt(b*b - 4*a*c), b)); + final double q = -0.5 * (b + copySign(sqrt(b*b - 4*a*c), b)); final double x1 = q/a; final double x2 = c/q; if (Double.isNaN(x1) && Double.isNaN(x2)) break; @@ -1069,9 +1076,9 @@ testNextNumber: while (true) { // Simulate a "goto" statement (usua b = coefficients[lower + 2] / a; a = coefficients[lower + 3] / a; final double a2 = a*a; - final double p = -3./8 * (a2) + b; - final double q = 1./8 * (a2*a) - 1./2 * (a*b) + c; - final double r = -3./256 * (a2*a2) + 1./16 * (a2*b) - 1./4 * (a*c) + d; + final double p = fma(-3./8, a2, b); + final double q = fma( 1./8* a2 - 1./2 *b, a, c); + final double r = fma(-3./256, a2, 1./16*b)*a2 + fma(-1./4*a, c, d); final double[] roots = solveCubic(-2*p, p*p-4*r, q*q, true); if (roots.length != 4) break; for (int i=0; i<3; i++) { @@ -1116,7 +1123,7 @@ testNextNumber: while (true) { // Simulate a "goto" statement (usua final double R = (a*(a*a - 4.5*b) + 13.5*c) / 27; // R from Numerical Recipes 5.6.10. final double Q3 = Q*Q*Q; final double R2 = R*R; - a /= 3; // Last term of Numerical Recipes 5.6.12, 17 and 18. + a /= -3; // Last term of Numerical Recipes 5.6.12, 17 and 18. if (R2 < Q3) { /* * Numerical Recipes 5.6.11 and 5.6.12 uses acos(R/sqrt(Q³)). It is possible to rewrite as @@ -1126,17 +1133,17 @@ testNextNumber: while (true) { // Simulate a "goto" statement (usua b = Math.acos(R/sqrt(Q3)) / 3; // θ from Numerical recipes 5.6.11, then b = θ/3. c = -2 * sqrt(Q); // First part of Numerical Recipes 5.6.12. double[] roots = new double[quartic ? 4 : 3]; - roots[2] = c*Math.cos(b - 2*Math.PI/3) - a; // TODO: try Math.fma with JDK9. - roots[1] = c*Math.cos(b + 2*Math.PI/3) - a; - roots[0] = c*Math.cos(b) - a; + roots[2] = fma(c, cos(b - 2*PI/3), a); + roots[1] = fma(c, cos(b + 2*PI/3), a); + roots[0] = fma(c, cos(b), a); if (!quartic) { roots = removeDuplicated(roots); } return roots; } if (!quartic) { - b = -Math.copySign(Math.cbrt(abs(R) + sqrt(R2 - Q3)), R); // A from Numerical Recipes 5.6.15. - final double x = (b == 0 ? 0 : b + Q/b) - a; + b = -copySign(cbrt(abs(R) + sqrt(R2 - Q3)), R); // A from Numerical Recipes 5.6.15. + final double x = (b == 0 ? 0 : b + Q/b) + a; if (!Double.isNaN(x)) { return new double[] {x}; } diff --git a/core/sis-utility/src/main/java/org/apache/sis/math/package-info.java b/core/sis-utility/src/main/java/org/apache/sis/math/package-info.java index e0aef6d448..4d8485cb46 100644 --- a/core/sis-utility/src/main/java/org/apache/sis/math/package-info.java +++ b/core/sis-utility/src/main/java/org/apache/sis/math/package-info.java @@ -40,7 +40,7 @@ * </ul> * * @author Martin Desruisseaux (Geomatys) - * @version 1.2 + * @version 1.4 * @since 0.3 */ package org.apache.sis.math; diff --git a/core/sis-utility/src/main/java/org/apache/sis/measure/LinearConverter.java b/core/sis-utility/src/main/java/org/apache/sis/measure/LinearConverter.java index 11909ba657..d75cc6884a 100644 --- a/core/sis-utility/src/main/java/org/apache/sis/measure/LinearConverter.java +++ b/core/sis-utility/src/main/java/org/apache/sis/measure/LinearConverter.java @@ -33,16 +33,16 @@ import org.apache.sis.internal.util.Numerics; * Note that the "linear" word in this class does not have the same meaning than the same word * in the {@link #isLinear()} method inherited from JSR-385. * - * <p><b>Implementation note:</b> + * <h2>Implementation note</h2> * for performance reason we should create specialized subtypes for the case where there is only a scale to apply, * or only an offset, <i>etc.</i> But we don't do that in Apache SIS implementation because we will rarely use the * {@code UnitConverter} for converting a lot of values. We rather use {@code MathTransform} for operations on * <var>n</var>-dimensional tuples, and unit conversions are only a special case of those more generic operations. * The {@code sis-referencing} module provided the specialized implementations needed for efficient operations - * and know how to copy the {@code UnitConverter} coefficients into an affine transform matrix.</p> + * and know how to copy the {@code UnitConverter} coefficients into an affine transform matrix. * * @author Martin Desruisseaux (Geomatys) - * @version 1.0 + * @version 1.4 * @since 0.8 */ final class LinearConverter extends AbstractConverter implements LenientComparable { @@ -273,8 +273,7 @@ final class LinearConverter extends AbstractConverter implements LenientComparab */ @Override public double convert(final double value) { - // TODO: use JDK9' Math.fma(…) and verify if it solve the accuracy issue in LinearConverterTest.inverse(). - return (value * scale + offset) / divisor; + return Math.fma(value, scale, offset) / divisor; } /** diff --git a/core/sis-utility/src/test/java/org/apache/sis/measure/LinearConverterTest.java b/core/sis-utility/src/test/java/org/apache/sis/measure/LinearConverterTest.java index b0277ac213..5c8fce4e25 100644 --- a/core/sis-utility/src/test/java/org/apache/sis/measure/LinearConverterTest.java +++ b/core/sis-utility/src/test/java/org/apache/sis/measure/LinearConverterTest.java @@ -176,7 +176,6 @@ public final class LinearConverterTest extends TestCase { c = LinearConverter.offset(27315, 100); inv = (LinearConverter) c.inverse(); assertEquals(12.3, c.convert(inv.convert(12.3)), 1E-13); - // TODO: use JDK9' Math.fma(…) in LinearConverter.convert(double) and verify if it solve the accuracy issue. } /**