001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.numbers.angle;
018
019import java.util.function.DoubleSupplier;
020import java.util.function.DoubleUnaryOperator;
021
022/**
023 * Represents the <a href="https://en.wikipedia.org/wiki/Angle">angle</a> concept.
024 */
025public abstract class Angle implements DoubleSupplier {
026    /** 2&pi;. */
027    public static final double TWO_PI = 2 * Math.PI;
028    /** &pi;/2. */
029    public static final double PI_OVER_TWO = 0.5 * Math.PI;
030    /** Turns to degrees conversion factor. */
031    private static final double TURN_TO_DEG = 360d;
032    /** Radians to degrees conversion factor. */
033    private static final double RAD_TO_DEG = 180.0 / Math.PI;
034    /** Degrees to radians conversion factor. */
035    private static final double DEG_TO_RAD = Math.PI / 180.0;
036
037    /** Value (unit depends on concrete instance). */
038    private final double value;
039
040    /**
041     * @param value Value in turns.
042     */
043    private Angle(final double value) {
044        this.value = value;
045    }
046
047    /** @return the value. */
048    @Override
049    public double getAsDouble() {
050        return value;
051    }
052
053    /** {@inheritDoc} */
054    @Override
055    public int hashCode() {
056        return Double.hashCode(value);
057    }
058
059    /**
060     * Test for equality with another object.
061     * Objects are considered to be equal if their concrete types are
062     * equal and their values are exactly the same (or both are {@code Double.NaN}).
063     *
064     * @param other Object to test for equality with this instance.
065     * @return {@code true} if the objects are equal, {@code false} if
066     * {@code other} is {@code null}, not of the same type as this instance,
067     * or not equal to this instance.
068     */
069    @Override
070    public boolean equals(final Object other) {
071        return other != null &&
072                getClass().equals(other.getClass()) &&
073                Double.doubleToLongBits(value) == Double.doubleToLongBits(((Angle) other).value);
074    }
075
076    /**
077     * @return the angle in <a href="https://en.wikipedia.org/wiki/Turn_%28geometry%29">turns</a>.
078     */
079    public abstract Turn toTurn();
080
081    /**
082     * @return the angle in <a href="https://en.wikipedia.org/wiki/Radian">radians</a>.
083     */
084    public abstract Rad toRad();
085
086    /**
087     * @return the angle in <a href="https://en.wikipedia.org/wiki/Degree_%28angle%29">degrees</a>.
088     */
089    public abstract Deg toDeg();
090
091    /**
092     * Unit: <a href="https://en.wikipedia.org/wiki/Turn_%28geometry%29">turns</a>.
093     */
094    public static final class Turn extends Angle {
095        /** Zero. */
096        public static final Turn ZERO = Turn.of(0d);
097        /** Normalizing operator (result will be within the {@code [0, 1[} interval). */
098        public static final DoubleUnaryOperator WITHIN_0_AND_1 = normalizer(0d);
099
100        /**
101         * @param angle (in turns).
102         */
103        private Turn(final double angle) {
104            super(angle);
105        }
106
107        /**
108         * @param angle (in turns).
109         * @return a new intance.
110         */
111        public static Turn of(final double angle) {
112            return new Turn(angle);
113        }
114
115        /** {@inheritDoc} */
116        @Override
117        public Turn toTurn() {
118            return this;
119        }
120
121        /** {@inheritDoc} */
122        @Override
123        public Rad toRad() {
124            return Rad.of(getAsDouble() * TWO_PI);
125        }
126
127        /** {@inheritDoc} */
128        @Override
129        public Deg toDeg() {
130            return Deg.of(getAsDouble() * TURN_TO_DEG);
131        }
132
133        /**
134         * Creates an operator for normalizing/reducing an angle.
135         * The output will be within the {@code [lo, lo + 1[} interval.
136         *
137         * @param lo Lower bound of the normalized interval.
138         * @return the normalization operator.
139         */
140        public static DoubleUnaryOperator normalizer(final double lo) {
141            return new Normalizer(lo, 1d);
142        }
143    }
144
145    /**
146     * Unit: <a href="https://en.wikipedia.org/wiki/Radian">radians</a>.
147     */
148    public static final class Rad extends Angle {
149        /** Zero. */
150        public static final Rad ZERO = Rad.of(0d);
151        /** &pi;. */
152        public static final Rad PI = Rad.of(Math.PI);
153        /** 2&pi;. */
154        public static final Rad TWO_PI = Rad.of(Angle.TWO_PI);
155        /** Normalizing operator (result will be within the <code>[0, 2&pi;[</code> interval). */
156        public static final DoubleUnaryOperator WITHIN_0_AND_2PI = normalizer(0d);
157        /** Normalizing operator (result will be within the <code>[-&pi;, &pi;[</code> interval). */
158        public static final DoubleUnaryOperator WITHIN_MINUS_PI_AND_PI = normalizer(-Math.PI);
159
160        /**
161         * @param angle (in radians).
162         */
163        private Rad(final double angle) {
164            super(angle);
165        }
166
167        /**
168         * @param angle (in radians).
169         * @return a new intance.
170         */
171        public static Rad of(final double angle) {
172            return new Rad(angle);
173        }
174
175        /** {@inheritDoc} */
176        @Override
177        public Turn toTurn() {
178            return Turn.of(getAsDouble() / Angle.TWO_PI);
179        }
180
181        /** {@inheritDoc} */
182        @Override
183        public Rad toRad() {
184            return this;
185        }
186
187        /** {@inheritDoc} */
188        @Override
189        public Deg toDeg() {
190            return Deg.of(getAsDouble() * RAD_TO_DEG);
191        }
192
193        /**
194         * Creates an operator for normalizing/reducing an angle.
195         * The output will be within the <code> [lo, lo + 2&pi;[</code> interval.
196         *
197         * @param lo Lower bound of the normalized interval.
198         * @return the normalization operator.
199         */
200        public static DoubleUnaryOperator normalizer(final double lo) {
201            return new Normalizer(lo, Angle.TWO_PI);
202        }
203    }
204
205    /**
206     * Unit: <a href="https://en.wikipedia.org/wiki/Degree_%28angle%29">degrees</a>.
207     */
208    public static final class Deg extends Angle {
209        /** Zero. */
210        public static final Deg ZERO = Deg.of(0d);
211        /** Normalizing operator (result will be within the {@code [0, 360[} interval). */
212        public static final DoubleUnaryOperator WITHIN_0_AND_360 = normalizer(0d);
213
214        /**
215         * @param angle (in degrees).
216         */
217        private Deg(final double angle) {
218            super(angle);
219        }
220
221        /**
222         * @param angle (in degrees).
223         * @return a new intance.
224         */
225        public static Deg of(final double angle) {
226            return new Deg(angle);
227        }
228
229        /** {@inheritDoc} */
230        @Override
231        public Turn toTurn() {
232            return Turn.of(getAsDouble() / TURN_TO_DEG);
233        }
234
235        /** {@inheritDoc} */
236        @Override
237        public Rad toRad() {
238            return Rad.of(getAsDouble() * DEG_TO_RAD);
239        }
240
241        /** {@inheritDoc} */
242        @Override
243        public Deg toDeg() {
244            return this;
245        }
246
247        /**
248         * Creates an operator for normalizing/reducing an angle.
249         * The output will be within the {@code [c, c + 360[} interval.
250         *
251         * @param lo Lower bound of the normalized interval.
252         * @return the normalization operator.
253         */
254        public static DoubleUnaryOperator normalizer(final double lo) {
255            return new Normalizer(lo, TURN_TO_DEG);
256        }
257    }
258
259    /**
260     * Normalizes an angle around a center value.
261     */
262    private static final class Normalizer implements DoubleUnaryOperator {
263        /** Lower bound. */
264        private final double lo;
265        /** Upper bound. */
266        private final double hi;
267        /** Period. */
268        private final double period;
269        /** Normalizer. */
270        private final Reduce reduce;
271
272        /**
273         * Note: It is assumed that both arguments have the same unit.
274         *
275         * @param lo Lower bound of the desired interval.
276         * @param period Circonference of the circle.
277         */
278        Normalizer(final double lo,
279                   final double period) {
280            this.period = period;
281            this.lo = lo;
282            this.hi = lo + period;
283            reduce = new Reduce(lo, period);
284        }
285
286        /**
287         * @param a Angle.
288         * @return {@code = a - k} where {@code k} is an integer that satisfies
289         * {@code lo <= a - k < lo + period}.
290         */
291        @Override
292        public double applyAsDouble(final double a) {
293            if (lo <= a &&
294                a < hi) {
295                // Already within the main interval.
296                return a;
297            }
298
299            final double normalized = reduce.applyAsDouble(a) + lo;
300            return normalized < hi ?
301                normalized :
302                // If value is too small to be representable compared to the
303                // floor expression above (i.e. value + x = x), then we may
304                // end up with a number exactly equal to the upper bound.
305                // In that case, subtract one period from the normalized value
306                // so that the result is strictly less than the upper bound. (We also
307                // want to ensure that we do not return anything less than the lower bound.)
308                Math.max(lo, normalized - period);
309        }
310    }
311}