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);
}
}
}