Angle.java
- /*
- * 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.numbers.angle;
- import java.util.function.DoubleSupplier;
- import java.util.function.DoubleUnaryOperator;
- /**
- * Represents the <a href="https://en.wikipedia.org/wiki/Angle">angle</a> concept.
- */
- public abstract class Angle implements DoubleSupplier {
- /** 2π. */
- public static final double TWO_PI = 2 * Math.PI;
- /** π/2. */
- public static final double PI_OVER_TWO = 0.5 * Math.PI;
- /** Turns to degrees conversion factor. */
- private static final double TURN_TO_DEG = 360d;
- /** Radians to degrees conversion factor. */
- private static final double RAD_TO_DEG = 180.0 / Math.PI;
- /** Degrees to radians conversion factor. */
- private static final double DEG_TO_RAD = Math.PI / 180.0;
- /** Value (unit depends on concrete instance). */
- private final double value;
- /**
- * @param value Value in turns.
- */
- Angle(final double value) {
- this.value = value;
- }
- /** @return the value. */
- @Override
- public double getAsDouble() {
- return value;
- }
- /** {@inheritDoc} */
- @Override
- public int hashCode() {
- return Double.hashCode(value);
- }
- /**
- * Test for equality with another object.
- * Objects are considered to be equal if their concrete types are
- * equal and their values are exactly the same (or both are {@code Double.NaN}).
- *
- * @param other Object to test for equality with this instance.
- * @return {@code true} if the objects are equal, {@code false} if
- * {@code other} is {@code null}, not of the same type as this instance,
- * or not equal to this instance.
- */
- @Override
- public boolean equals(final Object other) {
- return other != null &&
- getClass().equals(other.getClass()) &&
- Double.doubleToLongBits(value) == Double.doubleToLongBits(((Angle) other).value);
- }
- /**
- * Convert to a {@link Turn}.
- *
- * @return the angle in <a href="https://en.wikipedia.org/wiki/Turn_%28geometry%29">turns</a>.
- */
- public abstract Turn toTurn();
- /**
- * Convert to a {@link Rad}.
- *
- * @return the angle in <a href="https://en.wikipedia.org/wiki/Radian">radians</a>.
- */
- public abstract Rad toRad();
- /**
- * Convert to a {@link Deg}.
- *
- * @return the angle in <a href="https://en.wikipedia.org/wiki/Degree_%28angle%29">degrees</a>.
- */
- public abstract Deg toDeg();
- /**
- * Unit: <a href="https://en.wikipedia.org/wiki/Turn_%28geometry%29">turns</a>.
- */
- public static final class Turn extends Angle {
- /** Zero. */
- public static final Turn ZERO = of(0d);
- /** Normalizing operator (result will be within the {@code [0, 1[} interval). */
- public static final DoubleUnaryOperator WITHIN_0_AND_1 = normalizer(0d);
- /**
- * Create an instance.
- *
- * @param angle (in turns).
- */
- private Turn(final double angle) {
- super(angle);
- }
- /**
- * Create an instance.
- *
- * @param angle (in turns).
- * @return a new instance.
- */
- public static Turn of(final double angle) {
- return new Turn(angle);
- }
- /** {@inheritDoc} */
- @Override
- public Turn toTurn() {
- return this;
- }
- /** {@inheritDoc} */
- @Override
- public Rad toRad() {
- return Rad.of(getAsDouble() * TWO_PI);
- }
- /** {@inheritDoc} */
- @Override
- public Deg toDeg() {
- return Deg.of(getAsDouble() * TURN_TO_DEG);
- }
- /**
- * Creates an operator for normalizing/reducing an angle.
- * The output will be within the {@code [lo, lo + 1[} interval.
- *
- * @param lo Lower bound of the normalized interval.
- * @return the normalization operator.
- */
- public static DoubleUnaryOperator normalizer(final double lo) {
- return new Normalizer(lo, 1d);
- }
- }
- /**
- * Unit: <a href="https://en.wikipedia.org/wiki/Radian">radians</a>.
- */
- public static final class Rad extends Angle {
- /** Zero. */
- public static final Rad ZERO = of(0d);
- /** π. */
- public static final Rad PI = of(Math.PI);
- /** 2π. */
- public static final Rad TWO_PI = of(Angle.TWO_PI);
- /** Normalizing operator (result will be within the <code>[0, 2π[</code> interval). */
- public static final DoubleUnaryOperator WITHIN_0_AND_2PI = normalizer(0d);
- /** Normalizing operator (result will be within the <code>[-π, π[</code> interval). */
- public static final DoubleUnaryOperator WITHIN_MINUS_PI_AND_PI = normalizer(-Math.PI);
- /**
- * Create an instance.
- *
- * @param angle (in radians).
- */
- private Rad(final double angle) {
- super(angle);
- }
- /**
- * Create an instance.
- *
- * @param angle (in radians).
- * @return a new instance.
- */
- public static Rad of(final double angle) {
- return new Rad(angle);
- }
- /** {@inheritDoc} */
- @Override
- public Turn toTurn() {
- return Turn.of(getAsDouble() / Angle.TWO_PI);
- }
- /** {@inheritDoc} */
- @Override
- public Rad toRad() {
- return this;
- }
- /** {@inheritDoc} */
- @Override
- public Deg toDeg() {
- return Deg.of(getAsDouble() * RAD_TO_DEG);
- }
- /**
- * Creates an operator for normalizing/reducing an angle.
- * The output will be within the <code> [lo, lo + 2π[</code> interval.
- *
- * @param lo Lower bound of the normalized interval.
- * @return the normalization operator.
- */
- public static DoubleUnaryOperator normalizer(final double lo) {
- return new Normalizer(lo, Angle.TWO_PI);
- }
- }
- /**
- * Unit: <a href="https://en.wikipedia.org/wiki/Degree_%28angle%29">degrees</a>.
- */
- public static final class Deg extends Angle {
- /** Zero. */
- public static final Deg ZERO = of(0d);
- /** Normalizing operator (result will be within the {@code [0, 360[} interval). */
- public static final DoubleUnaryOperator WITHIN_0_AND_360 = normalizer(0d);
- /**
- * Create an instance.
- *
- * @param angle (in degrees).
- */
- private Deg(final double angle) {
- super(angle);
- }
- /**
- * Create an instance.
- *
- * @param angle (in degrees).
- * @return a new instance.
- */
- public static Deg of(final double angle) {
- return new Deg(angle);
- }
- /** {@inheritDoc} */
- @Override
- public Turn toTurn() {
- return Turn.of(getAsDouble() / TURN_TO_DEG);
- }
- /** {@inheritDoc} */
- @Override
- public Rad toRad() {
- return Rad.of(getAsDouble() * DEG_TO_RAD);
- }
- /** {@inheritDoc} */
- @Override
- public Deg toDeg() {
- return this;
- }
- /**
- * Creates an operator for normalizing/reducing an angle.
- * The output will be within the {@code [c, c + 360[} interval.
- *
- * @param lo Lower bound of the normalized interval.
- * @return the normalization operator.
- */
- public static DoubleUnaryOperator normalizer(final double lo) {
- return new Normalizer(lo, TURN_TO_DEG);
- }
- }
- /**
- * Normalizes an angle around a center value.
- */
- private static final class Normalizer implements DoubleUnaryOperator {
- /** Lower bound. */
- private final double lo;
- /** Upper bound. */
- private final double hi;
- /** Period. */
- private final double period;
- /** Normalizer. */
- private final Reduce reduce;
- /**
- * Note: It is assumed that both arguments have the same unit.
- *
- * @param lo Lower bound of the desired interval.
- * @param period Circonference of the circle.
- */
- Normalizer(final double lo,
- final double period) {
- this.period = period;
- this.lo = lo;
- this.hi = lo + period;
- reduce = new Reduce(lo, period);
- }
- /**
- * @param a Angle.
- * @return {@code = a - k} where {@code k} is an integer that satisfies
- * {@code lo <= a - k < lo + period}.
- */
- @Override
- public double applyAsDouble(final double a) {
- if (lo <= a &&
- a < hi) {
- // Already within the main interval.
- return a;
- }
- final double normalized = reduce.applyAsDouble(a) + lo;
- return normalized < hi ?
- normalized :
- // If value is too small to be representable compared to the
- // floor expression above (i.e. value + x = x), then we may
- // end up with a number exactly equal to the upper bound.
- // In that case, subtract one period from the normalized value
- // so that the result is strictly less than the upper bound. (We also
- // want to ensure that we do not return anything less than the lower bound.)
- Math.max(lo, normalized - period);
- }
- }
- }