osdir.com


[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[GitHub] asfgit closed pull request #9: Geometry 8


asfgit closed pull request #9: Geometry 8
URL: https://github.com/apache/commons-geometry/pull/9
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/Vector.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/Vector.java
index d4eb296..b04d644 100644
--- a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/Vector.java
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/Vector.java
@@ -16,6 +16,8 @@
  */
 package org.apache.commons.geometry.core;
 
+import org.apache.commons.geometry.core.exception.IllegalNormException;
+
 /** Interface representing a vector in a vector space. The most common
  * use of this interface is to represent displacement vectors in an affine
  * space.
@@ -120,9 +122,9 @@
     /** Get a normalized vector aligned with the instance. The returned
      * vector has a magnitude of 1.
      * @return a new normalized vector
-     * @exception IllegalStateException if the norm is zero
+     * @exception IllegalNormException if the norm is zero, NaN, or infinite
      */
-    V normalize() throws IllegalStateException;
+    V normalize() throws IllegalNormException;
 
     /** Multiply the instance by a scalar.
      * @param a scalar
@@ -180,10 +182,10 @@
      * </code>
      * @param base base vector
      * @return the vector projection of the instance onto {@code base}
-     * @exception IllegalStateException if the norm of the base vector is zero
+     * @exception IllegalNormException if the norm of the base vector is zero, NaN, or infinite
      * @see #reject(Vector)
      */
-    V project(V base) throws IllegalStateException;
+    V project(V base) throws IllegalNormException;
 
     /** Get the rejection of the instance from the given base vector. The returned
      * vector is orthogonal to {@code base}. This operation can be interpreted as
@@ -195,15 +197,15 @@
      * </code>
      * @param base base vector
      * @return the vector rejection of the instance from {@code base}
-     * @exception IllegalStateException if the norm of the base vector is zero
+     * @exception IllegalNormException if the norm of the base vector is zero, NaN, or infinite
      * @see #project(Vector)
      */
-    V reject(V base) throws IllegalStateException;
+    V reject(V base) throws IllegalNormException;
 
     /** Compute the angular separation between two vectors in radians.
      * @param v other vector
      * @return angular separation between this instance and v in radians
-     * @exception IllegalStateException if either vector has a zero norm
+     * @exception IllegalNormException if either vector has a zero, NaN, or infinite norm
      */
-    double angle(V v) throws IllegalStateException;
+    double angle(V v) throws IllegalNormException;
 }
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/exception/GeometryException.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/exception/GeometryException.java
new file mode 100644
index 0000000..57462be
--- /dev/null
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/exception/GeometryException.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.geometry.core.exception;
+
+/** Base class for geometry exceptions. The semantics of this class
+ * are intended to be similar to {@link java.lang.ArithmeticException}
+ * but for geometric operations.
+ */
+public class GeometryException extends RuntimeException {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = 20180909L;
+
+    /** Simple constructor with error message.
+     * @param msg exception message string
+     */
+    public GeometryException(String msg) {
+        super(msg);
+    }
+}
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/exception/IllegalNormException.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/exception/IllegalNormException.java
new file mode 100644
index 0000000..26fc11c
--- /dev/null
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/exception/IllegalNormException.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.geometry.core.exception;
+
+/** Exception thrown when an illegal vector norm value is encountered
+ * in an operation.
+ */
+public class IllegalNormException extends GeometryException {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = 20180909L;
+
+    /** Simple constructor accepting the illegal norm value.
+     * @param norm the illegal norm value
+     */
+    public IllegalNormException(double norm) {
+        super("Illegal norm: " + norm);
+    }
+
+    /** Constructor accepting an error message.
+     * @param msg the error message
+     */
+    public IllegalNormException(String msg) {
+        super(msg);
+    }
+}
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/exception/package-info.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/exception/package-info.java
new file mode 100644
index 0000000..2b7d21b
--- /dev/null
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/exception/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ *
+ * <p>
+ * This package contains specialized exception types used by this library.
+ * </p>
+ */
+package org.apache.commons.geometry.core.exception;
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/internal/GeometryInternalError.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/internal/GeometryInternalError.java
new file mode 100644
index 0000000..219ccdb
--- /dev/null
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/internal/GeometryInternalError.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.geometry.core.internal;
+
+/** Exception thrown when something that should not happen does happen.
+ * This class is not intended to be part of the public API and should
+ * never be seen by the user when algorithms are functioning correctly.
+ */
+public class GeometryInternalError extends IllegalStateException {
+
+    /** Error message used for exceptions of this type. */
+    private static final String ERROR_MSG = "An internal geometry error occurred. This most often indicates an " +
+            "error in the algorithm implementation than in the calling code or data. Please file a bug report " +
+            "with the developers.";
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = 20180913L;
+
+    /** Simple constructor with a default error message.
+     */
+    public GeometryInternalError() {
+        super(ERROR_MSG);
+    }
+}
diff --git a/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/GeometryTestUtils.java b/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/GeometryTestUtils.java
index 3dbf58a..60bdeea 100644
--- a/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/GeometryTestUtils.java
+++ b/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/GeometryTestUtils.java
@@ -45,6 +45,38 @@ public static void assertNegativeInfinity(double value) {
         Assert.assertTrue(msg, value < 0);
     }
 
+    /** Asserts that the given Runnable throws an exception of the given type.
+     * @param r the Runnable instance
+     * @param exceptionType the expected exception type
+     */
+    public static void assertThrows(Runnable r, Class<?> exceptionType) {
+        assertThrows(r, exceptionType, null);
+    }
+
+    /** Asserts that the given Runnable throws an exception of the given type. If
+     * {@code message} is not null, the exception message is asserted to equal the
+     * given value.
+     * @param r the Runnable instance
+     * @param exceptionType the expected exception type
+     * @param message the expected exception message; ignored if null
+     */
+    public static void assertThrows(Runnable r, Class<?> exceptionType, String message) {
+        try {
+            r.run();
+            Assert.fail("Operation should have thrown an exception");
+        }
+        catch (Exception exc) {
+            Class<?> actualType = exc.getClass();
+
+            Assert.assertTrue("Expected exception of type " + exceptionType.getName() + " but was " + actualType.getName(),
+                    exceptionType.isAssignableFrom(actualType));
+
+            if (message != null) {
+                Assert.assertEquals(message, exc.getMessage());
+            }
+        }
+    }
+
     /**
      * Serializes and then recovers an object from a byte array. Returns the deserialized object.
      *
diff --git a/commons-geometry-enclosing/src/main/java/org/apache/commons/geometry/enclosing/WelzlEncloser.java b/commons-geometry-enclosing/src/main/java/org/apache/commons/geometry/enclosing/WelzlEncloser.java
index 4ec5ad5..65dbc2a 100644
--- a/commons-geometry-enclosing/src/main/java/org/apache/commons/geometry/enclosing/WelzlEncloser.java
+++ b/commons-geometry-enclosing/src/main/java/org/apache/commons/geometry/enclosing/WelzlEncloser.java
@@ -20,6 +20,7 @@
 import java.util.List;
 
 import org.apache.commons.geometry.core.Point;
+import org.apache.commons.geometry.core.internal.GeometryInternalError;
 
 /** Class implementing Emo Welzl algorithm to find the smallest enclosing ball in linear time.
  * <p>
@@ -98,7 +99,7 @@ public WelzlEncloser(final double tolerance, final SupportBallGenerator<P> gener
             ball = moveToFrontBall(extreme, extreme.size(), support);
             if (ball.getRadius() < savedBall.getRadius()) {
                 // this should never happen
-                throw new IllegalStateException("Please file a bug report");
+                throw new GeometryInternalError();
             }
 
             // it was an interesting point, move it to the front
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/internal/Vectors.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/internal/Vectors.java
similarity index 87%
rename from commons-geometry-core/src/main/java/org/apache/commons/geometry/core/internal/Vectors.java
rename to commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/internal/Vectors.java
index 673d53b..31ec162 100644
--- a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/internal/Vectors.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/internal/Vectors.java
@@ -14,7 +14,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.commons.geometry.core.internal;
+package org.apache.commons.geometry.euclidean.internal;
+
+import org.apache.commons.geometry.core.exception.IllegalNormException;
 
 /** This class consists exclusively of static vector utility methods.
  */
@@ -23,6 +25,33 @@
     /** Private constructor. */
     private Vectors() {}
 
+    /** Returns true if the given value is finite (ie, not NaN or inifinite)
+     * and not equal to zero.
+     * @param value the value to test
+     * @return true if {@code value} is not NaN, infinite, or zero; otherwise
+     *      false
+     */
+    public static boolean isFiniteNonZero(final double value) {
+        return Double.isFinite(value) && value != 0.0;
+    }
+
+
+    /** Throws an {@link IllegalNormException} if the  given norm value
+     * is not finite (ie, NaN or infinite) or zero. The argument is returned
+     * to allow this method to be called inline.
+     * @param norm vector norm value
+     * @return the validated norm value
+     * @throws IllegalNormException if the given norm value is NaN, infinite,
+     *  or zero
+     */
+    public static double ensureFiniteNonZeroNorm(final double norm) throws IllegalNormException {
+        if (!isFiniteNonZero(norm)) {
+            throw new IllegalNormException(norm);
+        }
+
+        return norm;
+    }
+
     /** Get the L<sub>1</sub> norm for the vector with the given components.
      * This is defined as the sum of the absolute values of all vector components.
      * @param x vector component
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/Vector1D.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/Vector1D.java
index 10fabed..79a4d35 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/Vector1D.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/Vector1D.java
@@ -18,9 +18,8 @@
 
 import org.apache.commons.geometry.core.Geometry;
 import org.apache.commons.geometry.core.internal.SimpleTupleFormat;
-import org.apache.commons.geometry.core.internal.Vectors;
 import org.apache.commons.geometry.euclidean.EuclideanVector;
-import org.apache.commons.geometry.euclidean.internal.ZeroNormException;
+import org.apache.commons.geometry.euclidean.internal.Vectors;
 import org.apache.commons.numbers.arrays.LinearCombination;
 
 /** This class represents a vector in one-dimensional Euclidean space.
@@ -117,14 +116,9 @@ public double getMagnitudeSq() {
     /** {@inheritDoc} */
     @Override
     public Vector1D withMagnitude(double magnitude) {
-        final double x = getX();
-        if (x > 0.0) {
-            return new Vector1D(magnitude);
-        }
-        else if (x < 0.0) {
-            return new Vector1D(-magnitude);
-        }
-        throw new ZeroNormException();
+        Vectors.ensureFiniteNonZeroNorm(getNorm());
+
+        return (getX() > 0.0)? new Vector1D(magnitude) : new Vector1D(-magnitude);
     }
 
     /** {@inheritDoc} */
@@ -160,14 +154,9 @@ public Vector1D negate() {
     /** {@inheritDoc} */
     @Override
     public Vector1D normalize() {
-        final double x = getX();
-        if (x > 0.0) {
-            return ONE;
-        }
-        else if (x < 0.0) {
-            return MINUS_ONE;
-        }
-        throw new ZeroNormException();
+        Vectors.ensureFiniteNonZeroNorm(getNorm());
+
+        return (getX() > 0.0) ? ONE : MINUS_ONE;
     }
 
     /** {@inheritDoc} */
@@ -211,9 +200,8 @@ public double dotProduct(Vector1D v) {
      */
     @Override
     public Vector1D project(final Vector1D base) {
-        if (base.getX() == 0) {
-            throw new ZeroNormException(ZeroNormException.INVALID_BASE);
-        }
+        Vectors.ensureFiniteNonZeroNorm(base.getNorm());
+
         return this;
     }
 
@@ -222,9 +210,8 @@ public Vector1D project(final Vector1D base) {
      */
     @Override
     public Vector1D reject(final Vector1D base) {
-        if (base.getX() == 0) {
-            throw new ZeroNormException(ZeroNormException.INVALID_BASE);
-        }
+        Vectors.ensureFiniteNonZeroNorm(base.getNorm());
+
         return Vector1D.ZERO;
     }
 
@@ -234,12 +221,12 @@ public Vector1D reject(final Vector1D base) {
      */
     @Override
     public double angle(final Vector1D v) {
+        Vectors.ensureFiniteNonZeroNorm(getNorm());
+        Vectors.ensureFiniteNonZeroNorm(v.getNorm());
+
         final double sig1 = Math.signum(getX());
         final double sig2 = Math.signum(v.getX());
 
-        if (sig1 == 0 || sig2 == 0) {
-            throw new ZeroNormException();
-        }
         // the angle is 0 if the x value signs are the same and pi if not
         return (sig1 == sig2) ? 0.0 : Geometry.PI;
     }
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Plane.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Plane.java
index bc3c040..424fbb3 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Plane.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Plane.java
@@ -18,6 +18,7 @@
 
 import org.apache.commons.geometry.core.partitioning.Embedding;
 import org.apache.commons.geometry.core.partitioning.Hyperplane;
+import org.apache.commons.geometry.euclidean.internal.Vectors;
 import org.apache.commons.geometry.euclidean.oned.Point1D;
 import org.apache.commons.geometry.euclidean.twod.Point2D;
 import org.apache.commons.geometry.euclidean.twod.PolygonsSet;
@@ -141,10 +142,8 @@ public void reset(final Plane original) {
      * @exception IllegalArgumentException if the normal norm is too close to zero
      */
     private void setNormal(final Vector3D normal) {
-        final double norm = normal.getNorm();
-        if (norm < 1.0e-10) {
-            throw new IllegalArgumentException("Norm is zero");
-        }
+        final double norm = Vectors.ensureFiniteNonZeroNorm(normal.getNorm());
+
         w = Vector3D.linearCombination(1.0 / norm, normal);
     }
 
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Point3D.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Point3D.java
index 3958326..51f725e 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Point3D.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Point3D.java
@@ -18,8 +18,8 @@
 package org.apache.commons.geometry.euclidean.threed;
 
 import org.apache.commons.geometry.core.internal.SimpleTupleFormat;
-import org.apache.commons.geometry.core.internal.Vectors;
 import org.apache.commons.geometry.euclidean.EuclideanPoint;
+import org.apache.commons.geometry.euclidean.internal.Vectors;
 import org.apache.commons.numbers.arrays.LinearCombination;
 
 /** This class represents a point in three-dimensional Euclidean space.
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Rotation.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Rotation.java
index f696f37..466aedc 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Rotation.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Rotation.java
@@ -19,6 +19,9 @@
 
 import java.io.Serializable;
 
+import org.apache.commons.geometry.core.exception.GeometryException;
+import org.apache.commons.geometry.core.exception.IllegalNormException;
+import org.apache.commons.geometry.euclidean.internal.Vectors;
 import org.apache.commons.numbers.arrays.LinearCombination;
 
 /**
@@ -96,12 +99,6 @@
   /** Serializable version identifier */
   private static final long serialVersionUID = 20180903L;
 
-  /** Error message for Cardan angle singularities */
-  private static final String CARDAN_SINGULARITY_MSG = "Cardan angles singularity";
-
-  /** Error message for Euler angle singularities */
-  private static final String EULER_SINGULARITY_MSG = "Euler angles singularity";
-
   /** Scalar coordinate of the quaternion. */
   private final double q0;
 
@@ -171,14 +168,14 @@ public Rotation(Vector3D axis, double angle) {
    * @param axis axis around which to rotate
    * @param angle rotation angle
    * @param convention convention to use for the semantics of the angle
-   * @exception IllegalArgumentException if the axis norm is zero
+   * @exception IllegalNormException if the axis norm is zero, NaN, or infinite
    */
   public Rotation(final Vector3D axis, final double angle, final RotationConvention convention)
-      throws IllegalArgumentException {
+      throws IllegalNormException {
 
     double norm = axis.getNorm();
-    if (norm == 0) {
-      throw new IllegalArgumentException("Zero norm for rotation axis");
+    if (!Vectors.isFiniteNonZero(norm)) {
+      throw new IllegalNormException("Illegal norm for rotation axis: " + norm);
     }
 
     double halfAngle = convention == RotationConvention.VECTOR_OPERATOR ? -0.5 * angle : +0.5 * angle;
@@ -264,54 +261,48 @@ public Rotation(double[][] m, double threshold)
    * @param u2 second vector of the origin pair
    * @param v1 desired image of u1 by the rotation
    * @param v2 desired image of u2 by the rotation
-   * @exception IllegalArgumentException if the norm of one of the vectors is zero,
-   * or if one of the pair is degenerated (i.e. the vectors of the pair are collinear)
+   * @exception IllegalNormException if the norm of one of the vectors is zero, NaN, infinite,
+   *    or if one of the pair is degenerated (i.e. the vectors of the pair are collinear)
    */
   public Rotation(Vector3D u1, Vector3D u2, Vector3D v1, Vector3D v2)
-      throws IllegalArgumentException {
-
-      try {
-          // build orthonormalized base from u1, u2
-          // this fails when vectors are null or collinear, which is forbidden to define a rotation
-          final Vector3D u3 = u1.crossProduct(u2).normalize();
-          u2 = u3.crossProduct(u1).normalize();
-          u1 = u1.normalize();
-
-          // build an orthonormalized base from v1, v2
-          // this fails when vectors are null or collinear, which is forbidden to define a rotation
-          final Vector3D v3 = v1.crossProduct(v2).normalize();
-          v2 = v3.crossProduct(v1).normalize();
-          v1 = v1.normalize();
-
-          // buid a matrix transforming the first base into the second one
-          final double[][] m = new double[][] {
-              {
-                  LinearCombination.value(u1.getX(), v1.getX(), u2.getX(), v2.getX(), u3.getX(), v3.getX()),
-                  LinearCombination.value(u1.getY(), v1.getX(), u2.getY(), v2.getX(), u3.getY(), v3.getX()),
-                  LinearCombination.value(u1.getZ(), v1.getX(), u2.getZ(), v2.getX(), u3.getZ(), v3.getX())
-              },
-              {
-                  LinearCombination.value(u1.getX(), v1.getY(), u2.getX(), v2.getY(), u3.getX(), v3.getY()),
-                  LinearCombination.value(u1.getY(), v1.getY(), u2.getY(), v2.getY(), u3.getY(), v3.getY()),
-                  LinearCombination.value(u1.getZ(), v1.getY(), u2.getZ(), v2.getY(), u3.getZ(), v3.getY())
-              },
-              {
-                  LinearCombination.value(u1.getX(), v1.getZ(), u2.getX(), v2.getZ(), u3.getX(), v3.getZ()),
-                  LinearCombination.value(u1.getY(), v1.getZ(), u2.getY(), v2.getZ(), u3.getY(), v3.getZ()),
-                  LinearCombination.value(u1.getZ(), v1.getZ(), u2.getZ(), v2.getZ(), u3.getZ(), v3.getZ())
-              }
-          };
-
-          double[] quat = mat2quat(m);
-          q0 = quat[0];
-          q1 = quat[1];
-          q2 = quat[2];
-          q3 = quat[3];
-
-      } catch (IllegalStateException exc) {
-          throw new IllegalArgumentException("Invalid rotation vector pairs", exc);
-      }
+      throws IllegalNormException {
+
+      // build orthonormalized base from u1, u2
+      // this fails when vectors are null or collinear, which is forbidden to define a rotation
+      final Vector3D u3 = u1.crossProduct(u2).normalize();
+      u2 = u3.crossProduct(u1).normalize();
+      u1 = u1.normalize();
+
+      // build an orthonormalized base from v1, v2
+      // this fails when vectors are null or collinear, which is forbidden to define a rotation
+      final Vector3D v3 = v1.crossProduct(v2).normalize();
+      v2 = v3.crossProduct(v1).normalize();
+      v1 = v1.normalize();
+
+      // buid a matrix transforming the first base into the second one
+      final double[][] m = new double[][] {
+          {
+              LinearCombination.value(u1.getX(), v1.getX(), u2.getX(), v2.getX(), u3.getX(), v3.getX()),
+              LinearCombination.value(u1.getY(), v1.getX(), u2.getY(), v2.getX(), u3.getY(), v3.getX()),
+              LinearCombination.value(u1.getZ(), v1.getX(), u2.getZ(), v2.getX(), u3.getZ(), v3.getX())
+          },
+          {
+              LinearCombination.value(u1.getX(), v1.getY(), u2.getX(), v2.getY(), u3.getX(), v3.getY()),
+              LinearCombination.value(u1.getY(), v1.getY(), u2.getY(), v2.getY(), u3.getY(), v3.getY()),
+              LinearCombination.value(u1.getZ(), v1.getY(), u2.getZ(), v2.getY(), u3.getZ(), v3.getY())
+          },
+          {
+              LinearCombination.value(u1.getX(), v1.getZ(), u2.getX(), v2.getZ(), u3.getX(), v3.getZ()),
+              LinearCombination.value(u1.getY(), v1.getZ(), u2.getY(), v2.getZ(), u3.getY(), v3.getZ()),
+              LinearCombination.value(u1.getZ(), v1.getZ(), u2.getZ(), v2.getZ(), u3.getZ(), v3.getZ())
+          }
+      };
 
+      double[] quat = mat2quat(m);
+      q0 = quat[0];
+      q1 = quat[1];
+      q2 = quat[2];
+      q3 = quat[3];
   }
 
   /** Build one of the rotations that transform one vector into another one.
@@ -325,13 +316,13 @@ public Rotation(Vector3D u1, Vector3D u2, Vector3D v1, Vector3D v2)
 
    * @param u origin vector
    * @param v desired image of u by the rotation
-   * @exception IllegalArgumentException if the norm of one of the vectors is zero
+   * @exception IllegalNormException if the norm of one of the vectors is zero, NaN, or infinite
    */
   public Rotation(Vector3D u, Vector3D v) {
 
     double normProduct = u.getNorm() * v.getNorm();
-    if (normProduct == 0) {
-        throw new IllegalArgumentException("Zero norm for rotation defining vector");
+    if (!Vectors.isFiniteNonZero(normProduct)) {
+        throw new IllegalNormException("Illegal norm for rotation defining vector: " + normProduct);
     }
 
     double dot = u.dotProduct(v);
@@ -573,13 +564,13 @@ public double getAngle() {
 
    * @param order rotation order to use
    * @return an array of three angles, in the order specified by the set
-   * @exception IllegalStateException if the rotation is
+   * @exception AngleSetSingularityException if the rotation is
    * singular with respect to the angles set specified
    * @deprecated as of 3.6, replaced with {@link #getAngles(RotationOrder, RotationConvention)}
    */
   @Deprecated
   public double[] getAngles(RotationOrder order)
-      throws IllegalStateException {
+      throws AngleSetSingularityException {
       return getAngles(order, RotationConvention.VECTOR_OPERATOR);
   }
 
@@ -616,11 +607,11 @@ public double getAngle() {
    * @param order rotation order to use
    * @param convention convention to use for the semantics of the angle
    * @return an array of three angles, in the order specified by the set
-   * @exception IllegalStateException if the rotation is
-   * singular with respect to the angles set specified
+   * @exception AngleSetSingularityException if the rotation is
+   * singular with respect to the angle set specified
    */
   public double[] getAngles(RotationOrder order, RotationConvention convention)
-      throws IllegalStateException {
+      throws AngleSetSingularityException {
 
       if (convention == RotationConvention.VECTOR_OPERATOR) {
           if (order == RotationOrder.XYZ) {
@@ -633,7 +624,7 @@ public double getAngle() {
               Vector3D v1 = applyTo(Vector3D.PLUS_Z);
               Vector3D v2 = applyInverseTo(Vector3D.PLUS_X);
               if  ((v2.getZ() < -0.9999999999) || (v2.getZ() > 0.9999999999)) {
-                  throw new IllegalStateException(CARDAN_SINGULARITY_MSG);
+                  throw new CardanSingularityException();
               }
               return new double[] {
                   Math.atan2(-(v1.getY()), v1.getZ()),
@@ -651,7 +642,7 @@ public double getAngle() {
               Vector3D v1 = applyTo(Vector3D.PLUS_Y);
               Vector3D v2 = applyInverseTo(Vector3D.PLUS_X);
               if ((v2.getY() < -0.9999999999) || (v2.getY() > 0.9999999999)) {
-                  throw new IllegalStateException(CARDAN_SINGULARITY_MSG);
+                  throw new CardanSingularityException();
               }
               return new double[] {
                   Math.atan2(v1.getZ(), v1.getY()),
@@ -669,7 +660,7 @@ public double getAngle() {
               Vector3D v1 = applyTo(Vector3D.PLUS_Z);
               Vector3D v2 = applyInverseTo(Vector3D.PLUS_Y);
               if ((v2.getZ() < -0.9999999999) || (v2.getZ() > 0.9999999999)) {
-                  throw new IllegalStateException(CARDAN_SINGULARITY_MSG);
+                  throw new CardanSingularityException();
               }
               return new double[] {
                   Math.atan2(v1.getX(), v1.getZ()),
@@ -687,7 +678,7 @@ public double getAngle() {
               Vector3D v1 = applyTo(Vector3D.PLUS_X);
               Vector3D v2 = applyInverseTo(Vector3D.PLUS_Y);
               if ((v2.getX() < -0.9999999999) || (v2.getX() > 0.9999999999)) {
-                  throw new IllegalStateException(CARDAN_SINGULARITY_MSG);
+                  throw new CardanSingularityException();
               }
               return new double[] {
                   Math.atan2(-(v1.getZ()), v1.getX()),
@@ -705,7 +696,7 @@ public double getAngle() {
               Vector3D v1 = applyTo(Vector3D.PLUS_Y);
               Vector3D v2 = applyInverseTo(Vector3D.PLUS_Z);
               if ((v2.getY() < -0.9999999999) || (v2.getY() > 0.9999999999)) {
-                  throw new IllegalStateException(CARDAN_SINGULARITY_MSG);
+                  throw new CardanSingularityException();
               }
               return new double[] {
                   Math.atan2(-(v1.getX()), v1.getY()),
@@ -723,7 +714,7 @@ public double getAngle() {
               Vector3D v1 = applyTo(Vector3D.PLUS_X);
               Vector3D v2 = applyInverseTo(Vector3D.PLUS_Z);
               if ((v2.getX() < -0.9999999999) || (v2.getX() > 0.9999999999)) {
-                  throw new IllegalStateException(CARDAN_SINGULARITY_MSG);
+                  throw new CardanSingularityException();
               }
               return new double[] {
                   Math.atan2(v1.getY(), v1.getX()),
@@ -741,7 +732,7 @@ public double getAngle() {
               Vector3D v1 = applyTo(Vector3D.PLUS_X);
               Vector3D v2 = applyInverseTo(Vector3D.PLUS_X);
               if ((v2.getX() < -0.9999999999) || (v2.getX() > 0.9999999999)) {
-                  throw new IllegalStateException(EULER_SINGULARITY_MSG);
+                  throw new EulerSingularityException();
               }
               return new double[] {
                   Math.atan2(v1.getY(), -v1.getZ()),
@@ -759,7 +750,7 @@ public double getAngle() {
               Vector3D v1 = applyTo(Vector3D.PLUS_X);
               Vector3D v2 = applyInverseTo(Vector3D.PLUS_X);
               if ((v2.getX() < -0.9999999999) || (v2.getX() > 0.9999999999)) {
-                  throw new IllegalStateException(EULER_SINGULARITY_MSG);
+                  throw new EulerSingularityException();
               }
               return new double[] {
                   Math.atan2(v1.getZ(), v1.getY()),
@@ -777,7 +768,7 @@ public double getAngle() {
               Vector3D v1 = applyTo(Vector3D.PLUS_Y);
               Vector3D v2 = applyInverseTo(Vector3D.PLUS_Y);
               if ((v2.getY() < -0.9999999999) || (v2.getY() > 0.9999999999)) {
-                  throw new IllegalStateException(EULER_SINGULARITY_MSG);
+                  throw new EulerSingularityException();
               }
               return new double[] {
                   Math.atan2(v1.getX(), v1.getZ()),
@@ -795,7 +786,7 @@ public double getAngle() {
               Vector3D v1 = applyTo(Vector3D.PLUS_Y);
               Vector3D v2 = applyInverseTo(Vector3D.PLUS_Y);
               if ((v2.getY() < -0.9999999999) || (v2.getY() > 0.9999999999)) {
-                  throw new IllegalStateException(EULER_SINGULARITY_MSG);
+                  throw new EulerSingularityException();
               }
               return new double[] {
                   Math.atan2(v1.getZ(), -v1.getX()),
@@ -813,7 +804,7 @@ public double getAngle() {
               Vector3D v1 = applyTo(Vector3D.PLUS_Z);
               Vector3D v2 = applyInverseTo(Vector3D.PLUS_Z);
               if ((v2.getZ() < -0.9999999999) || (v2.getZ() > 0.9999999999)) {
-                  throw new IllegalStateException(EULER_SINGULARITY_MSG);
+                  throw new EulerSingularityException();
               }
               return new double[] {
                   Math.atan2(v1.getX(), -v1.getY()),
@@ -831,7 +822,7 @@ public double getAngle() {
               Vector3D v1 = applyTo(Vector3D.PLUS_Z);
               Vector3D v2 = applyInverseTo(Vector3D.PLUS_Z);
               if ((v2.getZ() < -0.9999999999) || (v2.getZ() > 0.9999999999)) {
-                  throw new IllegalStateException(EULER_SINGULARITY_MSG);
+                  throw new EulerSingularityException();
               }
               return new double[] {
                   Math.atan2(v1.getY(), v1.getX()),
@@ -851,7 +842,7 @@ public double getAngle() {
               Vector3D v1 = applyTo(Vector3D.PLUS_X);
               Vector3D v2 = applyInverseTo(Vector3D.PLUS_Z);
               if ((v2.getX() < -0.9999999999) || (v2.getX() > 0.9999999999)) {
-                  throw new IllegalStateException(CARDAN_SINGULARITY_MSG);
+                  throw new CardanSingularityException();
               }
               return new double[] {
                   Math.atan2(-v2.getY(), v2.getZ()),
@@ -869,7 +860,7 @@ public double getAngle() {
               Vector3D v1 = applyTo(Vector3D.PLUS_X);
               Vector3D v2 = applyInverseTo(Vector3D.PLUS_Y);
               if ((v2.getX() < -0.9999999999) || (v2.getX() > 0.9999999999)) {
-                  throw new IllegalStateException(CARDAN_SINGULARITY_MSG);
+                  throw new CardanSingularityException();
               }
               return new double[] {
                   Math.atan2(v2.getZ(), v2.getY()),
@@ -887,7 +878,7 @@ public double getAngle() {
               Vector3D v1 = applyTo(Vector3D.PLUS_Y);
               Vector3D v2 = applyInverseTo(Vector3D.PLUS_Z);
               if ((v2.getY() < -0.9999999999) || (v2.getY() > 0.9999999999)) {
-                  throw new IllegalStateException(CARDAN_SINGULARITY_MSG);
+                  throw new CardanSingularityException();
               }
               return new double[] {
                   Math.atan2(v2.getX(), v2.getZ()),
@@ -905,7 +896,7 @@ public double getAngle() {
               Vector3D v1 = applyTo(Vector3D.PLUS_Y);
               Vector3D v2 = applyInverseTo(Vector3D.PLUS_X);
               if ((v2.getY() < -0.9999999999) || (v2.getY() > 0.9999999999)) {
-                  throw new IllegalStateException(CARDAN_SINGULARITY_MSG);
+                  throw new CardanSingularityException();
               }
               return new double[] {
                   Math.atan2(-v2.getZ(), v2.getX()),
@@ -923,7 +914,7 @@ public double getAngle() {
               Vector3D v1 = applyTo(Vector3D.PLUS_Z);
               Vector3D v2 = applyInverseTo(Vector3D.PLUS_Y);
               if ((v2.getZ() < -0.9999999999) || (v2.getZ() > 0.9999999999)) {
-                  throw new IllegalStateException(CARDAN_SINGULARITY_MSG);
+                  throw new CardanSingularityException();
               }
               return new double[] {
                   Math.atan2(-v2.getX(), v2.getY()),
@@ -941,7 +932,7 @@ public double getAngle() {
               Vector3D v1 = applyTo(Vector3D.PLUS_Z);
               Vector3D v2 = applyInverseTo(Vector3D.PLUS_X);
               if  ((v2.getZ() < -0.9999999999) || (v2.getZ() > 0.9999999999)) {
-                  throw new IllegalStateException(CARDAN_SINGULARITY_MSG);
+                  throw new CardanSingularityException();
               }
               return new double[] {
                   Math.atan2(v2.getY(), v2.getX()),
@@ -959,7 +950,7 @@ public double getAngle() {
               Vector3D v1 = applyTo(Vector3D.PLUS_X);
               Vector3D v2 = applyInverseTo(Vector3D.PLUS_X);
               if ((v2.getX() < -0.9999999999) || (v2.getX() > 0.9999999999)) {
-                  throw new IllegalStateException(EULER_SINGULARITY_MSG);
+                  throw new EulerSingularityException();
               }
               return new double[] {
                   Math.atan2(v2.getY(), -v2.getZ()),
@@ -977,7 +968,7 @@ public double getAngle() {
               Vector3D v1 = applyTo(Vector3D.PLUS_X);
               Vector3D v2 = applyInverseTo(Vector3D.PLUS_X);
               if ((v2.getX() < -0.9999999999) || (v2.getX() > 0.9999999999)) {
-                  throw new IllegalStateException(EULER_SINGULARITY_MSG);
+                  throw new EulerSingularityException();
               }
               return new double[] {
                   Math.atan2(v2.getZ(), v2.getY()),
@@ -995,7 +986,7 @@ public double getAngle() {
               Vector3D v1 = applyTo(Vector3D.PLUS_Y);
               Vector3D v2 = applyInverseTo(Vector3D.PLUS_Y);
               if ((v2.getY() < -0.9999999999) || (v2.getY() > 0.9999999999)) {
-                  throw new IllegalStateException(EULER_SINGULARITY_MSG);
+                  throw new EulerSingularityException();
               }
               return new double[] {
                   Math.atan2(v2.getX(), v2.getZ()),
@@ -1013,7 +1004,7 @@ public double getAngle() {
               Vector3D v1 = applyTo(Vector3D.PLUS_Y);
               Vector3D v2 = applyInverseTo(Vector3D.PLUS_Y);
               if ((v2.getY() < -0.9999999999) || (v2.getY() > 0.9999999999)) {
-                  throw new IllegalStateException(EULER_SINGULARITY_MSG);
+                  throw new EulerSingularityException();
               }
               return new double[] {
                   Math.atan2(v2.getZ(), -v2.getX()),
@@ -1031,7 +1022,7 @@ public double getAngle() {
               Vector3D v1 = applyTo(Vector3D.PLUS_Z);
               Vector3D v2 = applyInverseTo(Vector3D.PLUS_Z);
               if ((v2.getZ() < -0.9999999999) || (v2.getZ() > 0.9999999999)) {
-                  throw new IllegalStateException(EULER_SINGULARITY_MSG);
+                  throw new EulerSingularityException();
               }
               return new double[] {
                   Math.atan2(v2.getX(), -v2.getY()),
@@ -1049,7 +1040,7 @@ public double getAngle() {
               Vector3D v1 = applyTo(Vector3D.PLUS_Z);
               Vector3D v2 = applyInverseTo(Vector3D.PLUS_Z);
               if ((v2.getZ() < -0.9999999999) || (v2.getZ() > 0.9999999999)) {
-                  throw new IllegalStateException(EULER_SINGULARITY_MSG);
+                  throw new EulerSingularityException();
               }
               return new double[] {
                   Math.atan2(v2.getY(), v2.getX()),
@@ -1416,4 +1407,48 @@ public static double distance(Rotation r1, Rotation r2) {
       return r1.composeInverseInternal(r2).getAngle();
   }
 
+  /** Exception thrown when an angle set encounters a singularity.
+   */
+  public static class AngleSetSingularityException extends GeometryException {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = 20180913L;
+
+    /** Simple constructor with an error message.
+     * @param msg error message
+     */
+    public AngleSetSingularityException(String msg) {
+      super(msg);
+    }
+  }
+
+  /** Exception thrown when a Cardan angles singularity is encountered.
+   */
+  public static class CardanSingularityException extends AngleSetSingularityException {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = 20180913L;
+
+    /**
+     * Simple constructor.
+     */
+    public CardanSingularityException() {
+      super("Cardan angles singularity");
+    }
+  }
+
+  /** Exception thrown when an Euler angles singularity is encountered.
+   */
+  public static class EulerSingularityException extends AngleSetSingularityException {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = 20180913L;
+
+    /**
+     * Simple constructor.
+     */
+    public EulerSingularityException() {
+      super("Euler angles singularity");
+    }
+  }
 }
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Vector3D.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Vector3D.java
index 75b797a..4b96f43 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Vector3D.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Vector3D.java
@@ -16,10 +16,10 @@
  */
 package org.apache.commons.geometry.euclidean.threed;
 
+import org.apache.commons.geometry.core.exception.IllegalNormException;
 import org.apache.commons.geometry.core.internal.SimpleTupleFormat;
-import org.apache.commons.geometry.core.internal.Vectors;
 import org.apache.commons.geometry.euclidean.EuclideanVector;
-import org.apache.commons.geometry.euclidean.internal.ZeroNormException;
+import org.apache.commons.geometry.euclidean.internal.Vectors;
 import org.apache.commons.numbers.arrays.LinearCombination;
 
 /** This class represents a vector in three-dimensional Euclidean space.
@@ -131,7 +131,7 @@ public double getMagnitudeSq() {
     /** {@inheritDoc} */
     @Override
     public Vector3D withMagnitude(double magnitude) {
-        final double invNorm = 1.0 / getNonZeroNorm();
+        final double invNorm = 1.0 / getFiniteNonZeroNorm();
 
         return new Vector3D(
                     magnitude * getX() * invNorm,
@@ -189,7 +189,7 @@ public Vector3D negate() {
     /** {@inheritDoc} */
     @Override
     public Vector3D normalize() {
-        return scalarMultiply(1.0 / getNonZeroNorm());
+        return scalarMultiply(1.0 / getFiniteNonZeroNorm());
     }
 
     /** Get a vector orthogonal to the instance.
@@ -205,10 +205,11 @@ public Vector3D normalize() {
      *   Vector3D j = Vector3D.crossProduct(k, i);
      * </code></pre>
      * @return a new normalized vector orthogonal to the instance
-     * @exception IllegalStateException if the norm of the instance is zero
+     * @exception IllegalNormException if the norm of the instance is zero, NaN,
+     *  or infinite
      */
     public Vector3D orthogonal() {
-        double threshold = 0.6 * getNonZeroNorm();
+        double threshold = 0.6 * getFiniteNonZeroNorm();
 
         final double x = getX();
         final double y = getY();
@@ -234,7 +235,7 @@ public Vector3D orthogonal() {
      */
     @Override
     public double angle(Vector3D v) {
-        double normProduct = getNonZeroNorm() * v.getNonZeroNorm();
+        double normProduct = getFiniteNonZeroNorm() * v.getFiniteNonZeroNorm();
 
         double dot = dotProduct(v);
         double threshold = normProduct * 0.9999;
@@ -382,17 +383,13 @@ public boolean equals(Object other) {
         return false;
     }
 
-    /** Returns the vector norm, throwing an IllegalStateException if the norm is zero.
-     * @return the non-zero norm value
-     * @throws IllegalStateException if the norm is zero
+    /** Returns the vector norm, throwing an IllegalNormException if the norm is zero,
+     * NaN, or infinite.
+     * @return the finite, non-zero norm value
+     * @throws IllegalNormException if the norm is zero, NaN, or infinite
      */
-    private double getNonZeroNorm() {
-        final double n = getNorm();
-        if (n == 0) {
-            throw new ZeroNormException();
-        }
-
-        return n;
+    private double getFiniteNonZeroNorm() {
+        return Vectors.ensureFiniteNonZeroNorm(getNorm());
     }
 
     /** Returns a component of the current instance relative to the given base
@@ -404,17 +401,14 @@ private double getNonZeroNorm() {
      *      is returned.
      * @return The projection or rejection of this instance relative to {@code base},
      *      depending on the value of {@code reject}.
-     * @throws IllegalStateException if {@code base} has a zero norm
+     * @throws IllegalNormException if {@code base} has a zero, NaN, or infinite norm
      */
     private Vector3D getComponent(Vector3D base, boolean reject) {
         final double aDotB = dotProduct(base);
 
-        final double baseMagSq = base.getNormSq();
-        if (baseMagSq == 0.0) {
-            throw new ZeroNormException(ZeroNormException.INVALID_BASE);
-        }
+        final double baseMag = Vectors.ensureFiniteNonZeroNorm(base.getNorm());
 
-        final double scale = aDotB / baseMagSq;
+        final double scale = aDotB / (baseMag * baseMag);
 
         final double projX = scale * base.getX();
         final double projY = scale * base.getY();
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Point2D.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Point2D.java
index fb6e23d..ece33f0 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Point2D.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Point2D.java
@@ -17,8 +17,8 @@
 package org.apache.commons.geometry.euclidean.twod;
 
 import org.apache.commons.geometry.core.internal.SimpleTupleFormat;
-import org.apache.commons.geometry.core.internal.Vectors;
 import org.apache.commons.geometry.euclidean.EuclideanPoint;
+import org.apache.commons.geometry.euclidean.internal.Vectors;
 import org.apache.commons.numbers.arrays.LinearCombination;
 
 /** This class represents a point in two-dimensional Euclidean space.
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Vector2D.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Vector2D.java
index 95313ae..36338d0 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Vector2D.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Vector2D.java
@@ -16,10 +16,10 @@
  */
 package org.apache.commons.geometry.euclidean.twod;
 
+import org.apache.commons.geometry.core.exception.IllegalNormException;
 import org.apache.commons.geometry.core.internal.SimpleTupleFormat;
-import org.apache.commons.geometry.core.internal.Vectors;
 import org.apache.commons.geometry.euclidean.EuclideanVector;
-import org.apache.commons.geometry.euclidean.internal.ZeroNormException;
+import org.apache.commons.geometry.euclidean.internal.Vectors;
 import org.apache.commons.numbers.arrays.LinearCombination;
 
 /** This class represents a vector in two-dimensional Euclidean space.
@@ -131,7 +131,7 @@ public double getMagnitudeSq() {
     /** {@inheritDoc} */
     @Override
     public Vector2D withMagnitude(double magnitude) {
-        final double invNorm = 1.0 / getNonZeroNorm();
+        final double invNorm = 1.0 / getFiniteNonZeroNorm();
 
         return new Vector2D(
                     magnitude * getX() * invNorm,
@@ -172,7 +172,7 @@ public Vector2D negate() {
     /** {@inheritDoc} */
     @Override
     public Vector2D normalize() {
-        return scalarMultiply(1.0 / getNonZeroNorm());
+        return scalarMultiply(1.0 / getFiniteNonZeroNorm());
     }
 
     /** {@inheritDoc} */
@@ -232,7 +232,7 @@ public Vector2D reject(Vector2D base) {
      */
     @Override
     public double angle(Vector2D v) {
-        double normProduct = getNonZeroNorm() * v.getNonZeroNorm();
+        double normProduct = getFiniteNonZeroNorm() * v.getFiniteNonZeroNorm();
 
         double dot = dotProduct(v);
         double threshold = normProduct * 0.9999;
@@ -328,17 +328,13 @@ public boolean equals(Object other) {
         return false;
     }
 
-    /** Returns the vector norm, throwing an IllegalStateException if the norm is zero.
-     * @return the non-zero norm value
-     * @throws IllegalStateException if the norm is zero
+    /** Returns the vector norm, throwing an IllegalNormException if the norm is zero,
+     * NaN, or infinite.
+     * @return the finite, non-zero norm value
+     * @throws IllegalNormException if the norm is zero, NaN, or infinite
      */
-    private double getNonZeroNorm() {
-        final double n = getNorm();
-        if (n == 0) {
-            throw new ZeroNormException();
-        }
-
-        return n;
+    private double getFiniteNonZeroNorm() {
+        return Vectors.ensureFiniteNonZeroNorm(getNorm());
     }
 
     /** Returns a component of the current instance relative to the given base
@@ -350,17 +346,14 @@ private double getNonZeroNorm() {
      *      is returned.
      * @return The projection or rejection of this instance relative to {@code base},
      *      depending on the value of {@code reject}.
-     * @throws IllegalStateException if {@code base} has a zero norm
+     * @throws IllegalNormException if {@code base} has a zero, NaN, or infinite norm
      */
     private Vector2D getComponent(Vector2D base, boolean reject) {
         final double aDotB = dotProduct(base);
 
-        final double baseMagSq = base.getNormSq();
-        if (baseMagSq == 0.0) {
-            throw new ZeroNormException(ZeroNormException.INVALID_BASE);
-        }
+        final double baseMag = Vectors.ensureFiniteNonZeroNorm(base.getNorm());
 
-        final double scale = aDotB / baseMagSq;
+        final double scale = aDotB / (baseMag * baseMag);
 
         final double projX = scale * base.getX();
         final double projY = scale * base.getY();
diff --git a/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/internal/VectorsTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/internal/VectorsTest.java
similarity index 78%
rename from commons-geometry-core/src/test/java/org/apache/commons/geometry/core/internal/VectorsTest.java
rename to commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/internal/VectorsTest.java
index 9817b28..2d38b03 100644
--- a/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/internal/VectorsTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/internal/VectorsTest.java
@@ -14,9 +14,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.commons.geometry.core.internal;
+package org.apache.commons.geometry.euclidean.internal;
 
-import org.apache.commons.geometry.core.internal.Vectors;
+import org.apache.commons.geometry.core.GeometryTestUtils;
+import org.apache.commons.geometry.core.exception.IllegalNormException;
 import org.junit.Assert;
 import org.junit.Test;
 
@@ -25,6 +26,41 @@
 
     private static final double EPS = Math.ulp(1d);
 
+    @Test
+    public void testIsFiniteNonZero() {
+        // act/assert
+        Assert.assertTrue(Vectors.isFiniteNonZero(1e-20));
+        Assert.assertTrue(Vectors.isFiniteNonZero(1e20));
+        Assert.assertTrue(Vectors.isFiniteNonZero(-1e-20));
+        Assert.assertTrue(Vectors.isFiniteNonZero(-1e20));
+
+        Assert.assertFalse(Vectors.isFiniteNonZero(0.0));
+        Assert.assertFalse(Vectors.isFiniteNonZero(Double.NaN));
+        Assert.assertFalse(Vectors.isFiniteNonZero(Double.POSITIVE_INFINITY));
+        Assert.assertFalse(Vectors.isFiniteNonZero(Double.NEGATIVE_INFINITY));
+    }
+
+    @Test
+    public void testEnsureFiniteNonZeroNorm() {
+        // act/assert
+        Assert.assertEquals(1.0, Vectors.ensureFiniteNonZeroNorm(1.0), EPS);
+        Assert.assertEquals(23.12, Vectors.ensureFiniteNonZeroNorm(23.12), EPS);
+        Assert.assertEquals(2e-12, Vectors.ensureFiniteNonZeroNorm(2e-12), EPS);
+
+        Assert.assertEquals(-1.0, Vectors.ensureFiniteNonZeroNorm(-1.0), EPS);
+        Assert.assertEquals(-23.12, Vectors.ensureFiniteNonZeroNorm(-23.12), EPS);
+        Assert.assertEquals(-2e-12, Vectors.ensureFiniteNonZeroNorm(-2e-12), EPS);
+
+        GeometryTestUtils.assertThrows(() -> Vectors.ensureFiniteNonZeroNorm(0.0),
+                IllegalNormException.class, "Illegal norm: 0.0");
+        GeometryTestUtils.assertThrows(() -> Vectors.ensureFiniteNonZeroNorm(Double.NaN),
+                IllegalNormException.class, "Illegal norm: NaN");
+        GeometryTestUtils.assertThrows(() -> Vectors.ensureFiniteNonZeroNorm(Double.POSITIVE_INFINITY),
+                IllegalNormException.class, "Illegal norm: Infinity");
+        GeometryTestUtils.assertThrows(() -> Vectors.ensureFiniteNonZeroNorm(Double.NEGATIVE_INFINITY),
+                IllegalNormException.class, "Illegal norm: -Infinity");
+    }
+
     @Test
     public void testNorm1_oneD() {
         // act/assert
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/oned/Vector1DTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/oned/Vector1DTest.java
index e5b580a..ee817cc 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/oned/Vector1DTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/oned/Vector1DTest.java
@@ -19,6 +19,8 @@
 import java.util.regex.Pattern;
 
 import org.apache.commons.geometry.core.Geometry;
+import org.apache.commons.geometry.core.GeometryTestUtils;
+import org.apache.commons.geometry.core.exception.IllegalNormException;
 import org.apache.commons.numbers.core.Precision;
 import org.junit.Assert;
 import org.junit.Test;
@@ -119,10 +121,17 @@ public void testWithMagnitude() {
         checkVector(Vector1D.of(-5).withMagnitude(3.0), -3.0);
     }
 
-    @Test(expected = IllegalStateException.class)
-    public void testWithMagnitude_zeroNorm() {
+    @Test
+    public void testWithMagnitude_illegalNorm() {
         // act/assert
-        Vector1D.ZERO.withMagnitude(1.0);
+        GeometryTestUtils.assertThrows(() -> Vector1D.ZERO.withMagnitude(2.0),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector1D.NaN.withMagnitude(2.0),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector1D.POSITIVE_INFINITY.withMagnitude(2.0),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector1D.NEGATIVE_INFINITY.withMagnitude(2.0),
+                IllegalNormException.class);
     }
 
     @Test
@@ -196,10 +205,17 @@ public void testNormalize() {
         checkVector(Vector1D.of(-5).normalize(), -1);
     }
 
-    @Test(expected = IllegalStateException.class)
-    public void testNormalize_zeroNorm() {
-        // act
-        Vector1D.ZERO.normalize();
+    @Test
+    public void testNormalize_illegalNorm() {
+        // act/assert
+        GeometryTestUtils.assertThrows(() -> Vector1D.ZERO.normalize(),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector1D.NaN.normalize(),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector1D.POSITIVE_INFINITY.normalize(),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector1D.NEGATIVE_INFINITY.normalize(),
+                IllegalNormException.class);
     }
 
     @Test
@@ -317,10 +333,17 @@ public void testProject() {
         checkVector(v3.project(v3), 4);
     }
 
-    @Test(expected = IllegalStateException.class)
-    public void testProject_baseHasZeroNorm() {
+    @Test
+    public void testProject_baseHasIllegalNorm() {
         // act/assert
-        Vector1D.of(2.0).project(Vector1D.ZERO);
+        GeometryTestUtils.assertThrows(() -> Vector1D.of(2.0).project(Vector1D.ZERO),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector1D.of(2.0).project(Vector1D.NaN),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector1D.of(2.0).project(Vector1D.POSITIVE_INFINITY),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector1D.of(2.0).project(Vector1D.NEGATIVE_INFINITY),
+                IllegalNormException.class);
     }
 
     @Test
@@ -340,10 +363,17 @@ public void testReject() {
         checkVector(v2.reject(v2), 0);
     }
 
-    @Test(expected = IllegalStateException.class)
-    public void testReject_baseHasZeroNorm() {
+    @Test
+    public void testReject_baseHasIllegalNorm() {
         // act/assert
-        Vector1D.of(2.0).reject(Vector1D.ZERO);
+        GeometryTestUtils.assertThrows(() -> Vector1D.of(2.0).reject(Vector1D.ZERO),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector1D.of(2.0).reject(Vector1D.NaN),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector1D.of(2.0).reject(Vector1D.POSITIVE_INFINITY),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector1D.of(2.0).reject(Vector1D.NEGATIVE_INFINITY),
+                IllegalNormException.class);
     }
 
     @Test
@@ -376,16 +406,29 @@ public void testAngle() {
         Assert.assertEquals(0.0, v4.angle(v4), TEST_TOLERANCE);
     }
 
-    @Test(expected = IllegalStateException.class)
-    public void testAngle_firstVectorZero() {
-        // act/assert
-        Vector1D.ZERO.angle(Vector1D.of(1.0));
-    }
-
-    @Test(expected = IllegalStateException.class)
-    public void testAngle_secondVectorZero() {
-        // act/assert
-        Vector1D.of(1.0).angle(Vector1D.ZERO);
+    @Test
+    public void testAngle_illegalNorm() {
+        // arrange
+        Vector1D v = Vector1D.of(1.0);
+
+        // act/assert
+        GeometryTestUtils.assertThrows(() -> Vector1D.ZERO.angle(v),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector1D.NaN.angle(v),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector1D.POSITIVE_INFINITY.angle(v),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector1D.NEGATIVE_INFINITY.angle(v),
+                IllegalNormException.class);
+
+        GeometryTestUtils.assertThrows(() -> v.angle(Vector1D.ZERO),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> v.angle(Vector1D.NaN),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> v.angle(Vector1D.POSITIVE_INFINITY),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> v.angle(Vector1D.NEGATIVE_INFINITY),
+                IllegalNormException.class);
     }
 
     @Test
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/RotationTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/RotationTest.java
index ca28f44..136a4cb 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/RotationTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/RotationTest.java
@@ -17,6 +17,9 @@
 
 package org.apache.commons.geometry.euclidean.threed;
 
+import org.apache.commons.geometry.core.exception.IllegalNormException;
+import org.apache.commons.geometry.euclidean.threed.Rotation.CardanSingularityException;
+import org.apache.commons.geometry.euclidean.threed.Rotation.EulerSingularityException;
 import org.apache.commons.numbers.angle.PlaneAngleRadians;
 import org.junit.Assert;
 import org.junit.Test;
@@ -62,7 +65,7 @@ public void testAxisAngleDeprecated() {
     try {
       new Rotation(Vector3D.of(0, 0, 0), 2 * Math.PI / 3);
       Assert.fail("an exception should have been thrown");
-    } catch (IllegalArgumentException e) {
+    } catch (IllegalNormException e) {
     }
 
     r = new Rotation(Vector3D.PLUS_Z, 1.5 * Math.PI);
@@ -92,7 +95,7 @@ public void testAxisAngleVectorOperator() {
     try {
       new Rotation(Vector3D.of(0, 0, 0), 2 * Math.PI / 3, RotationConvention.VECTOR_OPERATOR);
       Assert.fail("an exception should have been thrown");
-    } catch (IllegalArgumentException e) {
+    } catch (IllegalNormException e) {
     }
 
     r = new Rotation(Vector3D.PLUS_Z, 1.5 * Math.PI, RotationConvention.VECTOR_OPERATOR);
@@ -125,7 +128,7 @@ public void testAxisAngleFrameTransform() {
     try {
       new Rotation(Vector3D.of(0, 0, 0), 2 * Math.PI / 3, RotationConvention.FRAME_TRANSFORM);
       Assert.fail("an exception should have been thrown");
-    } catch (IllegalArgumentException e) {
+    } catch (IllegalNormException e) {
     }
 
     r = new Rotation(Vector3D.PLUS_Z, 1.5 * Math.PI, RotationConvention.FRAME_TRANSFORM);
@@ -195,7 +198,7 @@ public void testVectorOnePair() {
     try {
         new Rotation(u, Vector3D.ZERO);
         Assert.fail("an exception should have been thrown");
-    } catch (IllegalArgumentException e) {
+    } catch (IllegalNormException e) {
         // expected behavior
     }
 
@@ -235,7 +238,7 @@ public void testVectorTwoPairs() {
     try {
         new Rotation(u1, u2, Vector3D.ZERO, v2);
         Assert.fail("an exception should have been thrown");
-    } catch (IllegalArgumentException e) {
+    } catch (IllegalNormException e) {
       // expected behavior
     }
 
@@ -493,7 +496,7 @@ public void testSingularities() {
                   try {
                       r.getAngles(CardanOrders[i], convention);
                       Assert.fail("an exception should have been caught");
-                  } catch (IllegalStateException cese) {
+                  } catch (CardanSingularityException cese) {
                       // expected behavior
                   }
               }
@@ -511,7 +514,7 @@ public void testSingularities() {
                   try {
                       r.getAngles(EulerOrders[i], convention);
                       Assert.fail("an exception should have been caught");
-                  } catch (IllegalStateException cese) {
+                  } catch (EulerSingularityException cese) {
                       // expected behavior
                   }
               }
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/Vector3DTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/Vector3DTest.java
index 3aa2dd7..4d34a91 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/Vector3DTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/Vector3DTest.java
@@ -20,6 +20,8 @@
 import java.util.regex.Pattern;
 
 import org.apache.commons.geometry.core.Geometry;
+import org.apache.commons.geometry.core.GeometryTestUtils;
+import org.apache.commons.geometry.core.exception.IllegalNormException;
 import org.apache.commons.geometry.euclidean.EuclideanTestUtils;
 import org.apache.commons.numbers.core.Precision;
 import org.apache.commons.rng.UniformRandomProvider;
@@ -144,10 +146,17 @@ public void testWithMagnitude() {
         checkVector(Vector3D.of(x, y, z).withMagnitude(3), 3 * normX, 3 * normY, 3 * normZ);
     }
 
-    @Test(expected = IllegalStateException.class)
-    public void testWithMagnitude_zeroNorm() {
+    @Test
+    public void testWithMagnitude_illegalNorm() {
         // act/assert
-        Vector3D.ZERO.withMagnitude(1.0);
+        GeometryTestUtils.assertThrows(() -> Vector3D.ZERO.withMagnitude(2.0),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector3D.NaN.withMagnitude(2.0),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector3D.POSITIVE_INFINITY.withMagnitude(2.0),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector3D.NEGATIVE_INFINITY.withMagnitude(2.0),
+                IllegalNormException.class);
     }
 
     @Test
@@ -250,10 +259,17 @@ public void testNormalize() {
         Assert.assertEquals(1.0, Vector3D.of(5, -4, 2).normalize().getNorm(), 1.0e-12);
     }
 
-    @Test(expected = IllegalStateException.class)
-    public void testNormalize_zeroNorm() {
+    @Test
+    public void testNormalize_illegalNorm() {
         // act/assert
-        Vector3D.ZERO.normalize();
+        GeometryTestUtils.assertThrows(() -> Vector3D.ZERO.normalize(),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector3D.NaN.normalize(),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector3D.POSITIVE_INFINITY.normalize(),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector3D.NEGATIVE_INFINITY.normalize(),
+                IllegalNormException.class);
     }
 
     @Test
@@ -271,10 +287,17 @@ public void testOrthogonal() {
         Assert.assertEquals(0.0, v4.dotProduct(v4.orthogonal()), EPS);
     }
 
-    @Test(expected = IllegalStateException.class)
-    public void testOrthogonal_zeroNorm() {
+    @Test
+    public void testOrthogonal_illegalNorm() {
         // act/assert
-        Vector3D.ZERO.orthogonal();
+        GeometryTestUtils.assertThrows(() -> Vector3D.ZERO.orthogonal(),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector3D.NaN.orthogonal(),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector3D.POSITIVE_INFINITY.orthogonal(),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector3D.NEGATIVE_INFINITY.orthogonal(),
+                IllegalNormException.class);
     }
 
     @Test
@@ -299,10 +322,29 @@ public void testAngle() {
         Assert.assertEquals(Geometry.HALF_PI, Vector3D.PLUS_X.angle(Vector3D.MINUS_Z), tolerance);
     }
 
-    @Test(expected = IllegalStateException.class)
-    public void testAngle_zeroNorm() {
+    @Test
+    public void testAngle_illegalNorm() {
+        // arrange
+        Vector3D v = Vector3D.of(1.0, 1.0, 1.0);
+
         // act/assert
-        Vector3D.ZERO.angle(Vector3D.PLUS_X);
+        GeometryTestUtils.assertThrows(() -> Vector3D.ZERO.angle(v),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector3D.NaN.angle(v),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector3D.POSITIVE_INFINITY.angle(v),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector3D.NEGATIVE_INFINITY.angle(v),
+                IllegalNormException.class);
+
+        GeometryTestUtils.assertThrows(() -> v.angle(Vector3D.ZERO),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> v.angle(Vector3D.NaN),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> v.angle(Vector3D.POSITIVE_INFINITY),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> v.angle(Vector3D.NEGATIVE_INFINITY),
+                IllegalNormException.class);
     }
 
     @Test
@@ -592,10 +634,20 @@ public void testProject() {
         checkVector(v2.project(Vector3D.of(-1.0, -1.0, -1.0)), -6.0, -6.0, -6.0);
     }
 
-    @Test(expected = IllegalStateException.class)
-    public void testProject_baseHasZeroNorm() {
+    @Test
+    public void testProject_baseHasIllegalNorm() {
+        // arrange
+        Vector3D v = Vector3D.of(1.0, 1.0, 1.0);
+
         // act/assert
-        Vector3D.of(1.0, 1.0, 1.0).project(Vector3D.ZERO);
+        GeometryTestUtils.assertThrows(() -> v.project(Vector3D.ZERO),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> v.project(Vector3D.NaN),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> v.project(Vector3D.POSITIVE_INFINITY),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> v.project(Vector3D.NEGATIVE_INFINITY),
+                IllegalNormException.class);
     }
 
     @Test
@@ -628,10 +680,20 @@ public void testReject() {
         checkVector(v2.reject(Vector3D.of(-1.0, -1.0, -1.0)), 1.0, 0.0, -1.0);
     }
 
-    @Test(expected = IllegalStateException.class)
-    public void testReject_baseHasZeroNorm() {
+    @Test
+    public void testReject_baseHasIllegalNorm() {
+        // arrange
+        Vector3D v = Vector3D.of(1.0, 1.0, 1.0);
+
         // act/assert
-        Vector3D.of(1.0, 1.0, 1.0).reject(Vector3D.ZERO);
+        GeometryTestUtils.assertThrows(() -> v.reject(Vector3D.ZERO),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> v.reject(Vector3D.NaN),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> v.reject(Vector3D.POSITIVE_INFINITY),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> v.reject(Vector3D.NEGATIVE_INFINITY),
+                IllegalNormException.class);
     }
 
     @Test
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/Vector2DTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/Vector2DTest.java
index 62e0c8e..791f1c6 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/Vector2DTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/Vector2DTest.java
@@ -19,6 +19,8 @@
 import java.util.regex.Pattern;
 
 import org.apache.commons.geometry.core.Geometry;
+import org.apache.commons.geometry.core.GeometryTestUtils;
+import org.apache.commons.geometry.core.exception.IllegalNormException;
 import org.apache.commons.geometry.euclidean.EuclideanTestUtils;
 import org.apache.commons.numbers.core.Precision;
 import org.junit.Assert;
@@ -159,10 +161,17 @@ public void testWithMagnitude() {
         checkVector(Vector2D.of(0.5, 0.5).withMagnitude(2), Math.sqrt(2), Math.sqrt(2));
     }
 
-    @Test(expected = IllegalStateException.class)
-    public void testWithMagnitude_zeroNorm() {
+    @Test
+    public void testWithMagnitude_illegalNorm() {
         // act/assert
-        Vector2D.ZERO.withMagnitude(1.0);
+        GeometryTestUtils.assertThrows(() -> Vector2D.ZERO.withMagnitude(2.0),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector2D.NaN.withMagnitude(2.0),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector2D.POSITIVE_INFINITY.withMagnitude(2.0),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector2D.NEGATIVE_INFINITY.withMagnitude(2.0),
+                IllegalNormException.class);
     }
 
     @Test
@@ -243,9 +252,17 @@ public void testNormalize() {
         checkVector(Vector2D.of(-1, 2).normalize(), -1.0/Math.sqrt(5), 2.0/Math.sqrt(5));
     }
 
-    @Test(expected = IllegalStateException.class)
-    public void testNormalize_zeroNorm() {
-        Vector2D.ZERO.normalize();
+    @Test
+    public void testNormalize_illegalNorm() {
+        // act/assert
+        GeometryTestUtils.assertThrows(() -> Vector2D.ZERO.normalize(),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector2D.NaN.normalize(),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector2D.POSITIVE_INFINITY.normalize(),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector2D.NEGATIVE_INFINITY.normalize(),
+                IllegalNormException.class);
     }
 
     @Test
@@ -376,9 +393,29 @@ public void testAngle() {
     }
 
 
-    @Test(expected = IllegalStateException.class)
-    public void testAngle_zeroNorm() {
-        Vector2D.of(1, 1).angle(Vector2D.ZERO);
+    @Test
+    public void testAngle_illegalNorm() {
+        // arrange
+        Vector2D v = Vector2D.of(1.0, 1.0);
+
+        // act/assert
+        GeometryTestUtils.assertThrows(() -> Vector2D.ZERO.angle(v),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector2D.NaN.angle(v),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector2D.POSITIVE_INFINITY.angle(v),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector2D.NEGATIVE_INFINITY.angle(v),
+                IllegalNormException.class);
+
+        GeometryTestUtils.assertThrows(() -> v.angle(Vector2D.ZERO),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> v.angle(Vector2D.NaN),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> v.angle(Vector2D.POSITIVE_INFINITY),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> v.angle(Vector2D.NEGATIVE_INFINITY),
+                IllegalNormException.class);
     }
 
     @Test
@@ -418,10 +455,20 @@ public void testProject() {
         checkVector(v2.project(v1), (19.0 / 25.0) * 3.0, (19.0 / 25.0) * 4.0);
     }
 
-    @Test(expected = IllegalStateException.class)
-    public void testProject_baseHasZeroNorm() {
+    @Test
+    public void testProject_baseHasIllegalNorm() {
+        // arrange
+        Vector2D v = Vector2D.of(1.0, 1.0);
+
         // act/assert
-        Vector2D.of(1.0, 1.0).project(Vector2D.ZERO);
+        GeometryTestUtils.assertThrows(() -> v.project(Vector2D.ZERO),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> v.project(Vector2D.NaN),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> v.project(Vector2D.POSITIVE_INFINITY),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> v.project(Vector2D.NEGATIVE_INFINITY),
+                IllegalNormException.class);
     }
 
     @Test
@@ -445,10 +492,20 @@ public void testReject() {
         checkVector(v2.reject(v1), (-32.0 / 25.0), (6.0 / 25.0) * 4.0);
     }
 
-    @Test(expected = IllegalStateException.class)
-    public void testReject_baseHasZeroNorm() {
+    @Test
+    public void testReject_baseHasIllegalNorm() {
+        // arrange
+        Vector2D v = Vector2D.of(1.0, 1.0);
+
         // act/assert
-        Vector2D.of(1.0, 1.0).reject(Vector2D.ZERO);
+        GeometryTestUtils.assertThrows(() -> v.reject(Vector2D.ZERO),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> v.reject(Vector2D.NaN),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> v.reject(Vector2D.POSITIVE_INFINITY),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> v.reject(Vector2D.NEGATIVE_INFINITY),
+                IllegalNormException.class);
     }
 
     @Test
diff --git a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/oned/ArcsSet.java b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/oned/ArcsSet.java
index 311950f..8ca86ce 100644
--- a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/oned/ArcsSet.java
+++ b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/oned/ArcsSet.java
@@ -23,6 +23,7 @@
 import java.util.NoSuchElementException;
 
 import org.apache.commons.geometry.core.Geometry;
+import org.apache.commons.geometry.core.internal.GeometryInternalError;
 import org.apache.commons.geometry.core.partitioning.AbstractRegion;
 import org.apache.commons.geometry.core.partitioning.BSPTree;
 import org.apache.commons.geometry.core.partitioning.BoundaryProjection;
@@ -42,9 +43,6 @@
  */
 public class ArcsSet extends AbstractRegion<S1Point, S1Point> implements Iterable<double[]> {
 
-    /** Message used for internal errors. */
-    private static final String INTERNAL_ERROR_MESSAGE = "Please file a bug report";
-
     /** Build an arcs set representing the whole circle.
      * @param tolerance tolerance below which close sub-arcs are merged together
      */
@@ -79,11 +77,11 @@ public ArcsSet(final double lower, final double upper, final double tolerance)
      * {@code Boolean.TRUE} and {@code Boolean.FALSE}</p>
      * @param tree inside/outside BSP tree representing the arcs set
      * @param tolerance tolerance below which close sub-arcs are merged together
-     * @exception InconsistentStateAt2PiWrapping if the tree leaf nodes are not
+     * @exception IllegalArgumentException if the tree leaf nodes are not
      * consistent across the \( 0, 2 \pi \) crossing
      */
     public ArcsSet(final BSPTree<S1Point> tree, final double tolerance)
-        throws InconsistentStateAt2PiWrapping {
+        throws IllegalArgumentException {
         super(tree, tolerance);
         check2PiConsistency();
     }
@@ -107,11 +105,11 @@ public ArcsSet(final BSPTree<S1Point> tree, final double tolerance)
      * space.</p>
      * @param boundary collection of boundary elements
      * @param tolerance tolerance below which close sub-arcs are merged together
-     * @exception InconsistentStateAt2PiWrapping if the tree leaf nodes are not
+     * @exception IllegalArgumentException if the tree leaf nodes are not
      * consistent across the \( 0, 2 \pi \) crossing
      */
     public ArcsSet(final Collection<SubHyperplane<S1Point>> boundary, final double tolerance)
-        throws InconsistentStateAt2PiWrapping {
+        throws IllegalArgumentException {
         super(boundary, tolerance);
         check2PiConsistency();
     }
@@ -167,10 +165,10 @@ public ArcsSet(final Collection<SubHyperplane<S1Point>> boundary, final double t
     }
 
     /** Check consistency.
-    * @exception InconsistentStateAt2PiWrapping if the tree leaf nodes are not
+    * @exception IllegalArgumentException if the tree leaf nodes are not
     * consistent across the \( 0, 2 \pi \) crossing
     */
-    private void check2PiConsistency() throws InconsistentStateAt2PiWrapping {
+    private void check2PiConsistency() throws IllegalArgumentException {
 
         // start search at the tree root
         BSPTree<S1Point> root = getTree(false);
@@ -185,7 +183,7 @@ private void check2PiConsistency() throws InconsistentStateAt2PiWrapping {
         final Boolean stateAfter = (Boolean) getLastLeaf(root).getAttribute();
 
         if (stateBefore ^ stateAfter) {
-            throw new InconsistentStateAt2PiWrapping();
+            throw new IllegalArgumentException("Inconsistent state at 2\\u03c0 wrapping");
         }
 
     }
@@ -655,7 +653,7 @@ private void selectPending() {
                 }
                 if (end == null) {
                     // this should never happen
-                    throw new IllegalStateException(INTERNAL_ERROR_MESSAGE);
+                    throw new GeometryInternalError();
                 }
 
                 // we have identified the last arc
@@ -794,7 +792,7 @@ private void addArcLimit(final BSPTree<S1Point> tree, final double alpha, final
         final BSPTree<S1Point> node = tree.getCell(limit.getLocation(), getTolerance());
         if (node.getCut() != null) {
             // this should never happen
-            throw new IllegalStateException(INTERNAL_ERROR_MESSAGE);
+            throw new GeometryInternalError();
         }
 
         node.insertCut(limit);
@@ -926,27 +924,5 @@ public Side getSide() {
                 return Side.HYPER;
             }
         }
-
-    }
-
-    /** Specialized exception for inconsistent BSP tree state inconsistency.
-     * <p>
-     * This exception is thrown at {@link ArcsSet} construction time when the
-     * {@link org.apache.commons.geometry.core.partitioning.Region.Location inside/outside}
-     * state is not consistent at the 0, \(2 \pi \) crossing.
-     * </p>
-     */
-    public static class InconsistentStateAt2PiWrapping extends IllegalArgumentException {
-
-        /** Serializable UID. */
-        private static final long serialVersionUID = 20140107L;
-
-        /** Simple constructor.
-         */
-        public InconsistentStateAt2PiWrapping() {
-            super("Inconsistent state at 2\\u03c0 wrapping");
-        }
-
     }
-
 }
diff --git a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/PropertiesComputer.java b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/PropertiesComputer.java
index 3f6da88..536a2dd 100644
--- a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/PropertiesComputer.java
+++ b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/PropertiesComputer.java
@@ -20,6 +20,7 @@
 import java.util.List;
 
 import org.apache.commons.geometry.core.Geometry;
+import org.apache.commons.geometry.core.internal.GeometryInternalError;
 import org.apache.commons.geometry.core.partitioning.BSPTree;
 import org.apache.commons.geometry.core.partitioning.BSPTreeVisitor;
 import org.apache.commons.geometry.euclidean.threed.Point3D;
@@ -79,7 +80,7 @@ public void visitLeafNode(final BSPTree<S2Point> node) {
             final List<Vertex> boundary = convex.getBoundaryLoops();
             if (boundary.size() != 1) {
                 // this should never happen
-                throw new IllegalStateException("Please file a bug report");
+                throw new GeometryInternalError();
             }
 
             // compute the geometrical properties of the convex cell
diff --git a/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/oned/ArcsSetTest.java b/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/oned/ArcsSetTest.java
index a6bab1a..8d8c600 100644
--- a/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/oned/ArcsSetTest.java
+++ b/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/oned/ArcsSetTest.java
@@ -358,7 +358,7 @@ public void testShiftedAngles() {
 
     }
 
-    @Test(expected=ArcsSet.InconsistentStateAt2PiWrapping.class)
+    @Test(expected=IllegalArgumentException.class)
     public void testInconsistentState() {
         SubLimitAngle l1 = new LimitAngle(S1Point.of(1.0), false, 1.0e-10).wholeHyperplane();
         SubLimitAngle l2 = new LimitAngle(S1Point.of(2.0), true,  1.0e-10).wholeHyperplane();


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@xxxxxxxxxxxxxxxx


With regards,
Apache Git Services

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@xxxxxxxxxxxxxxxxxx
For additional commands, e-mail: dev-help@xxxxxxxxxxxxxxxxxx