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 61aa86927fc6b5e207312c71a490a974f113edcd
Author: Martin Desruisseaux <martin.desruisse...@geomatys.com>
AuthorDate: Sat Jan 29 16:36:14 2022 +0100

    Move `fastHypot` in a location where `RotatedPole` can use.
    This is mostly for documenting why we use that function.
---
 .../apache/sis/internal/referencing/Formulas.java  | 48 +++++++++++++++++++++-
 .../projection/LambertConicConformal.java          |  1 +
 .../projection/ModifiedAzimuthalEquidistant.java   |  2 +-
 .../operation/projection/NormalizedProjection.java | 46 +--------------------
 .../operation/projection/ObliqueMercator.java      |  1 +
 .../operation/projection/ObliqueStereographic.java |  1 +
 .../operation/projection/PolarStereographic.java   |  1 +
 .../operation/projection/package-info.java         |  2 +-
 .../operation/transform/RotatedPole.java           |  3 +-
 .../projection/MapProjectionTestCase.java          |  2 +-
 10 files changed, 57 insertions(+), 50 deletions(-)

diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Formulas.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Formulas.java
index 80c250a..ce7408c 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Formulas.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Formulas.java
@@ -33,7 +33,7 @@ import static 
org.apache.sis.internal.metadata.ReferencingServices.NAUTICAL_MILE
  * do not want to expose publicly those arbitrary values (or at least not in a 
too direct way).
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.2
  * @since   0.4
  * @module
  */
@@ -216,4 +216,50 @@ public final class Formulas extends Static {
          */
         return semiMajorAxis / (semiMajorAxis - semiMinorAxis);
     }
+
+    /**
+     * Returns {@code sqrt(x² + y²)} for coordinate values on an ellipsoid of 
semi-major axis length of 1.
+     * This method does not provides the accuracy guarantees offered by {@link 
Math#hypot(double, double)}.
+     * However for values close to 1, this approximation seems to stay within 
1 ULP of {@code Math.hypot(…)}.
+     * We tested with random values in ranges up to [-6 … +6].
+     *
+     * <p>We define this method because {@link Math#hypot(double, double)} has 
been measured with JMH as 6 times
+     * slower than {@link Math#sqrt(double)} on Java 14.  According posts on 
internet, the same performance cost
+     * is observed in C/C++ too. Despite its cost, {@code hypot(…)} is 
generally recommended because computing a
+     * hypotenuse from large magnitudes has accuracy problems. But in the 
context of {@code NormalizedProjection}
+     * where semi-axis lengths are close to 1, input values should be (x,y) 
coordinates in the [−1 … +1] range.
+     * The actual range may be greater (e.g. [−5 … +5]), but it still far from 
ranges requiring protection against
+     * overflow.</p>
+     *
+     * <h4>Caution</h4>
+     * We may not need the full {@code Math.hypot(x,y)} accuracy in the 
context of map projections on ellipsoids.
+     * However some projection formulas require that {@code fastHypot(x,y) ≥ 
max(|x|,|y|)}, otherwise normalizations
+     * such as {@code x/hypot(x,y)} could result in values larger than 1, 
which in turn result in {@link Double#NaN}
+     * when given to {@link Math#asin(double)}. The assumption on x, y and 
{@code sqrt(x²+y²)} relative magnitude is
+     * broken when x=0 and |y| ≤ 1.4914711209038602E-154 or conversely. This 
method does not check for such cases;
+     * it is caller responsibility to add this check is necessary, for example 
as below:
+     *
+     * {@preformat java
+     *     double D = max(fastHypot(x, y), max(abs(x), abs(y)));
+     * }
+     *
+     * According JMH, above check is 1.65 time slower than {@code fastHypot} 
without checks.
+     * We define this {@code fastHypot(…)} method for tracing where {@code 
sqrt(x² + y²)} is used,
+     * so we can verify if it is used in context where the inaccuracy is 
acceptable.
+     *
+     * <h4>When to use</h4>
+     * We reserve this method to ellipsoidal formulas where approximations are 
used anyway. Implementations using
+     * exact formulas, such as spherical formulas, should use {@link 
Math#hypot(double, double)} for its accuracy.
+     *
+     * @param  x    one side of the triangle. Should be approximately in the 
[-1 … +1] range.
+     * @param  y  other side of the triangle. Should be approximately in the 
[-1 … +1] range.
+     * @return hypotenuse, not smaller than {@code max(|x|,|y|)} unless the 
values are less than 1.5E-154.
+     *
+     * @see <a 
href="https://issues.apache.org/jira/browse/NUMBERS-143";>Investigate Math.hypot 
for computing the absolute of a complex number</a>
+     * @see <a 
href="https://scicomp.stackexchange.com/questions/27758/is-there-any-point-to-using-hypot-for-sqrt1c2-0-le-c-le-1-for-real/27766";>Is
+     *      there any point to using <code>hypot(1, c)</code> for <code>sqrt(1 
+ c²)</code>, 0 ≤ c ≤ 1</a>
+     */
+    public static double fastHypot(final double x, final double y) {
+        return sqrt(x*x + y*y);
+    }
 }
diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/LambertConicConformal.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/LambertConicConformal.java
index 31d7117..037e770 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/LambertConicConformal.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/LambertConicConformal.java
@@ -41,6 +41,7 @@ import org.apache.sis.util.Workaround;
 import static java.lang.Math.*;
 import static java.lang.Double.*;
 import static org.apache.sis.math.MathFunctions.isPositive;
+import static org.apache.sis.internal.referencing.Formulas.fastHypot;
 
 
 /**
diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ModifiedAzimuthalEquidistant.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ModifiedAzimuthalEquidistant.java
index fd62083..121e82c 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ModifiedAzimuthalEquidistant.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ModifiedAzimuthalEquidistant.java
@@ -244,7 +244,7 @@ public class ModifiedAzimuthalEquidistant extends 
AzimuthalEquidistant {
          * exactly (without epsilon) because even a very small value is 
sufficient for avoiding NaN:
          * Since D ≥ max(|x|,|y|) we get x/D and y/D close to zero.
          *
-         * Note: the D ≥ max(|x|,|y|) assumption may not be always true (see 
NormalizedProjection.fastHypot(…)).
+         * Note: the D ≥ max(|x|,|y|) assumption may not be always true (see 
`Formulas.fastHypot(…)`).
          * Consequently sin(α) or cos(α) may be slightly greater than 1. 
However they are multiplied by terms
          * involving eccentricity, which are smaller than 1. An empirical 
verification is done with cos(φ₀) = 1
          * in AzimuthalEquidistantTest.testValuesNearZero().
diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/NormalizedProjection.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/NormalizedProjection.java
index 3e88c0a..43c7066 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/NormalizedProjection.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/NormalizedProjection.java
@@ -127,7 +127,7 @@ import static java.lang.Math.*;
  * @author  André Gosselin (MPO)
  * @author  Rueben Schulz (UBC)
  * @author  Rémi Maréchal (Geomatys)
- * @version 1.1
+ * @version 1.2
  *
  * @see ContextualParameters
  * @see <a href="https://mathworld.wolfram.com/MapProjection.html";>Map 
projections on MathWorld</a>
@@ -687,50 +687,6 @@ public abstract class NormalizedProjection extends 
AbstractMathTransform2D imple
         return θ;
     }
 
-    /**
-     * Returns {@code sqrt(x² + y²)} for coordinate values on an ellipsoid of 
semi-major axis length of 1.
-     * This method does not provides the accuracy guarantees offered by {@link 
Math#hypot(double, double)}.
-     * However for values close to 1, this approximation seems to stay within 
1 ULP of {@code Math.hypot(…)}.
-     * We tested with random values in ranges up to [-6 … +6].
-     *
-     * <p>We define this method because {@link Math#hypot(double, double)} has 
been measured with JMH as 6 times
-     * slower than {@link Math#sqrt(double)} on Java 14.  According posts on 
internet, the same performance cost
-     * is observed in C/C++ too. Despite its cost, {@code hypot(…)} is 
generally recommended because computing a
-     * hypotenuse from large magnitudes has accuracy problems. But in the 
context of {@code NormalizedProjection}
-     * where semi-axis lengths are close to 1, input values should be (x,y) 
coordinates in the [−1 … +1] range.
-     * The actual range may be greater (e.g. [−5 … +5]), but it still far from 
ranges requiring protection against
-     * overflow.</p>
-     *
-     * <h4>Caution</h4>
-     * We may not need the full {@code Math.hypot(x,y)} accuracy in the 
context of map projections on ellipsoids.
-     * However some projection formulas require that {@code fastHypot(x,y) ≥ 
max(|x|,|y|)}, otherwise normalizations
-     * such as {@code x/hypot(x,y)} could result in values larger than 1, 
which in turn result in {@link Double#NaN}
-     * when given to {@link Math#asin(double)}. The assumption on x, y and 
{@code sqrt(x²+y²)} relative magnitude is
-     * broken when x=0 and |y| ≤ 1.4914711209038602E-154 or conversely. This 
method does not a check for such cases;
-     * it is caller responsibility to add this check is necessary, for example 
as below:
-     *
-     * {@preformat java
-     *     double D = max(fastHypot(x, y), max(abs(x), abs(y)));
-     * }
-     *
-     * According JMH, above check is 1.65 time slower than {@code fastHypot} 
without checks.
-     * We define this {@code fastHypot(…)} method for tracing where {@code 
sqrt(x² + y²)} is used,
-     * so we can verify if it is used in context where the inaccuracy is 
acceptable.
-     *
-     * <h4>When to use</h4>
-     * We reserve this method to ellipsoidal formulas where approximations are 
used anyway. Implementations using
-     * exact formulas, such as spherical formulas, should use {@link 
Math#hypot(double, double)} for its accuracy.
-     *
-     * @see <a 
href="https://issues.apache.org/jira/browse/NUMBERS-143";>Investigate Math.hypot 
for computing the absolute of a complex number</a>
-     * @see <a 
href="https://scicomp.stackexchange.com/questions/27758/is-there-any-point-to-using-hypot-for-sqrt1c2-0-le-c-le-1-for-real/27766";>Is
-     *      there any point to using <code>hypot(1, c)</code> for <code>sqrt(1 
+ c²)</code>, 0 ≤ c ≤ 1</a>
-     */
-    static double fastHypot(final double x, final double y) {
-        return sqrt(x*x + y*y);
-        // TODO: consider using Math.fma on JDK9.
-        // Check also if we should do the same with plain x*x + y*y in 
subclasses.
-    }
-
     /*
      * TODO: consider adding a sqrt1ms(x) method for sqrt(1 - x*x), which 
could be implemented as sqrt(fma(x, -x, 1)).
      * The use of Math.fma(…) in this context would be valuable especially 
when x is close to 1 (to be verified).
diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ObliqueMercator.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ObliqueMercator.java
index 8080965..c38110c 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ObliqueMercator.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ObliqueMercator.java
@@ -33,6 +33,7 @@ import org.apache.sis.util.Workaround;
 
 import static java.lang.Math.*;
 import static org.apache.sis.math.MathFunctions.atanh;
+import static org.apache.sis.internal.referencing.Formulas.fastHypot;
 import static 
org.apache.sis.internal.referencing.provider.ObliqueMercatorCenter.*;
 
 
diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ObliqueStereographic.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ObliqueStereographic.java
index 4839bb5..8a96322 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ObliqueStereographic.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ObliqueStereographic.java
@@ -33,6 +33,7 @@ import org.apache.sis.internal.referencing.Resources;
 import org.apache.sis.util.Workaround;
 
 import static java.lang.Math.*;
+import static org.apache.sis.internal.referencing.Formulas.fastHypot;
 import static 
org.apache.sis.internal.referencing.provider.ObliqueStereographic.*;
 
 
diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/PolarStereographic.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/PolarStereographic.java
index 0cd2180..ff765a1 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/PolarStereographic.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/PolarStereographic.java
@@ -38,6 +38,7 @@ import org.apache.sis.measure.Latitude;
 import org.apache.sis.math.MathFunctions;
 
 import static java.lang.Math.*;
+import static org.apache.sis.internal.referencing.Formulas.fastHypot;
 
 
 /**
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 255d2c5..8a0001d 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
@@ -159,7 +159,7 @@
  * @author  Rémi Maréchal (Geomatys)
  * @author  Adrian Custer (Geomatys)
  * @author  Matthieu Bastianelli (Geomatys)
- * @version 1.1
+ * @version 1.2
  *
  * @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/RotatedPole.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/RotatedPole.java
index 513d4cd..cd5a385 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/RotatedPole.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/RotatedPole.java
@@ -43,6 +43,7 @@ import org.apache.sis.util.ComparisonMode;
 import org.apache.sis.util.Debug;
 
 import static java.lang.Math.*;
+import static org.apache.sis.internal.referencing.Formulas.fastHypot;
 
 
 /**
@@ -311,7 +312,7 @@ public class RotatedPole extends AbstractMathTransform2D 
implements Serializable
          */
         double xr =  cosφp * z - sinφp * x;
         double zr = -cosφp * x - sinφp * z;
-        double R  = sqrt(xr*xr + y*y);          // The slower hypot(…) is not 
needed because values are close to 1.
+        double R  = fastHypot(xr, y);           // The slower hypot(…) is not 
needed because values are close to 1.
         dstPts[dstOff]   = atan2(y, xr);
         dstPts[dstOff+1] = atan2(zr, R);
         if (!derivate) {
diff --git 
a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/MapProjectionTestCase.java
 
b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/MapProjectionTestCase.java
index 90f73f9..f45342d 100644
--- 
a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/MapProjectionTestCase.java
+++ 
b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/MapProjectionTestCase.java
@@ -238,7 +238,7 @@ abstract strictfp class MapProjectionTestCase extends 
MathTransformTestCase {
     /**
      * Tests coordinates close to zero. Callers must set the transform and 
tolerance threshold before to invoke
      * this method. This method tests (among others) the 
1.4914711209038602E-154 value, which is the threshold
-     * documented in {@link NormalizedProjection#fastHypot}.
+     * documented in {@link 
org.apache.sis.internal.referencing.Formulas#fastHypot(double, double)}.
      *
      * @throws TransformException if an error occurred while projecting the 
coordinate.
      */

Reply via email to