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 &amp; 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 &amp; 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 &amp; al.] page 4 algorithm 6, itself reproduced from 
[Shewchuk] page 326.</p>
+     * <p>Source: [Hida &amp; 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 &amp; 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.
     }
 
     /**

Reply via email to