angle_sc/
lib.rs

1// Copyright (c) 2024-2025 Ken Barker
2
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"),
5// to deal in the Software without restriction, including without limitation the
6// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
7// sell copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9
10// The above copyright notice and this permission notice shall be included in
11// all copies or substantial portions of the Software.
12
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19// THE SOFTWARE.
20
21//! [![crates.io](https://img.shields.io/crates/v/angle-sc.svg)](https://crates.io/crates/angle-sc)
22//! [![docs.io](https://docs.rs/angle-sc/badge.svg)](https://docs.rs/angle-sc/)
23//! [![License](https://img.shields.io/badge/License-MIT-blue)](https://opensource.org/license/mit/)
24//! [![Rust](https://github.com/kenba/angle-sc-rs/actions/workflows/rust.yml/badge.svg)](https://github.com/kenba/angle-sc-rs/actions)
25//! [![codecov](https://codecov.io/gh/kenba/angle-sc-rs/graph/badge.svg?token=6DTOY9Y4BT)](https://codecov.io/gh/kenba/angle-sc-rs)
26//!
27//! A Rust library for performing accurate and efficient trigonometry calculations.
28//!
29//! ## Description
30//!
31//! The standard trigonometry functions: `sin`, `cos`, `tan`, etc.
32//! [give unexpected results for well-known angles](https://stackoverflow.com/questions/31502120/sin-and-cos-give-unexpected-results-for-well-known-angles#answer-31525208).
33//! This is because the functions use parameters with `radians` units instead of `degrees`.
34//! The conversion from `degrees` to `radians` suffers from
35//! [round-off error](https://en.wikipedia.org/wiki/Round-off_error) due to
36//! `radians` being based on the irrational number π.
37//! This library provides a [sincos](src/trig.rs#sincos) function to calculate more
38//! accurate values than the standard `sin` and `cos` functions for angles in radians
39//! and a [sincosd](src/trig.rs#sincosd) function to calculate more accurate values
40//! for angles in degrees.
41//!
42//! The library also provides an [Angle](#angle) struct which represents an angle
43//! by its sine and cosine as the coordinates of a
44//! [unit circle](https://en.wikipedia.org/wiki/Unit_circle),
45//! see *Figure 1*.
46//!
47//! ![Unit circle](https://upload.wikimedia.org/wikipedia/commons/thumb/7/72/Sinus_und_Kosinus_am_Einheitskreis_1.svg/250px-Sinus_und_Kosinus_am_Einheitskreis_1.svg.png)
48//! *Figure 1 Unit circle formed by cos *θ* and sin *θ**
49//!
50//! The `Angle` struct enables more accurate calculations of angle rotations and
51//! conversions to and from `degrees` or `radians`.
52//!
53//! ## Features
54//!
55//! * `Degrees`, `Radians` and `Angle` types;
56//! * functions for accurately calculating sines and cosines of angles in `Degrees` or `Radians`
57//!     using [remquo](https://pubs.opengroup.org/onlinepubs/9699919799/functions/remquo.html);
58//! * functions for accurately calculating sines and cosines of differences of angles in `Degrees` or `Radians`
59//!     using the [2Sum](https://en.wikipedia.org/wiki/2Sum) algorithm;
60//! * functions for accurately calculating sums and differences of `Angles` using
61//!     [trigonometric identities](https://en.wikipedia.org/wiki/List_of_trigonometric_identities#Angle_sum_and_difference_identities);
62//! * and some [spherical trigonometry](https://en.wikipedia.org/wiki/Spherical_trigonometry) functions.
63//! * The library is declared [no_std](https://docs.rust-embedded.org/book/intro/no-std.html).
64//!
65//! ## Examples
66//!
67//! The following example shows the `round-off error` inherent in calculating angles in `radians`.
68//! It calculates the correct sine and cosine for 60° and converts them back
69//! precisely to 60°, but it fails to convert them to the precise angle in `radians`: π/3.
70//! ```
71//! use angle_sc::{Angle, Degrees, Radians, is_within_tolerance, trig};
72//!
73//! let angle_60 = Angle::from(Degrees(60.0));
74//! assert_eq!(trig::COS_30_DEGREES, angle_60.sin().0);
75//! assert_eq!(0.5, angle_60.cos().0);
76//! assert_eq!(60.0, Degrees::from(angle_60).0);
77//!
78//! // assert_eq!(core::f64::consts::FRAC_PI_3, Radians::from(angle_60).0); // Fails because PI is irrational
79//! assert!(is_within_tolerance(
80//!    core::f64::consts::FRAC_PI_3,
81//!    Radians::from(angle_60).0,
82//!    f64::EPSILON
83//! ));
84//! ```
85//!
86//! The following example calculates the sine and cosine between the difference
87//! of two angles in `degrees`: -155° - 175°.
88//! It is more accurate than calling the `Angle` `From` trait in the example above
89//! with the difference in `degrees`.
90//! It is particularly useful for implementing the
91//! [Haversine formula](https://en.wikipedia.org/wiki/Haversine_formula)
92//! which requires sines and cosines of both longitude and latitude differences.
93//! Note: in this example sine and cosine of 30° are converted precisely to π/6.
94//! ```
95//! use angle_sc::{Angle, Degrees, Radians, trig};
96//!
97//! // Difference of Degrees(-155.0) - Degrees(175.0)
98//! let angle_30 = Angle::from((Degrees(-155.0), Degrees(175.0)));
99//! assert_eq!(0.5, angle_30.sin().0);
100//! assert_eq!(trig::COS_30_DEGREES, angle_30.cos().0);
101//! assert_eq!(30.0, Degrees::from(angle_30).0);
102//! assert_eq!(core::f64::consts::FRAC_PI_6, Radians::from(angle_30).0);
103//! ```
104//!
105//! ## Design
106//!
107//! ### Trigonometry Functions
108//!
109//! The `trig` module contains accurate and efficient trigonometry functions.
110//!
111//! ### Angle
112//!
113//! The `Angle` struct represents an angle by its sine and cosine instead of in
114//! `degrees` or `radians`.
115//!
116//! This representation an angle makes functions such as
117//! rotating an angle +/-90° around the unit circle or calculating the opposite angle;
118//! simple, accurate and efficient since they just involve changing the signs
119//! and/or positions of the `sin` and `cos` values.
120//!
121//! `Angle` `Add` and `Sub` traits are implemented using
122//! [angle sum and difference](https://en.wikipedia.org/wiki/List_of_trigonometric_identities#Angle_sum_and_difference_identities)
123//! trigonometric identities,
124//! while `Angle` [double](https://en.wikipedia.org/wiki/List_of_trigonometric_identities#Double-angle_formulae)
125//! and [half](https://en.wikipedia.org/wiki/List_of_trigonometric_identities#Half-angle_formulae) methods use other
126//! trigonometric identities.
127//!
128//! The `sin` and `cos` fields of `Angle` are `UnitNegRange`s:,
129//! a [newtype](https://rust-unofficial.github.io/patterns/patterns/behavioural/newtype.html)
130//! with values in the range -1.0 to +1.0 inclusive.
131
132#![cfg_attr(not(test), no_std)]
133#![allow(clippy::float_cmp)]
134
135pub mod trig;
136use core::cmp::{Ordering, PartialOrd};
137use core::convert::From;
138use core::ops::{Add, AddAssign, Neg, Sub, SubAssign};
139use serde::{Deserialize, Deserializer, Serialize, Serializer};
140
141/// The Degrees newtype an f64.
142#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
143pub struct Degrees(pub f64);
144
145impl Degrees {
146    /// The absolute value of the angle.
147    #[must_use]
148    pub fn abs(self) -> Self {
149        Self(libm::fabs(self.0))
150    }
151
152    /// The opposite angle on the circle, i.e. +/- 180 degrees.
153    #[must_use]
154    pub fn opposite(self) -> Self {
155        Self(if 0.0 < self.0 {
156            self.0 - 180.0
157        } else {
158            self.0 + 180.0
159        })
160    }
161}
162
163impl Default for Degrees {
164    #[must_use]
165    fn default() -> Self {
166        Self(0.0)
167    }
168}
169
170impl Neg for Degrees {
171    type Output = Self;
172
173    /// An implementation of Neg for Degrees, i.e. -angle.
174    /// # Examples
175    /// ```
176    /// use angle_sc::Degrees;
177    ///
178    /// let angle_45 = Degrees(45.0);
179    /// let result_m45 = -angle_45;
180    /// assert_eq!(-45.0, result_m45.0);
181    /// ```
182    #[must_use]
183    fn neg(self) -> Self {
184        Self(0.0 - self.0)
185    }
186}
187
188impl Add for Degrees {
189    type Output = Self;
190
191    /// Add a pair of angles in Degrees, wraps around +/-180.
192    /// Uses the [2Sum](https://en.wikipedia.org/wiki/2Sum) algorithm to reduce
193    /// round-off error.
194    /// # Examples
195    /// ```
196    /// use angle_sc::{Degrees};
197    ///
198    /// let angle_120 = Degrees(120.0);
199    /// let result = angle_120 + angle_120;
200    /// assert_eq!(-angle_120, result);
201    /// ```
202    #[must_use]
203    fn add(self, other: Self) -> Self::Output {
204        let (s, t) = two_sum(self.0, other.0);
205        Self(if s <= -180.0 {
206            s + 360.0 + t
207        } else if s > 180.0 {
208            s - 360.0 + t
209        } else {
210            s
211        })
212    }
213}
214
215impl AddAssign for Degrees {
216    fn add_assign(&mut self, other: Self) {
217        *self = *self + other;
218    }
219}
220
221impl Sub for Degrees {
222    type Output = Self;
223
224    /// Subtract a pair of angles in Degrees, wraps around +/-180.
225    /// Uses the [2Sum](https://en.wikipedia.org/wiki/2Sum) algorithm to reduce
226    /// round-off error.
227    /// # Examples
228    /// ```
229    /// use angle_sc::{Degrees};
230    ///
231    /// let angle_120 = Degrees(120.0);
232    /// let result = -angle_120 - angle_120;
233    /// assert_eq!(angle_120, result);
234    /// ```
235    #[must_use]
236    fn sub(self, other: Self) -> Self::Output {
237        self + -other
238    }
239}
240
241impl SubAssign for Degrees {
242    fn sub_assign(&mut self, other: Self) {
243        *self = *self - other;
244    }
245}
246
247/// The Radians newtype an f64.
248#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
249pub struct Radians(pub f64);
250
251impl Radians {
252    /// The absolute value of the angle.
253    #[must_use]
254    pub fn abs(self) -> Self {
255        Self(libm::fabs(self.0))
256    }
257
258    /// The opposite angle on the circle, i.e. +/- PI.
259    #[must_use]
260    pub fn opposite(self) -> Self {
261        Self(if 0.0 < self.0 {
262            self.0 - core::f64::consts::PI
263        } else {
264            self.0 + core::f64::consts::PI
265        })
266    }
267
268    /// Clamp value into the range: `0.0..=max_value`.
269    /// # Examples
270    /// ```
271    /// use angle_sc::Radians;
272    ///
273    /// let value = Radians(-f64::EPSILON);
274    /// assert_eq!(Radians(0.0), value.clamp(Radians(1.0)));
275    /// let value = Radians(0.0);
276    /// assert_eq!(Radians(0.0), value.clamp(Radians(1.0)));
277    /// let value = Radians(1.0);
278    /// assert_eq!(Radians(1.0), value.clamp(Radians(1.0)));
279    /// let value = Radians(1.0 + f64::EPSILON);
280    /// assert_eq!(Radians(1.0), value.clamp(Radians(1.0)));
281    /// ```
282    #[must_use]
283    pub const fn clamp(self, max_value: Self) -> Self {
284        Self(self.0.clamp(0.0, max_value.0))
285    }
286}
287
288impl Default for Radians {
289    #[must_use]
290    fn default() -> Self {
291        Self(0.0)
292    }
293}
294
295impl Neg for Radians {
296    type Output = Self;
297
298    /// An implementation of Neg for Radians, i.e. -angle.
299    /// # Examples
300    /// ```
301    /// use angle_sc::Radians;
302    ///
303    /// let angle_45 = Radians(core::f64::consts::FRAC_PI_4);
304    /// let result_m45 = -angle_45;
305    /// assert_eq!(-core::f64::consts::FRAC_PI_4, result_m45.0);
306    /// ```
307    #[must_use]
308    fn neg(self) -> Self {
309        Self(0.0 - self.0)
310    }
311}
312
313impl Add for Radians {
314    type Output = Self;
315
316    /// Add a pair of angles in Radians, wraps around +/-PI.
317    /// Uses the [2Sum](https://en.wikipedia.org/wiki/2Sum) algorithm to reduce
318    /// round-off error.
319    /// # Examples
320    /// ```
321    /// use angle_sc::{Radians, is_within_tolerance};
322    ///
323    /// let angle_120 = Radians(2.0 * core::f64::consts::FRAC_PI_3);
324    /// let result = angle_120 + angle_120;
325    /// assert!(is_within_tolerance(-2.0 * core::f64::consts::FRAC_PI_3, result.0,  4.0 * f64::EPSILON));
326    /// ```
327    #[must_use]
328    fn add(self, other: Self) -> Self::Output {
329        let (s, t) = two_sum(self.0, other.0);
330        Self(if s <= -core::f64::consts::PI {
331            s + core::f64::consts::TAU + t
332        } else if s > core::f64::consts::PI {
333            s - core::f64::consts::TAU + t
334        } else {
335            s
336        })
337    }
338}
339
340impl AddAssign for Radians {
341    fn add_assign(&mut self, other: Self) {
342        *self = *self + other;
343    }
344}
345
346impl Sub for Radians {
347    type Output = Self;
348
349    /// Subtract a pair of angles in Radians,  wraps around +/-PI.
350    /// Uses the [2Sum](https://en.wikipedia.org/wiki/2Sum) algorithm to reduce
351    /// round-off error.
352    /// # Examples
353    /// ```
354    /// use angle_sc::{Radians, is_within_tolerance};
355    ///
356    /// let angle_120 = Radians(2.0 * core::f64::consts::FRAC_PI_3);
357    /// let angle_m120 = -angle_120;
358    /// let result = angle_m120 - angle_120;
359    /// assert!(is_within_tolerance(angle_120.0, result.0,  4.0 * f64::EPSILON));
360    /// ```
361    #[must_use]
362    fn sub(self, other: Self) -> Self::Output {
363        self + -other
364    }
365}
366
367impl SubAssign for Radians {
368    fn sub_assign(&mut self, other: Self) {
369        *self = *self - other;
370    }
371}
372
373/// An angle represented by it's sine and cosine as `UnitNegRanges`.
374#[derive(Clone, Copy, Debug, PartialEq)]
375pub struct Angle {
376    /// The sine of the angle.
377    sin: trig::UnitNegRange,
378    /// The cosine of the angle.
379    cos: trig::UnitNegRange,
380}
381
382/// A default angle: zero degrees or radians.
383impl Default for Angle {
384    /// Implementation of Default for Angle returns Angle(0.0, 1.0),
385    /// i.e. the Angle corresponding to zero degrees or radians.
386    /// # Examples
387    /// ```
388    /// use angle_sc::Angle;
389    ///
390    /// let zero = Angle::default();
391    /// assert_eq!(0.0, zero.sin().0);
392    /// assert_eq!(1.0, zero.cos().0);
393    /// ```
394    #[must_use]
395    fn default() -> Self {
396        Self {
397            sin: trig::UnitNegRange(0.0),
398            cos: trig::UnitNegRange(1.0),
399        }
400    }
401}
402
403impl Validate for Angle {
404    /// Test whether an `Angle` is valid, i.e. both sin and cos are valid
405    /// `UnitNegRange`s and the length of their hypotenuse is approximately 1.0.
406    fn is_valid(&self) -> bool {
407        self.sin.is_valid()
408            && self.cos.is_valid()
409            && is_within_tolerance(1.0, libm::hypot(self.sin.0, self.cos.0), f64::EPSILON)
410    }
411}
412
413impl Angle {
414    /// Construct an Angle from sin and cos values.
415    #[must_use]
416    pub const fn new(sin: trig::UnitNegRange, cos: trig::UnitNegRange) -> Self {
417        Self { sin, cos }
418    }
419
420    /// Construct an Angle from y and x values.
421    /// Normalizes the values.
422    #[must_use]
423    pub fn from_y_x(y: f64, x: f64) -> Self {
424        let length = libm::hypot(y, x);
425
426        if is_small(length, f64::EPSILON) {
427            Self::default()
428        } else {
429            Self::new(
430                trig::UnitNegRange::clamp(y / length),
431                trig::UnitNegRange::clamp(x / length),
432            )
433        }
434    }
435
436    /// The sine of the Angle.
437    #[must_use]
438    pub const fn sin(self) -> trig::UnitNegRange {
439        self.sin
440    }
441
442    /// The cosine of the Angle.
443    #[must_use]
444    pub const fn cos(self) -> trig::UnitNegRange {
445        self.cos
446    }
447
448    /// The tangent of the Angle.
449    ///
450    /// returns the tangent or `None` if `self.cos < SQ_EPSILON`
451    #[must_use]
452    pub fn tan(self) -> Option<f64> {
453        trig::tan(self.sin, self.cos)
454    }
455
456    /// The cosecant of the Angle.
457    ///
458    /// returns the cosecant or `None` if `self.sin < SQ_EPSILON`
459    #[must_use]
460    pub fn csc(self) -> Option<f64> {
461        trig::csc(self.sin)
462    }
463
464    /// The secant of the Angle.
465    ///
466    /// returns the secant or `None` if `self.cos < SQ_EPSILON`
467    #[must_use]
468    pub fn sec(self) -> Option<f64> {
469        trig::sec(self.cos)
470    }
471
472    /// The cotangent of the Angle.
473    ///
474    /// returns the cotangent or `None` if `self.sin < SQ_EPSILON`
475    #[must_use]
476    pub fn cot(self) -> Option<f64> {
477        trig::cot(self.sin, self.cos)
478    }
479
480    /// The absolute value of the angle, i.e. the angle with a positive sine.
481    /// # Examples
482    /// ```
483    /// use angle_sc::{Angle, Degrees};
484    ///
485    /// let angle_m45 = Angle::from(Degrees(-45.0));
486    /// let result_45 = angle_m45.abs();
487    /// assert_eq!(Degrees(45.0), Degrees::from(result_45));
488    /// ```
489    #[must_use]
490    pub fn abs(self) -> Self {
491        Self {
492            sin: self.sin.abs(),
493            cos: self.cos,
494        }
495    }
496
497    /// The opposite angle on the circle, i.e. +/- 180 degrees.
498    /// # Examples
499    /// ```
500    /// use angle_sc::{Angle, Degrees};
501    ///
502    /// let angle_m30 = Angle::from(Degrees(-30.0));
503    /// let result = angle_m30.opposite();
504    /// assert_eq!(Degrees(150.0), Degrees::from(result));
505    /// ```
506    #[must_use]
507    pub fn opposite(self) -> Self {
508        Self {
509            sin: -self.sin,
510            cos: -self.cos,
511        }
512    }
513
514    /// A quarter turn clockwise around the circle, i.e. + 90°.
515    /// # Examples
516    /// ```
517    /// use angle_sc::{Angle, Degrees};
518    ///
519    /// let angle_m30 = Angle::from(Degrees(-30.0));
520    /// let result = angle_m30.quarter_turn_cw();
521    /// assert_eq!(Angle::from(Degrees(60.0)), result);
522    /// ```
523    #[must_use]
524    pub fn quarter_turn_cw(self) -> Self {
525        Self {
526            sin: self.cos,
527            cos: -self.sin,
528        }
529    }
530
531    /// A quarter turn counter-clockwise around the circle, i.e. - 90°.
532    /// # Examples
533    /// ```
534    /// use angle_sc::{Angle, Degrees};
535    ///
536    /// let angle_120 = Angle::from(Degrees(120.0));
537    /// let result = angle_120.quarter_turn_ccw();
538    /// assert_eq!(Angle::from(Degrees(30.0)), result);
539    /// ```
540    #[must_use]
541    pub fn quarter_turn_ccw(self) -> Self {
542        Self {
543            sin: -self.cos,
544            cos: self.sin,
545        }
546    }
547
548    /// Negate the cosine of the Angle.
549    /// I.e. `PI` - `angle.radians()` for positive angles,
550    ///      `angle.radians()` + `PI` for negative angles
551    /// # Examples
552    /// ```
553    /// use angle_sc::{Angle, Degrees};
554    ///
555    /// let angle_45 = Angle::from(Degrees(45.0));
556    /// let result_45 = angle_45.negate_cos();
557    /// assert_eq!(Degrees(135.0), Degrees::from(result_45));
558    /// ```
559    #[must_use]
560    pub fn negate_cos(self) -> Self {
561        Self {
562            sin: self.sin,
563            cos: -self.cos,
564        }
565    }
566
567    /// Double the Angle.
568    /// See: [Double-angle formulae](https://en.wikipedia.org/wiki/List_of_trigonometric_identities#Double-angle_formulae)
569    /// # Examples
570    /// ```
571    /// use angle_sc::{Angle, Degrees};
572    ///
573    /// let angle_30 = Angle::from(Degrees(30.0));
574    /// let result_60 = angle_30.double();
575    ///
576    /// // Note: multiplication is not precise...
577    /// // assert_eq!(Degrees(60.0), Degrees::from(result_60));
578    /// let delta_angle = libm::fabs(60.0 - Degrees::from(result_60).0);
579    /// assert!(delta_angle <= 32.0 * f64::EPSILON);
580    /// ```
581    #[must_use]
582    pub fn double(self) -> Self {
583        Self {
584            sin: trig::UnitNegRange::clamp(2.0 * self.sin.0 * self.cos.0),
585            cos: trig::UnitNegRange::clamp((self.cos.0 - self.sin.0) * (self.cos.0 + self.sin.0)),
586        }
587    }
588
589    /// Half of the Angle.
590    /// See: [Half-angle formulae](https://en.wikipedia.org/wiki/List_of_trigonometric_identities#Half-angle_formulae)
591    /// # Examples
592    /// ```
593    /// use angle_sc::{Angle, Degrees};
594    ///
595    /// let angle_30 = Angle::from(Degrees(30.0));
596    /// let angle_60 = Angle::from(Degrees(60.0));
597    ///
598    /// assert_eq!(angle_30, angle_60.half());
599    /// ```
600    #[must_use]
601    pub fn half(self) -> Self {
602        Self {
603            sin: trig::UnitNegRange(libm::copysign(
604                libm::sqrt(trig::sq_sine_half(self.cos)),
605                self.sin.0,
606            )),
607            cos: trig::UnitNegRange(libm::sqrt(trig::sq_cosine_half(self.cos))),
608        }
609    }
610}
611
612impl Neg for Angle {
613    type Output = Self;
614
615    /// An implementation of Neg for Angle, i.e. -angle.
616    /// Negates the sine of the Angle, does not affect the cosine.
617    /// # Examples
618    /// ```
619    /// use angle_sc::{Angle, Degrees};
620    ///
621    /// let angle_45 = Angle::from(Degrees(45.0));
622    /// let result_m45 = -angle_45;
623    /// assert_eq!(Degrees(-45.0), Degrees::from(result_m45));
624    /// ```
625    #[must_use]
626    fn neg(self) -> Self {
627        Self {
628            sin: -self.sin,
629            cos: self.cos,
630        }
631    }
632}
633
634impl Add for Angle {
635    type Output = Self;
636
637    /// Add two Angles, i.e. a + b
638    /// Uses trigonometric identity functions, see:
639    /// [angle sum and difference identities](https://en.wikipedia.org/wiki/List_of_trigonometric_identities#Angle_sum_and_difference_identities).
640    /// # Examples
641    /// ```
642    /// use angle_sc::{Angle, Degrees};
643    ///
644    /// let angle_30 = Angle::from(Degrees(30.0));
645    /// let angle_60 = Angle::from(Degrees(60.0));
646    /// let result_90 = angle_30 + angle_60;
647    /// assert_eq!(Degrees(90.0), Degrees::from(result_90));
648    /// ```
649    #[must_use]
650    fn add(self, other: Self) -> Self::Output {
651        Self {
652            sin: trig::sine_sum(self.sin, self.cos, other.sin, other.cos),
653            cos: trig::cosine_sum(self.sin, self.cos, other.sin, other.cos),
654        }
655    }
656}
657
658impl AddAssign for Angle {
659    fn add_assign(&mut self, other: Self) {
660        *self = *self + other;
661    }
662}
663
664impl Sub for Angle {
665    type Output = Self;
666
667    /// Subtract two Angles, i.e. a - b
668    /// Uses trigonometric identity functions, see:
669    /// [angle sum and difference identities](https://en.wikipedia.org/wiki/List_of_trigonometric_identities#Angle_sum_and_difference_identities).
670    /// # Examples
671    /// ```
672    /// use angle_sc::{Angle, Degrees, is_within_tolerance};
673    ///
674    /// let angle_30 = Angle::from(Degrees(30.0));
675    /// let angle_60 = Angle::from(Degrees(60.0));
676    /// let result_30 = angle_60 - angle_30;
677    ///
678    /// assert!(is_within_tolerance(Degrees(30.0).0, Degrees::from(result_30).0, 32.0 * f64::EPSILON));
679    /// ```
680    #[must_use]
681    fn sub(self, other: Self) -> Self::Output {
682        Self {
683            sin: trig::sine_diff(self.sin, self.cos, other.sin, other.cos),
684            cos: trig::cosine_diff(self.sin, self.cos, other.sin, other.cos),
685        }
686    }
687}
688
689impl SubAssign for Angle {
690    fn sub_assign(&mut self, other: Self) {
691        *self = *self - other;
692    }
693}
694
695impl PartialOrd for Angle {
696    /// Compare two Angles, i.e. a < b.
697    /// It compares whether an `Angle` is clockwise of the other `Angle` on the
698    /// unit circle.
699    ///
700    /// # Examples
701    /// ```
702    /// use angle_sc::{Angle, Degrees};
703    /// let degrees_120 = Angle::from(Degrees(120.0));
704    /// let degrees_m120 = -degrees_120;
705    /// assert!(degrees_120 < degrees_m120);
706    /// ```
707    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
708        let delta = *other - *self;
709        trig::UnitNegRange(0.0).partial_cmp(&delta.sin)
710    }
711}
712
713impl From<Degrees> for Angle {
714    /// Construct an `Angle` from an angle in Degrees.
715    ///
716    /// Examples:
717    /// ```
718    /// use angle_sc::{Angle, Degrees, is_within_tolerance, trig};
719    ///
720    /// let angle = Angle::from(Degrees(60.0));
721    /// assert_eq!(trig::COS_30_DEGREES, angle.sin().0);
722    /// assert_eq!(0.5, angle.cos().0);
723    /// assert_eq!(60.0, Degrees::from(angle).0);
724    /// ```
725    #[must_use]
726    fn from(a: Degrees) -> Self {
727        let (sin, cos) = trig::sincosd(a);
728        Self { sin, cos }
729    }
730}
731
732impl From<(Degrees, Degrees)> for Angle {
733    /// Construct an `Angle` from the difference of a pair angles in Degrees:
734    /// a - b
735    ///
736    /// Examples:
737    /// ```
738    /// use angle_sc::{Angle, Degrees, trig};
739    ///
740    /// // Difference of Degrees(-155.0) - Degrees(175.0)
741    /// let angle = Angle::from((Degrees(-155.0), Degrees(175.0)));
742    /// assert_eq!(0.5, angle.sin().0);
743    /// assert_eq!(trig::COS_30_DEGREES, angle.cos().0);
744    /// assert_eq!(30.0, Degrees::from(angle).0);
745    /// ```
746    #[must_use]
747    fn from(params: (Degrees, Degrees)) -> Self {
748        let (sin, cos) = trig::sincosd_diff(params.0, params.1);
749        Self { sin, cos }
750    }
751}
752
753impl From<Radians> for Angle {
754    /// Construct an `Angle` from an angle in Radians.
755    ///
756    /// Examples:
757    /// ```
758    /// use angle_sc::{Angle, Radians, trig};
759    ///
760    /// let angle = Angle::from(Radians(-core::f64::consts::FRAC_PI_6));
761    /// assert_eq!(-0.5, angle.sin().0);
762    /// assert_eq!(trig::COS_30_DEGREES, angle.cos().0);
763    /// assert_eq!(-core::f64::consts::FRAC_PI_6, Radians::from(angle).0);
764    /// ```
765    #[must_use]
766    fn from(a: Radians) -> Self {
767        let (sin, cos) = trig::sincos(a);
768        Self { sin, cos }
769    }
770}
771
772impl From<(Radians, Radians)> for Angle {
773    /// Construct an Angle from the difference of a pair angles in Radians:
774    /// a - b
775    ///
776    /// Examples:
777    /// ```
778    /// use angle_sc::{Angle, Radians, trig};
779    ///
780    /// // 6*π - π/3 radians round trip
781    /// let angle = Angle::from((
782    ///     Radians(3.0 * core::f64::consts::TAU),
783    ///     Radians(core::f64::consts::FRAC_PI_3),
784    /// ));
785    /// assert_eq!(-core::f64::consts::FRAC_PI_3, Radians::from(angle).0);
786    /// ```
787    #[must_use]
788    fn from(params: (Radians, Radians)) -> Self {
789        let (sin, cos) = trig::sincos_diff(params.0, params.1);
790        Self { sin, cos }
791    }
792}
793
794impl From<Angle> for Radians {
795    /// Convert an Angle to Radians.
796    #[must_use]
797    fn from(a: Angle) -> Self {
798        trig::arctan2(a.sin, a.cos)
799    }
800}
801
802impl From<Angle> for Degrees {
803    /// Convert an Angle to Degrees.
804    #[must_use]
805    fn from(a: Angle) -> Self {
806        trig::arctan2d(a.sin, a.cos)
807    }
808}
809
810impl Serialize for Angle {
811    /// Serialize an Angle to an value in Degrees.
812    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
813    where
814        S: Serializer,
815    {
816        serializer.serialize_newtype_struct("Degrees", &Degrees::from(*self))
817    }
818}
819
820impl<'de> Deserialize<'de> for Angle {
821    /// Deserialize an value in Degrees to an Angle.
822    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
823    where
824        D: Deserializer<'de>,
825    {
826        Ok(Self::from(Degrees::deserialize(deserializer)?))
827    }
828}
829
830//////////////////////////////////////////////////////////////////////////////
831
832/// Calculates floating-point sum and error.
833/// The [2Sum](https://en.wikipedia.org/wiki/2Sum) algorithm.
834///
835/// * `a`, `b` the floating-point numbers to add.
836///
837/// returns (a + b) and the floating-point error: $t = a + b - (a \oplus b)$
838/// so: $a+b=s+t$.
839#[must_use]
840pub fn two_sum<T>(a: T, b: T) -> (T, T)
841where
842    T: Copy + Add<Output = T> + Sub<Output = T>,
843{
844    let s = a + b;
845    let a_prime = s - b;
846    let b_prime = s - a_prime;
847    let delta_a = a - a_prime;
848    let delta_b = b - b_prime;
849    let t = delta_a + delta_b;
850    (s, t)
851}
852
853/// Return the minimum of a or b.
854#[must_use]
855pub fn min<T>(a: T, b: T) -> T
856where
857    T: PartialOrd + Copy,
858{
859    if b < a { b } else { a }
860}
861
862/// Return the maximum of a or b.
863#[must_use]
864pub fn max<T>(a: T, b: T) -> T
865where
866    T: PartialOrd + Copy,
867{
868    if b < a { a } else { b }
869}
870
871/// The Validate trait.
872pub trait Validate {
873    /// return true if the type is valid, false otherwise.
874    fn is_valid(&self) -> bool;
875}
876
877/// Check whether value <= tolerance.
878#[must_use]
879pub fn is_small<T>(value: T, tolerance: T) -> bool
880where
881    T: PartialOrd + Copy,
882{
883    value <= tolerance
884}
885
886/// Check whether a value is within tolerance of a reference value.
887/// * `reference` the required value
888/// * `value` the value to test
889/// * `tolerance` the permitted tolerance
890///
891/// return true if abs(reference - value) is <= tolerance
892#[must_use]
893pub fn is_within_tolerance<T>(reference: T, value: T, tolerance: T) -> bool
894where
895    T: PartialOrd + Copy + Sub<Output = T>,
896{
897    let delta = max(reference, value) - min(reference, value);
898    is_small(delta, tolerance)
899}
900
901#[cfg(test)]
902mod tests {
903    use super::*;
904
905    #[test]
906    fn test_degrees_traits() {
907        let zero = Degrees::default();
908        assert_eq!(Degrees(0.0), zero);
909        let one = Degrees(1.0);
910        let mut one_clone = one.clone();
911        assert!(one_clone == one);
912        let two = Degrees(2.0);
913        let m_one = Degrees(-1.0);
914        assert_eq!(m_one, -one);
915
916        assert_eq!(one, m_one.abs());
917
918        assert_eq!(m_one, one - two);
919        one_clone -= two;
920        assert_eq!(m_one, one_clone);
921
922        assert_eq!(one, m_one + two);
923        one_clone += two;
924        assert_eq!(one, one_clone);
925
926        let d_120 = Degrees(120.0);
927        let d_m120 = Degrees(-120.0);
928        assert_eq!(d_120, d_m120.abs());
929
930        assert_eq!(Degrees(30.0), Degrees(-155.0) - Degrees(175.0));
931
932        assert_eq!(d_m120, d_120 + d_120);
933        assert_eq!(d_120, d_m120 + d_m120);
934        assert_eq!(d_120, d_m120 - d_120);
935
936        assert_eq!(Degrees(-60.0), d_120.opposite());
937        assert_eq!(Degrees(60.0), d_m120.opposite());
938
939        let serialized = serde_json::to_string(&one).unwrap();
940        let deserialized: Degrees = serde_json::from_str(&serialized).unwrap();
941        assert_eq!(one, deserialized);
942
943        let bad_text = "junk";
944        let _serde_error = serde_json::from_str::<Degrees>(&bad_text).unwrap_err();
945
946        print!("Degrees: {:?}", one);
947    }
948
949    #[test]
950    fn test_radians_traits() {
951        let zero = Radians::default();
952        assert_eq!(Radians(0.0), zero);
953        let one = Radians(1.0);
954        let mut one_clone = one.clone();
955        assert!(one_clone == one);
956        let two = Radians(2.0);
957        let m_two = -two;
958        assert!(one < two);
959        let m_one = Radians(-1.0);
960        assert_eq!(m_one, -one);
961
962        assert_eq!(one, m_one.abs());
963
964        assert_eq!(m_one, one - two);
965        one_clone -= two;
966        assert_eq!(m_one, one_clone);
967
968        assert_eq!(one, m_one + two);
969        one_clone += two;
970        assert_eq!(one, one_clone);
971
972        let result_1 = m_two - two;
973        assert_eq!(core::f64::consts::TAU - 4.0, result_1.0);
974        assert_eq!(core::f64::consts::PI - 4.0, result_1.opposite().0);
975
976        let result_2 = two - m_two;
977        assert_eq!(4.0 - core::f64::consts::TAU, result_2.0);
978        assert_eq!(4.0 - core::f64::consts::PI, result_2.opposite().0);
979
980        let value = Radians(-f64::EPSILON);
981        assert_eq!(Radians(0.0), value.clamp(Radians(1.0)));
982        let value = Radians(0.0);
983        assert_eq!(Radians(0.0), value.clamp(Radians(1.0)));
984        let value = Radians(1.0);
985        assert_eq!(Radians(1.0), value.clamp(Radians(1.0)));
986        let value = Radians(1.0 + f64::EPSILON);
987        assert_eq!(Radians(1.0), value.clamp(Radians(1.0)));
988
989        print!("Radians: {:?}", one);
990    }
991
992    #[test]
993    fn test_angle_traits() {
994        let zero = Angle::default();
995        assert_eq!(0.0, zero.sin().0);
996        assert_eq!(1.0, zero.cos().0);
997        assert_eq!(0.0, zero.tan().unwrap());
998        assert!(zero.csc().is_none());
999        assert_eq!(1.0, zero.sec().unwrap());
1000        assert!(zero.cot().is_none());
1001        assert!(zero.is_valid());
1002
1003        let zero_clone = zero.clone();
1004        assert_eq!(zero, zero_clone);
1005
1006        let one = Angle::from_y_x(1.0, 0.0);
1007        assert_eq!(1.0, one.sin().0);
1008        assert_eq!(0.0, one.cos().0);
1009        assert!(one.tan().is_none());
1010        assert_eq!(1.0, one.csc().unwrap());
1011        assert!(one.sec().is_none());
1012        assert_eq!(0.0, one.cot().unwrap());
1013        assert!(one.is_valid());
1014
1015        let angle_m45 = Angle::from_y_x(-f64::EPSILON, f64::EPSILON);
1016        assert!(is_within_tolerance(
1017            -core::f64::consts::FRAC_1_SQRT_2,
1018            angle_m45.sin().0,
1019            f64::EPSILON
1020        ));
1021        assert!(is_within_tolerance(
1022            core::f64::consts::FRAC_1_SQRT_2,
1023            angle_m45.cos().0,
1024            f64::EPSILON
1025        ));
1026
1027        assert!(angle_m45 < zero);
1028
1029        let serialized = serde_json::to_string(&zero).unwrap();
1030        let deserialized: Angle = serde_json::from_str(&serialized).unwrap();
1031        assert_eq!(zero, deserialized);
1032
1033        let bad_text = "junk";
1034        let _serde_error = serde_json::from_str::<Angle>(&bad_text).unwrap_err();
1035
1036        print!("Angle: {:?}", angle_m45);
1037    }
1038    #[test]
1039    fn test_angle_conversion() {
1040        let zero = Angle::default();
1041
1042        let too_small = Angle::from_y_x(-f64::EPSILON / 2.0, f64::EPSILON / 2.0);
1043        assert!(too_small.is_valid());
1044        assert_eq!(zero, too_small);
1045
1046        let small = Angle::from(-trig::MAX_COS_ANGLE_IS_ONE);
1047        assert!(small.is_valid());
1048        assert_eq!(-trig::MAX_COS_ANGLE_IS_ONE.0, small.sin().0);
1049        assert_eq!(1.0, small.cos().0);
1050        assert_eq!(-trig::MAX_COS_ANGLE_IS_ONE.0, Radians::from(small).0);
1051
1052        let angle_30 = Angle::from((
1053            Radians(core::f64::consts::FRAC_PI_3),
1054            Radians(core::f64::consts::FRAC_PI_6),
1055        ));
1056        assert!(angle_30.is_valid());
1057        assert_eq!(0.5, angle_30.sin().0);
1058        assert_eq!(libm::sqrt(3.0) / 2.0, angle_30.cos().0);
1059        assert_eq!(30.0, Degrees::from(angle_30).0);
1060        assert_eq!(core::f64::consts::FRAC_PI_6, Radians::from(angle_30).0);
1061
1062        let angle_45 = Angle::from(Radians(core::f64::consts::FRAC_PI_4));
1063        assert!(angle_45.is_valid());
1064        assert_eq!(core::f64::consts::FRAC_1_SQRT_2, angle_45.sin().0);
1065        assert_eq!(core::f64::consts::FRAC_1_SQRT_2, angle_45.cos().0);
1066        assert_eq!(45.0, Degrees::from(angle_45).0);
1067        assert_eq!(core::f64::consts::FRAC_PI_4, Radians::from(angle_45).0);
1068
1069        let angle_m45 = Angle::from(Degrees(-45.0));
1070        assert!(angle_m45.is_valid());
1071        assert_eq!(-core::f64::consts::FRAC_1_SQRT_2, angle_m45.sin().0);
1072        assert_eq!(core::f64::consts::FRAC_1_SQRT_2, angle_m45.cos().0);
1073        assert_eq!(-45.0, Degrees::from(angle_m45).0);
1074        assert_eq!(-core::f64::consts::FRAC_PI_4, Radians::from(angle_m45).0);
1075
1076        let angle_60 = Angle::from((Degrees(-140.0), Degrees(160.0)));
1077        assert!(angle_60.is_valid());
1078        assert_eq!(libm::sqrt(3.0) / 2.0, angle_60.sin().0);
1079        assert_eq!(0.5, angle_60.cos().0);
1080        assert_eq!(60.0, Degrees::from(angle_60).0);
1081        // Fails because PI is irrational
1082        // assert_eq!(core::f64::consts::FRAC_PI_3, Radians::from(angle_60).0);
1083        assert!(is_within_tolerance(
1084            core::f64::consts::FRAC_PI_3,
1085            Radians::from(angle_60).0,
1086            f64::EPSILON
1087        ));
1088
1089        let angle_30 = Angle::from((Degrees(-155.0), Degrees(175.0)));
1090        // assert!(angle_30.is_valid());
1091        assert_eq!(0.5, angle_30.sin().0);
1092        assert_eq!(libm::sqrt(3.0) / 2.0, angle_30.cos().0);
1093        assert_eq!(30.0, Degrees::from(angle_30).0);
1094        assert_eq!(core::f64::consts::FRAC_PI_6, Radians::from(angle_30).0);
1095
1096        let angle_120 = Angle::from(Degrees(120.0));
1097        assert!(angle_120.is_valid());
1098        assert_eq!(libm::sqrt(3.0) / 2.0, angle_120.sin().0);
1099        assert_eq!(-0.5, angle_120.cos().0);
1100        assert_eq!(120.0, Degrees::from(angle_120).0);
1101        assert_eq!(
1102            2.0 * core::f64::consts::FRAC_PI_3,
1103            Radians::from(angle_120).0
1104        );
1105
1106        let angle_m120 = Angle::from(Degrees(-120.0));
1107        assert!(angle_m120.is_valid());
1108        assert_eq!(-libm::sqrt(3.0) / 2.0, angle_m120.sin().0);
1109        assert_eq!(-0.5, angle_m120.cos().0);
1110        assert_eq!(-120.0, Degrees::from(angle_m120).0);
1111        assert_eq!(
1112            -2.0 * core::f64::consts::FRAC_PI_3,
1113            Radians::from(angle_m120).0
1114        );
1115
1116        let angle_m140 = Angle::from(Degrees(-140.0));
1117        assert!(angle_m140.is_valid());
1118        assert!(is_within_tolerance(
1119            -0.6427876096865393,
1120            angle_m140.sin().0,
1121            f64::EPSILON
1122        ));
1123        assert!(is_within_tolerance(
1124            -0.7660444431189781,
1125            angle_m140.cos().0,
1126            f64::EPSILON
1127        ));
1128        assert_eq!(-140.0, Degrees::from(angle_m140).0);
1129
1130        let angle_180 = Angle::from(Degrees(180.0));
1131        assert!(angle_180.is_valid());
1132        assert_eq!(0.0, angle_180.sin().0);
1133        assert_eq!(-1.0, angle_180.cos().0);
1134        assert_eq!(180.0, Degrees::from(angle_180).0);
1135        assert_eq!(core::f64::consts::PI, Radians::from(angle_180).0);
1136    }
1137
1138    #[test]
1139    fn test_angle_maths() {
1140        let degrees_30 = Angle::from(Degrees(30.0));
1141        let degrees_60 = Angle::from(Degrees(60.0));
1142        let degrees_120 = Angle::from(Degrees(120.0));
1143        let degrees_m120 = -degrees_120;
1144
1145        assert!(degrees_120 < degrees_m120);
1146        assert_eq!(degrees_120, degrees_m120.abs());
1147        assert_eq!(degrees_60, degrees_m120.opposite());
1148        assert_eq!(degrees_120, degrees_30.quarter_turn_cw());
1149        assert_eq!(degrees_30, degrees_120.quarter_turn_ccw());
1150        assert_eq!(degrees_60, degrees_120.negate_cos());
1151
1152        let result = degrees_m120 - degrees_120;
1153        assert_eq!(Degrees(120.0).0, Degrees::from(result).0);
1154
1155        let mut result = degrees_m120;
1156        result -= degrees_120;
1157        assert_eq!(Degrees(120.0).0, Degrees::from(result).0);
1158
1159        let result = degrees_120 + degrees_120;
1160        assert_eq!(Degrees(-120.0).0, Degrees::from(result).0);
1161
1162        let mut result = degrees_120;
1163        result += degrees_120;
1164        assert_eq!(Degrees(-120.0).0, Degrees::from(result).0);
1165
1166        let result = degrees_60.double();
1167        assert_eq!(Degrees(120.0).0, Degrees::from(result).0);
1168
1169        let result = degrees_120.double();
1170        assert_eq!(Degrees(-120.0).0, Degrees::from(result).0);
1171
1172        assert_eq!(-degrees_60, degrees_m120.half());
1173    }
1174
1175    #[test]
1176    fn test_two_sum() {
1177        let result = two_sum(1.0, 1.0);
1178        assert_eq!(2.0, result.0);
1179        assert_eq!(0.0, result.1);
1180
1181        let result = two_sum(1.0, 1e-53);
1182        assert_eq!(1.0, result.0);
1183        assert_eq!(1e-53, result.1);
1184
1185        let result = two_sum(1.0, -1e-53);
1186        assert_eq!(1.0, result.0);
1187        assert_eq!(-1e-53, result.1);
1188    }
1189
1190    #[test]
1191    fn test_min_and_max() {
1192        // min -ve and +ve
1193        assert_eq!(min(-1.0 + f64::EPSILON, -1.0), -1.0);
1194        assert_eq!(min(1.0, 1.0 + f64::EPSILON), 1.0);
1195        // max -ve and +ve
1196        assert_eq!(max(-1.0, -1.0 - f64::EPSILON), -1.0);
1197        assert_eq!(max(1.0 - f64::EPSILON, 1.0), 1.0);
1198    }
1199
1200    #[test]
1201    fn test_is_within_tolerance() {
1202        // below minimum tolerance
1203        assert_eq!(
1204            false,
1205            is_within_tolerance(1.0 - 2.0 * f64::EPSILON, 1.0, f64::EPSILON)
1206        );
1207
1208        // within minimum tolerance
1209        assert!(is_within_tolerance(1.0 - f64::EPSILON, 1.0, f64::EPSILON));
1210
1211        // within maximum tolerance
1212        assert!(is_within_tolerance(1.0 + f64::EPSILON, 1.0, f64::EPSILON));
1213
1214        // above maximum tolerance
1215        assert_eq!(
1216            false,
1217            is_within_tolerance(1.0 + 2.0 * f64::EPSILON, 1.0, f64::EPSILON)
1218        );
1219    }
1220}