View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.geometry.euclidean.twod;
18  
19  import java.util.Arrays;
20  import java.util.Comparator;
21  import java.util.Iterator;
22  import java.util.function.UnaryOperator;
23  
24  import org.apache.commons.geometry.core.internal.DoubleFunction2N;
25  import org.apache.commons.geometry.core.internal.SimpleTupleFormat;
26  import org.apache.commons.geometry.euclidean.EuclideanVectorSum;
27  import org.apache.commons.geometry.euclidean.MultiDimensionalEuclideanVector;
28  import org.apache.commons.geometry.euclidean.internal.Vectors;
29  import org.apache.commons.numbers.core.Precision;
30  
31  /** This class represents vectors and points in two-dimensional Euclidean space.
32   * Instances of this class are guaranteed to be immutable.
33   */
34  public class Vector2D extends MultiDimensionalEuclideanVector<Vector2D> {
35  
36      /** Zero vector (coordinates: 0, 0). */
37      public static final Vector2D ZERO = new Vector2D(0, 0);
38  
39      /** A vector with all coordinates set to NaN. */
40      public static final Vector2D NaN = new Vector2D(Double.NaN, Double.NaN);
41  
42      /** A vector with all coordinates set to positive infinity. */
43      public static final Vector2D POSITIVE_INFINITY =
44          new Vector2D(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY);
45  
46      /** A vector with all coordinates set to negative infinity. */
47      public static final Vector2D NEGATIVE_INFINITY =
48          new Vector2D(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY);
49  
50      /** Comparator that sorts vectors in component-wise ascending order.
51       * Vectors are only considered equal if their coordinates match exactly.
52       * Null arguments are evaluated as being greater than non-null arguments.
53       */
54      public static final Comparator<Vector2D> COORDINATE_ASCENDING_ORDER = (a, b) -> {
55          int cmp = 0;
56  
57          if (a != null && b != null) {
58              cmp = Double.compare(a.getX(), b.getX());
59              if (cmp == 0) {
60                  cmp = Double.compare(a.getY(), b.getY());
61              }
62          } else if (a != null) {
63              cmp = -1;
64          } else if (b != null) {
65              cmp = 1;
66          }
67  
68          return cmp;
69      };
70  
71      /** Abscissa (first coordinate). */
72      private final double x;
73  
74      /** Ordinate (second coordinate). */
75      private final double y;
76  
77      /** Simple constructor.
78       * @param x abscissa (first coordinate)
79       * @param y ordinate (second coordinate)
80       */
81      private Vector2D(final double x, final double y) {
82          this.x = x;
83          this.y = y;
84      }
85  
86      /** Returns the abscissa (first coordinate value) of the instance.
87       * @return the abscissa
88       */
89      public double getX() {
90          return x;
91      }
92  
93      /** Returns the ordinate (second coordinate value) of the instance.
94       * @return the ordinate
95       */
96      public double getY() {
97          return y;
98      }
99  
100     /** Get the coordinates for this instance as a dimension 2 array.
101      * @return coordinates for this instance
102      */
103     public double[] toArray() {
104         return new double[]{x, y};
105     }
106 
107     /** {@inheritDoc} */
108     @Override
109     public int getDimension() {
110         return 2;
111     }
112 
113     /** {@inheritDoc} */
114     @Override
115     public boolean isNaN() {
116         return Double.isNaN(x) || Double.isNaN(y);
117     }
118 
119     /** {@inheritDoc} */
120     @Override
121     public boolean isInfinite() {
122         return !isNaN() && (Double.isInfinite(x) || Double.isInfinite(y));
123     }
124 
125     /** {@inheritDoc} */
126     @Override
127     public boolean isFinite() {
128         return Double.isFinite(x) && Double.isFinite(y);
129     }
130 
131     /** {@inheritDoc} */
132     @Override
133     public Vector2D vectorTo(final Vector2D v) {
134         return v.subtract(this);
135     }
136 
137     /** {@inheritDoc} */
138     @Override
139     public Unit directionTo(final Vector2D v) {
140         return vectorTo(v).normalize();
141     }
142 
143     /** {@inheritDoc} */
144     @Override
145     public Vector2D lerp(final Vector2D p, final double t) {
146         return Sum.create()
147                 .addScaled(1.0 - t, this)
148                 .addScaled(t, p).get();
149     }
150 
151     /** {@inheritDoc} */
152     @Override
153     public Vector2D getZero() {
154         return ZERO;
155     }
156 
157     /** {@inheritDoc} */
158     @Override
159     public double norm() {
160         return Vectors.norm(x, y);
161     }
162 
163     /** {@inheritDoc} */
164     @Override
165     public double normSq() {
166         return Vectors.normSq(x, y);
167     }
168 
169     /** {@inheritDoc} */
170     @Override
171     public Vector2D withNorm(final double magnitude) {
172         final double invNorm = 1.0 / getCheckedNorm();
173 
174         return new Vector2D(
175                     magnitude * x * invNorm,
176                     magnitude * y * invNorm
177                 );
178     }
179 
180     /** {@inheritDoc} */
181     @Override
182     public Vector2D add(final Vector2D v) {
183         return new Vector2D(x + v.x, y + v.y);
184     }
185 
186     /** {@inheritDoc} */
187     @Override
188     public Vector2D add(final double factor, final Vector2D v) {
189         return new Vector2D(x + (factor * v.x), y + (factor * v.y));
190     }
191 
192     /** {@inheritDoc} */
193     @Override
194     public Vector2D subtract(final Vector2D v) {
195         return new Vector2D(x - v.x, y - v.y);
196     }
197 
198     /** {@inheritDoc} */
199     @Override
200     public Vector2D subtract(final double factor, final Vector2D v) {
201         return new Vector2D(x - (factor * v.x), y - (factor * v.y));
202     }
203 
204     /** {@inheritDoc} */
205     @Override
206     public Vector2D negate() {
207         return new Vector2D(-x, -y);
208     }
209 
210     /** {@inheritDoc} */
211     @Override
212     public Unit normalize() {
213         return Unit.from(x, y);
214     }
215 
216     /** {@inheritDoc} */
217     @Override
218     public Unit normalizeOrNull() {
219         return Unit.tryCreateNormalized(x, y, false);
220     }
221 
222     /** {@inheritDoc} */
223     @Override
224     public Vector2D multiply(final double a) {
225         return new Vector2D(a * x, a * y);
226     }
227 
228     /** {@inheritDoc} */
229     @Override
230     public double distance(final Vector2D v) {
231         return Vectors.norm(x - v.x, y - v.y);
232     }
233 
234     /** {@inheritDoc} */
235     @Override
236     public double distanceSq(final Vector2D v) {
237         return Vectors.normSq(x - v.x, y - v.y);
238     }
239 
240     /** {@inheritDoc} */
241     @Override
242     public double dot(final Vector2D v) {
243         return Vectors.linearCombination(x, v.x, y, v.y);
244     }
245 
246     /** {@inheritDoc}
247      * <p>This method computes the angular separation between the two
248      * vectors using the dot product for well separated vectors and the
249      * cross product for almost aligned vectors. This allows to have a
250      * good accuracy in all cases, even for vectors very close to each
251      * other.</p>
252      */
253     @Override
254     public double angle(final Vector2D v) {
255         final double normProduct = getCheckedNorm() * v.getCheckedNorm();
256 
257         final double dot = dot(v);
258         final double threshold = normProduct * 0.9999;
259         if ((dot < -threshold) || (dot > threshold)) {
260             // the vectors are almost aligned, compute using the sine
261             final double n = Math.abs(Vectors.linearCombination(x, v.y, -y, v.x));
262             if (dot >= 0) {
263                 return Math.asin(n / normProduct);
264             }
265             return Math.PI - Math.asin(n / normProduct);
266         }
267 
268         // the vectors are sufficiently separated to use the cosine
269         return Math.acos(dot / normProduct);
270     }
271 
272     /** {@inheritDoc} */
273     @Override
274     public Vector2D project(final Vector2D base) {
275         return getComponent(base, false, Vector2D::new);
276     }
277 
278     /** {@inheritDoc} */
279     @Override
280     public Vector2D reject(final Vector2D base) {
281         return getComponent(base, true, Vector2D::new);
282     }
283 
284     /** {@inheritDoc}
285      * The returned vector is computed by rotating the current instance {@code pi/2} radians
286      * counterclockwise around the origin and normalizing. For example, if this method is
287      * called on a vector pointing along the positive x-axis, then a unit vector representing
288      * the positive y-axis is returned.
289      * @return a unit vector orthogonal to the current instance
290      * @throws IllegalArgumentException if the norm of the current instance is zero, NaN, or infinite
291      */
292     @Override
293     public Vector2D.Unit orthogonal() {
294         return Unit.from(-y, x);
295     }
296 
297     /** {@inheritDoc} */
298     @Override
299     public Vector2D.Unit orthogonal(final Vector2D dir) {
300         return dir.getComponent(this, true, Vector2D.Unit::from);
301     }
302 
303     /** Compute the signed area of the parallelogram with sides formed by this instance
304      * and the given vector.
305      *
306      * <p>The parallelogram in question can be visualized by taking the current instance as the
307      * first side and placing {@code v} at the end of it to create the second. The other sides
308      * are formed by lines parallel to these two vectors. If {@code v} points to the <em>left</em> of
309      * the current instance (ie, the parallelogram is wound counter-clockwise), then the
310      * returned area is positive. If {@code v} points to the <em>right</em> of the current instance,
311      * (ie, the parallelogram is wound clockwise), then the returned area is negative. If
312      * the vectors are collinear (ie, they lie on the same line), then 0 is returned. The area of
313      * the triangle formed by the two vectors is exactly half of the returned value.
314      * @param v vector representing the second side of the constructed parallelogram
315      * @return the signed area of the parallelogram formed by this instance and the given vector
316      */
317     public double signedArea(final Vector2D v) {
318         return Vectors.linearCombination(
319                 x, v.y,
320                 -y, v.x);
321     }
322 
323     /** Convenience method to apply a function to this vector. This
324      * can be used to transform the vector inline with other methods.
325      * @param fn the function to apply
326      * @return the transformed vector
327      */
328     public Vector2D transform(final UnaryOperator<Vector2D> fn) {
329         return fn.apply(this);
330     }
331 
332     /** {@inheritDoc} */
333     @Override
334     public boolean eq(final Vector2D vec, final Precision.DoubleEquivalence precision) {
335         return precision.eq(x, vec.x) &&
336                 precision.eq(y, vec.y);
337     }
338 
339     /**
340      * Get a hashCode for the 2D coordinates.
341      * <p>
342      * All NaN values have the same hash code.</p>
343      *
344      * @return a hash code value for this object
345      */
346     @Override
347     public int hashCode() {
348         if (isNaN()) {
349             return 542;
350         }
351         return 122 * (76 * Double.hashCode(x) + Double.hashCode(y));
352     }
353 
354     /**
355      * Test for the equality of two vector instances.
356      * <p>
357      * If all coordinates of two vectors are exactly the same, and none are
358      * <code>Double.NaN</code>, the two instances are considered to be equal.
359      * </p>
360      * <p>
361      * <code>NaN</code> coordinates are considered to globally affect the vector
362      * and be equal to each other - i.e, if either (or all) coordinates of the
363      * vector are equal to <code>Double.NaN</code>, the vector is equal to
364      * {@link #NaN}.
365      * </p>
366      *
367      * @param other Object to test for equality to this
368      * @return true if two Vector2D objects are equal, false if
369      *         object is null, not an instance of Vector2D, or
370      *         not equal to this Vector2D instance
371      *
372      */
373     @Override
374     public boolean equals(final Object other) {
375         if (this == other) {
376             return true;
377         }
378         if (other instanceof Vector2D) {
379             final Vector2D rhs = (Vector2D) other;
380             if (rhs.isNaN()) {
381                 return this.isNaN();
382             }
383 
384             return Double.compare(x, rhs.x) == 0 &&
385                     Double.compare(y, rhs.y) == 0;
386         }
387         return false;
388     }
389 
390     /** {@inheritDoc} */
391     @Override
392     public String toString() {
393         return SimpleTupleFormat.getDefault().format(x, y);
394     }
395 
396     /** Returns a component of the current instance relative to the given base
397      * vector. If {@code reject} is true, the vector rejection is returned; otherwise,
398      * the projection is returned.
399      * @param base The base vector
400      * @param reject If true, the rejection of this instance from {@code base} is
401      *      returned. If false, the projection of this instance onto {@code base}
402      *      is returned.
403      * @param factory factory function used to build the final vector
404      * @param <T> Vector implementation type
405      * @return The projection or rejection of this instance relative to {@code base},
406      *      depending on the value of {@code reject}.
407      * @throws IllegalArgumentException if {@code base} has a zero, NaN, or infinite norm
408      */
409     private <T extends Vector2D> T getComponent(final Vector2D base, final boolean reject,
410             final DoubleFunction2N<T> factory) {
411         final double aDotB = dot(base);
412 
413         // We need to check the norm value here to ensure that it's legal. However, we don't
414         // want to incur the cost or floating point error of getting the actual norm and then
415         // multiplying it again to get the square norm. So, we'll just check the squared norm
416         // directly. This will produce the same error result as checking the actual norm since
417         // Math.sqrt(0.0) == 0.0, Math.sqrt(Double.NaN) == Double.NaN and
418         // Math.sqrt(Double.POSITIVE_INFINITY) == Double.POSITIVE_INFINITY.
419         final double baseMagSq = Vectors.checkedNorm(base.normSq());
420 
421         final double scale = aDotB / baseMagSq;
422 
423         final double projX = scale * base.x;
424         final double projY = scale * base.y;
425 
426         if (reject) {
427             return factory.apply(x - projX, y - projY);
428         }
429 
430         return factory.apply(projX, projY);
431     }
432 
433     /** Returns a vector with the given coordinate values.
434      * @param x abscissa (first coordinate value)
435      * @param y abscissa (second coordinate value)
436      * @return vector instance
437      */
438     public static Vector2D of(final double x, final double y) {
439         return new Vector2D(x, y);
440     }
441 
442     /** Creates a vector from the coordinates in the given 2-element array.
443      * @param v coordinates array
444      * @return new vector
445      * @exception IllegalArgumentException if the array does not have 2 elements
446      */
447     public static Vector2D of(final double[] v) {
448         if (v.length != 2) {
449             throw new IllegalArgumentException("Dimension mismatch: " + v.length + " != 2");
450         }
451         return new Vector2D(v[0], v[1]);
452     }
453 
454     /** Parses the given string and returns a new vector instance. The expected string
455      * format is the same as that returned by {@link #toString()}.
456      * @param str the string to parse
457      * @return vector instance represented by the string
458      * @throws IllegalArgumentException if the given string has an invalid format
459      */
460     public static Vector2D parse(final String str) {
461         return SimpleTupleFormat.getDefault().parse(str, Vector2D::new);
462     }
463 
464     /** Return a vector containing the maximum component values from all input vectors.
465      * @param first first vector
466      * @param more additional vectors
467      * @return a vector containing the maximum component values from all input vectors
468      */
469     public static Vector2D max(final Vector2D first, final Vector2D... more) {
470         return computeMax(first, Arrays.asList(more).iterator());
471     }
472 
473     /** Return a vector containing the maximum component values from all input vectors.
474      * @param vecs input vectors
475      * @return a vector containing the maximum component values from all input vectors
476      * @throws IllegalArgumentException if the argument does not contain any vectors
477      */
478     public static Vector2D max(final Iterable<Vector2D> vecs) {
479         final Iterator<Vector2D> it = vecs.iterator();
480         if (!it.hasNext()) {
481             throw new IllegalArgumentException("Cannot compute vector max: no vectors given");
482         }
483 
484         return computeMax(it.next(), it);
485     }
486 
487     /** Internal method for computing a max vector.
488      * @param first first vector
489      * @param more iterator with additional vectors
490      * @return vector containing the maximum component values of all input vectors
491      */
492     private static Vector2D computeMax(final Vector2D first, final Iterator<? extends Vector2D> more) {
493         double x = first.getX();
494         double y = first.getY();
495 
496         Vector2D vec;
497         while (more.hasNext()) {
498             vec = more.next();
499 
500             x = Math.max(x, vec.getX());
501             y = Math.max(y, vec.getY());
502         }
503 
504         return Vector2D.of(x, y);
505     }
506 
507     /** Return a vector containing the minimum component values from all input vectors.
508      * @param first first vector
509      * @param more more vectors
510      * @return a vector containing the minimum component values from all input vectors
511      */
512     public static Vector2D min(final Vector2D first, final Vector2D... more) {
513         return computeMin(first, Arrays.asList(more).iterator());
514     }
515 
516     /** Return a vector containing the minimum component values from all input vectors.
517      * @param vecs input vectors
518      * @return a vector containing the minimum component values from all input vectors
519      * @throws IllegalArgumentException if the argument does not contain any vectors
520      */
521     public static Vector2D min(final Iterable<Vector2D> vecs) {
522         final Iterator<Vector2D> it = vecs.iterator();
523         if (!it.hasNext()) {
524             throw new IllegalArgumentException("Cannot compute vector min: no vectors given");
525         }
526 
527         return computeMin(it.next(), it);
528     }
529 
530     /** Internal method for computing a min vector.
531      * @param first first vector
532      * @param more iterator with additional vectors
533      * @return vector containing the minimum component values of all input vectors
534      */
535     private static Vector2D computeMin(final Vector2D first, final Iterator<? extends Vector2D> more) {
536         double x = first.getX();
537         double y = first.getY();
538 
539         Vector2D vec;
540         while (more.hasNext()) {
541             vec = more.next();
542 
543             x = Math.min(x, vec.getX());
544             y = Math.min(y, vec.getY());
545         }
546 
547         return Vector2D.of(x, y);
548     }
549 
550     /** Compute the centroid of the given points. The centroid is the arithmetic mean position of a set
551      * of points.
552      * @param first first point
553      * @param more additional points
554      * @return the centroid of the given points
555      */
556     public static Vector2D centroid(final Vector2D first, final Vector2D... more) {
557         return computeCentroid(first, Arrays.asList(more).iterator());
558     }
559 
560     /** Compute the centroid of the given points. The centroid is the arithmetic mean position of a set
561      * of points.
562      * @param pts the points to compute the centroid of
563      * @return the centroid of the given points
564      * @throws IllegalArgumentException if the argument contains no points
565      */
566     public static Vector2D centroid(final Iterable<Vector2D> pts) {
567         final Iterator<Vector2D> it = pts.iterator();
568         if (!it.hasNext()) {
569             throw new IllegalArgumentException("Cannot compute centroid: no points given");
570         }
571 
572         return computeCentroid(it.next(), it);
573     }
574 
575     /** Internal method for computing the centroid of a set of points.
576      * @param first first point
577      * @param more iterator with additional points
578      * @return the centroid of the point set
579      */
580     private static Vector2D computeCentroid(final Vector2D first, final Iterator<? extends Vector2D> more) {
581         final Sum sum = Sum.of(first);
582         int count = 1;
583 
584         while (more.hasNext()) {
585             sum.add(more.next());
586             ++count;
587         }
588 
589         return sum.get().multiply(1.0 / count);
590     }
591 
592     /**
593      * Represents unit vectors.
594      * This allows optimizations for certain operations.
595      */
596     public static final class Unit extends Vector2D {
597         /** Unit vector (coordinates: 1, 0). */
598         public static final Unit PLUS_X  = new Unit(1d, 0d);
599         /** Negation of unit vector (coordinates: -1, 0). */
600         public static final Unit MINUS_X = new Unit(-1d, 0d);
601         /** Unit vector (coordinates: 0, 1). */
602         public static final Unit PLUS_Y  = new Unit(0d, 1d);
603         /** Negation of unit vector (coordinates: 0, -1). */
604         public static final Unit MINUS_Y = new Unit(0d, -1d);
605 
606         /** Maximum coordinate value for computing normalized vectors
607          * with raw, unscaled values.
608          */
609         private static final double UNSCALED_MAX = 0x1.0p+500;
610 
611         /** Factor used to scale up coordinate values in order to produce
612          * normalized coordinates without overflow or underflow.
613          */
614         private static final double SCALE_UP_FACTOR = 0x1.0p+600;
615 
616         /** Factor used to scale down coordinate values in order to produce
617          * normalized coordinates without overflow or underflow.
618          */
619         private static final double SCALE_DOWN_FACTOR = 0x1.0p-600;
620 
621         /** Simple constructor. Callers are responsible for ensuring that the given
622          * values represent a normalized vector.
623          * @param x abscissa (first coordinate value)
624          * @param y abscissa (second coordinate value)
625          */
626         private Unit(final double x, final double y) {
627             super(x, y);
628         }
629 
630         /** {@inheritDoc} */
631         @Override
632         public double norm() {
633             return 1;
634         }
635 
636         /** {@inheritDoc} */
637         @Override
638         public double normSq() {
639             return 1;
640         }
641 
642         /** {@inheritDoc} */
643         @Override
644         public Unit normalize() {
645             return this;
646         }
647 
648         /** {@inheritDoc} */
649         @Override
650         public Unit normalizeOrNull() {
651             return this;
652         }
653 
654         /** {@inheritDoc} */
655         @Override
656         public Vector2D.Unit orthogonal() {
657             return new Unit(-getY(), getX());
658         }
659 
660         /** {@inheritDoc} */
661         @Override
662         public Vector2D withNorm(final double mag) {
663             return multiply(mag);
664         }
665 
666         /** {@inheritDoc} */
667         @Override
668         public Unit negate() {
669             return new Unit(-getX(), -getY());
670         }
671 
672         /** Create a normalized vector.
673          * @param x Vector coordinate.
674          * @param y Vector coordinate.
675          * @return a vector whose norm is 1.
676          * @throws IllegalArgumentException if the norm of the given value is zero, NaN,
677          *      or infinite
678          */
679         public static Unit from(final double x, final double y) {
680             return tryCreateNormalized(x, y, true);
681         }
682 
683         /** Create a normalized vector.
684          * @param v Vector.
685          * @return a vector whose norm is 1.
686          * @throws IllegalArgumentException if the norm of the given value is zero, NaN,
687          *      or infinite
688          */
689         public static Unit from(final Vector2D v) {
690             return v instanceof Unit ?
691                 (Unit) v :
692                 from(v.getX(), v.getY());
693         }
694 
695         /** Attempt to create a normalized vector from the given coordinate values. If {@code throwOnFailure}
696          * is true, an exception is thrown if a normalized vector cannot be created. Otherwise, null
697          * is returned.
698          * @param x x coordinate
699          * @param y y coordinate
700          * @param throwOnFailure if true, an exception will be thrown if a normalized vector cannot be created
701          * @return normalized vector or null if one cannot be created and {@code throwOnFailure}
702          *      is false
703          * @throws IllegalArgumentException if the computed norm is zero, NaN, or infinite
704          */
705         private static Unit tryCreateNormalized(final double x, final double y, final boolean throwOnFailure) {
706 
707             // Compute the inverse norm directly. If the result is a non-zero real number,
708             // then we can go ahead and construct the unit vector immediately. If not,
709             // we'll do some extra work for edge cases.
710             final double norm = Vectors.norm(x, y);
711             final double normInv = 1.0 / norm;
712             if (Vectors.isRealNonZero(normInv)) {
713                 return new Unit(
714                         x * normInv,
715                         y * normInv);
716             }
717 
718             // Direct computation did not work. Try scaled versions of the coordinates
719             // to handle overflow and underflow.
720             final double scaledX;
721             final double scaledY;
722 
723             final double maxCoord = Math.max(Math.abs(x), Math.abs(y));
724             if (maxCoord > UNSCALED_MAX) {
725                 scaledX = x * SCALE_DOWN_FACTOR;
726                 scaledY = y * SCALE_DOWN_FACTOR;
727             } else {
728                 scaledX = x * SCALE_UP_FACTOR;
729                 scaledY = y * SCALE_UP_FACTOR;
730             }
731 
732             final double scaledNormInv = 1.0 / Vectors.norm(scaledX, scaledY);
733 
734             if (Vectors.isRealNonZero(scaledNormInv)) {
735                 return new Unit(
736                         scaledX * scaledNormInv,
737                         scaledY * scaledNormInv);
738             } else if (throwOnFailure) {
739                 throw Vectors.illegalNorm(norm);
740             }
741             return null;
742         }
743     }
744 
745     /** Class used to create high-accuracy sums of vectors. Each vector component is
746      * summed using an instance of {@link org.apache.commons.numbers.core.Sum}.
747      *
748      * <p>This class is mutable and not thread-safe.
749      * @see org.apache.commons.numbers.core.Sum
750      */
751     public static final class Sum extends EuclideanVectorSum<Vector2D> {
752         /** X component sum. */
753         private final org.apache.commons.numbers.core.Sum xsum;
754         /** Y component sum. */
755         private final org.apache.commons.numbers.core.Sum ysum;
756 
757         /** Construct a new instance with the given initial value.
758          * @param initial initial value
759          */
760         Sum(final Vector2D initial) {
761             this.xsum = org.apache.commons.numbers.core.Sum.of(initial.x);
762             this.ysum = org.apache.commons.numbers.core.Sum.of(initial.y);
763         }
764 
765         /** {@inheritDoc} */
766         @Override
767         public Sum add(final Vector2D vec) {
768             xsum.add(vec.x);
769             ysum.add(vec.y);
770             return this;
771         }
772 
773         /** {@inheritDoc} */
774         @Override
775         public Sum addScaled(final double scale, final Vector2D vec) {
776             xsum.addProduct(scale, vec.x);
777             ysum.addProduct(scale, vec.y);
778             return this;
779         }
780 
781         /** {@inheritDoc} */
782         @Override
783         public Vector2D get() {
784             return Vector2D.of(
785                     xsum.getAsDouble(),
786                     ysum.getAsDouble());
787         }
788 
789         /** Create a new instance with an initial value set to the {@link Vector2D#ZERO zero vector}.
790          * @return new instance set to zero
791          */
792         public static Sum create() {
793             return new Sum(Vector2D.ZERO);
794         }
795 
796         /** Construct a new instance with an initial value set to the argument.
797          * @param initial initial sum value
798          * @return new instance
799          */
800         public static Sum of(final Vector2D initial) {
801             return new Sum(initial);
802         }
803 
804         /** Construct a new instance from multiple values.
805          * @param first first vector
806          * @param more additional vectors
807          * @return new instance
808          */
809         public static Sum of(final Vector2D first, final Vector2D... more) {
810             final Sum s = new Sum(first);
811             for (final Vector2D v : more) {
812                 s.add(v);
813             }
814             return s;
815         }
816     }
817 }