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)]
143#[repr(transparent)]
144pub struct Degrees(pub f64);
145
146impl Degrees {
147    /// The absolute value of the angle.
148    #[must_use]
149    pub fn abs(self) -> Self {
150        Self(libm::fabs(self.0))
151    }
152
153    /// The opposite angle on the circle, i.e. +/- 180 degrees.
154    #[must_use]
155    pub fn opposite(self) -> Self {
156        Self(if 0.0 < self.0 {
157            self.0 - 180.0
158        } else {
159            self.0 + 180.0
160        })
161    }
162}
163
164impl Default for Degrees {
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    fn neg(self) -> Self {
183        Self(0.0 - self.0)
184    }
185}
186
187impl Add for Degrees {
188    type Output = Self;
189
190    /// Add a pair of angles in Degrees, wraps around +/-180.
191    /// Uses the [2Sum](https://en.wikipedia.org/wiki/2Sum) algorithm to reduce
192    /// round-off error.
193    /// # Examples
194    /// ```
195    /// use angle_sc::{Degrees};
196    ///
197    /// let angle_120 = Degrees(120.0);
198    /// let result = angle_120 + angle_120;
199    /// assert_eq!(-angle_120, result);
200    /// ```
201    fn add(self, other: Self) -> Self::Output {
202        let (s, t) = two_sum(self.0, other.0);
203        Self(if s <= -180.0 {
204            s + 360.0 + t
205        } else if s > 180.0 {
206            s - 360.0 + t
207        } else {
208            s
209        })
210    }
211}
212
213impl AddAssign for Degrees {
214    fn add_assign(&mut self, other: Self) {
215        *self = *self + other;
216    }
217}
218
219impl Sub for Degrees {
220    type Output = Self;
221
222    /// Subtract a pair of angles in Degrees, wraps around +/-180.
223    /// Uses the [2Sum](https://en.wikipedia.org/wiki/2Sum) algorithm to reduce
224    /// round-off error.
225    /// # Examples
226    /// ```
227    /// use angle_sc::{Degrees};
228    ///
229    /// let angle_120 = Degrees(120.0);
230    /// let result = -angle_120 - angle_120;
231    /// assert_eq!(angle_120, result);
232    /// ```
233    fn sub(self, other: Self) -> Self::Output {
234        self + -other
235    }
236}
237
238impl SubAssign for Degrees {
239    fn sub_assign(&mut self, other: Self) {
240        *self = *self - other;
241    }
242}
243
244/// The Radians newtype an f64.
245#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
246#[repr(transparent)]
247pub struct Radians(pub f64);
248
249impl Radians {
250    /// The absolute value of the angle.
251    #[must_use]
252    pub fn abs(self) -> Self {
253        Self(libm::fabs(self.0))
254    }
255
256    /// The opposite angle on the circle, i.e. +/- PI.
257    #[must_use]
258    pub fn opposite(self) -> Self {
259        Self(if 0.0 < self.0 {
260            self.0 - core::f64::consts::PI
261        } else {
262            self.0 + core::f64::consts::PI
263        })
264    }
265
266    /// Clamp value into the range: `0.0..=max_value`.
267    /// # Examples
268    /// ```
269    /// use angle_sc::Radians;
270    ///
271    /// let value = Radians(-f64::EPSILON);
272    /// assert_eq!(Radians(0.0), value.clamp(Radians(1.0)));
273    /// let value = Radians(0.0);
274    /// assert_eq!(Radians(0.0), value.clamp(Radians(1.0)));
275    /// let value = Radians(1.0);
276    /// assert_eq!(Radians(1.0), value.clamp(Radians(1.0)));
277    /// let value = Radians(1.0 + f64::EPSILON);
278    /// assert_eq!(Radians(1.0), value.clamp(Radians(1.0)));
279    /// ```
280    #[must_use]
281    pub const fn clamp(self, max_value: Self) -> Self {
282        Self(self.0.clamp(0.0, max_value.0))
283    }
284}
285
286impl Default for Radians {
287    fn default() -> Self {
288        Self(0.0)
289    }
290}
291
292impl Neg for Radians {
293    type Output = Self;
294
295    /// An implementation of Neg for Radians, i.e. -angle.
296    /// # Examples
297    /// ```
298    /// use angle_sc::Radians;
299    ///
300    /// let angle_45 = Radians(core::f64::consts::FRAC_PI_4);
301    /// let result_m45 = -angle_45;
302    /// assert_eq!(-core::f64::consts::FRAC_PI_4, result_m45.0);
303    /// ```
304    fn neg(self) -> Self {
305        Self(0.0 - self.0)
306    }
307}
308
309impl Add for Radians {
310    type Output = Self;
311
312    /// Add a pair of angles in Radians, wraps around +/-PI.
313    /// Uses the [2Sum](https://en.wikipedia.org/wiki/2Sum) algorithm to reduce
314    /// round-off error.
315    /// # Examples
316    /// ```
317    /// use angle_sc::{Radians, is_within_tolerance};
318    ///
319    /// let angle_120 = Radians(2.0 * core::f64::consts::FRAC_PI_3);
320    /// let result = angle_120 + angle_120;
321    /// assert!(is_within_tolerance(-2.0 * core::f64::consts::FRAC_PI_3, result.0,  4.0 * f64::EPSILON));
322    /// ```
323    fn add(self, other: Self) -> Self::Output {
324        let (s, t) = two_sum(self.0, other.0);
325        Self(if s <= -core::f64::consts::PI {
326            s + core::f64::consts::TAU + t
327        } else if s > core::f64::consts::PI {
328            s - core::f64::consts::TAU + t
329        } else {
330            s
331        })
332    }
333}
334
335impl AddAssign for Radians {
336    fn add_assign(&mut self, other: Self) {
337        *self = *self + other;
338    }
339}
340
341impl Sub for Radians {
342    type Output = Self;
343
344    /// Subtract a pair of angles in Radians,  wraps around +/-PI.
345    /// Uses the [2Sum](https://en.wikipedia.org/wiki/2Sum) algorithm to reduce
346    /// round-off error.
347    /// # Examples
348    /// ```
349    /// use angle_sc::{Radians, is_within_tolerance};
350    ///
351    /// let angle_120 = Radians(2.0 * core::f64::consts::FRAC_PI_3);
352    /// let angle_m120 = -angle_120;
353    /// let result = angle_m120 - angle_120;
354    /// assert!(is_within_tolerance(angle_120.0, result.0,  4.0 * f64::EPSILON));
355    /// ```
356    fn sub(self, other: Self) -> Self::Output {
357        self + -other
358    }
359}
360
361impl SubAssign for Radians {
362    fn sub_assign(&mut self, other: Self) {
363        *self = *self - other;
364    }
365}
366
367/// An angle represented by it's sine and cosine as `UnitNegRanges`.
368#[derive(Clone, Copy, Debug, PartialEq)]
369pub struct Angle {
370    /// The sine of the angle.
371    sin: trig::UnitNegRange,
372    /// The cosine of the angle.
373    cos: trig::UnitNegRange,
374}
375
376/// A default angle: zero degrees or radians.
377impl Default for Angle {
378    /// Implementation of Default for Angle returns Angle(0.0, 1.0),
379    /// i.e. the Angle corresponding to zero degrees or radians.
380    /// # Examples
381    /// ```
382    /// use angle_sc::Angle;
383    ///
384    /// let zero = Angle::default();
385    /// assert_eq!(0.0, zero.sin().0);
386    /// assert_eq!(1.0, zero.cos().0);
387    /// ```
388    fn default() -> Self {
389        Self {
390            sin: trig::UnitNegRange(0.0),
391            cos: trig::UnitNegRange(1.0),
392        }
393    }
394}
395
396impl Validate for Angle {
397    /// Test whether an `Angle` is valid, i.e. both sin and cos are valid
398    /// `UnitNegRange`s and the length of their hypotenuse is approximately 1.0.
399    fn is_valid(&self) -> bool {
400        self.sin.is_valid()
401            && self.cos.is_valid()
402            && is_within_tolerance(1.0, libm::hypot(self.sin.0, self.cos.0), f64::EPSILON)
403    }
404}
405
406impl Angle {
407    /// Construct an Angle from sin and cos values.
408    #[must_use]
409    pub const fn new(sin: trig::UnitNegRange, cos: trig::UnitNegRange) -> Self {
410        Self { sin, cos }
411    }
412
413    /// Construct an Angle from y and x values.
414    /// Normalizes the values.
415    #[must_use]
416    pub fn from_y_x(y: f64, x: f64) -> Self {
417        let length = libm::hypot(y, x);
418
419        if is_small(length, f64::EPSILON) {
420            Self::default()
421        } else {
422            Self::new(
423                trig::UnitNegRange::clamp(y / length),
424                trig::UnitNegRange::clamp(x / length),
425            )
426        }
427    }
428
429    /// The sine of the Angle.
430    #[must_use]
431    pub const fn sin(self) -> trig::UnitNegRange {
432        self.sin
433    }
434
435    /// The cosine of the Angle.
436    #[must_use]
437    pub const fn cos(self) -> trig::UnitNegRange {
438        self.cos
439    }
440
441    /// The tangent of the Angle.
442    ///
443    /// returns the tangent or `None` if `self.cos < SQ_EPSILON`
444    #[must_use]
445    pub fn tan(self) -> Option<f64> {
446        trig::tan(self.sin, self.cos)
447    }
448
449    /// The cosecant of the Angle.
450    ///
451    /// returns the cosecant or `None` if `self.sin < SQ_EPSILON`
452    #[must_use]
453    pub fn csc(self) -> Option<f64> {
454        trig::csc(self.sin)
455    }
456
457    /// The secant of the Angle.
458    ///
459    /// returns the secant or `None` if `self.cos < SQ_EPSILON`
460    #[must_use]
461    pub fn sec(self) -> Option<f64> {
462        trig::sec(self.cos)
463    }
464
465    /// The cotangent of the Angle.
466    ///
467    /// returns the cotangent or `None` if `self.sin < SQ_EPSILON`
468    #[must_use]
469    pub fn cot(self) -> Option<f64> {
470        trig::cot(self.sin, self.cos)
471    }
472
473    /// The absolute value of the angle, i.e. the angle with a positive sine.
474    /// # Examples
475    /// ```
476    /// use angle_sc::{Angle, Degrees};
477    ///
478    /// let angle_m45 = Angle::from(Degrees(-45.0));
479    /// let result_45 = angle_m45.abs();
480    /// assert_eq!(Degrees(45.0), Degrees::from(result_45));
481    /// ```
482    #[must_use]
483    pub fn abs(self) -> Self {
484        Self {
485            sin: self.sin.abs(),
486            cos: self.cos,
487        }
488    }
489
490    /// The opposite angle on the circle, i.e. +/- 180 degrees.
491    /// # Examples
492    /// ```
493    /// use angle_sc::{Angle, Degrees};
494    ///
495    /// let angle_m30 = Angle::from(Degrees(-30.0));
496    /// let result = angle_m30.opposite();
497    /// assert_eq!(Degrees(150.0), Degrees::from(result));
498    /// ```
499    #[must_use]
500    pub fn opposite(self) -> Self {
501        Self {
502            sin: -self.sin,
503            cos: -self.cos,
504        }
505    }
506
507    /// A quarter turn clockwise around the circle, i.e. + 90°.
508    /// # Examples
509    /// ```
510    /// use angle_sc::{Angle, Degrees};
511    ///
512    /// let angle_m30 = Angle::from(Degrees(-30.0));
513    /// let result = angle_m30.quarter_turn_cw();
514    /// assert_eq!(Angle::from(Degrees(60.0)), result);
515    /// ```
516    #[must_use]
517    pub fn quarter_turn_cw(self) -> Self {
518        Self {
519            sin: self.cos,
520            cos: -self.sin,
521        }
522    }
523
524    /// A quarter turn counter-clockwise around the circle, i.e. - 90°.
525    /// # Examples
526    /// ```
527    /// use angle_sc::{Angle, Degrees};
528    ///
529    /// let angle_120 = Angle::from(Degrees(120.0));
530    /// let result = angle_120.quarter_turn_ccw();
531    /// assert_eq!(Angle::from(Degrees(30.0)), result);
532    /// ```
533    #[must_use]
534    pub fn quarter_turn_ccw(self) -> Self {
535        Self {
536            sin: -self.cos,
537            cos: self.sin,
538        }
539    }
540
541    /// Negate the cosine of the Angle.
542    /// I.e. `PI` - `angle.radians()` for positive angles,
543    ///      `angle.radians()` + `PI` for negative angles
544    /// # Examples
545    /// ```
546    /// use angle_sc::{Angle, Degrees};
547    ///
548    /// let angle_45 = Angle::from(Degrees(45.0));
549    /// let result_45 = angle_45.negate_cos();
550    /// assert_eq!(Degrees(135.0), Degrees::from(result_45));
551    /// ```
552    #[must_use]
553    pub fn negate_cos(self) -> Self {
554        Self {
555            sin: self.sin,
556            cos: -self.cos,
557        }
558    }
559
560    /// Double the Angle.
561    /// See: [Double-angle formulae](https://en.wikipedia.org/wiki/List_of_trigonometric_identities#Double-angle_formulae)
562    /// # Examples
563    /// ```
564    /// use angle_sc::{Angle, Degrees};
565    ///
566    /// let angle_30 = Angle::from(Degrees(30.0));
567    /// let result_60 = angle_30.double();
568    ///
569    /// // Note: multiplication is not precise...
570    /// // assert_eq!(Degrees(60.0), Degrees::from(result_60));
571    /// let delta_angle = libm::fabs(60.0 - Degrees::from(result_60).0);
572    /// assert!(delta_angle <= 32.0 * f64::EPSILON);
573    /// ```
574    #[must_use]
575    pub fn double(self) -> Self {
576        Self {
577            sin: trig::UnitNegRange::clamp(2.0 * self.sin.0 * self.cos.0),
578            cos: trig::sq_a_minus_sq_b(self.cos, self.sin),
579        }
580    }
581
582    /// Half of the Angle.
583    /// See: [Half-angle formulae](https://en.wikipedia.org/wiki/List_of_trigonometric_identities#Half-angle_formulae)
584    /// # Examples
585    /// ```
586    /// use angle_sc::{Angle, Degrees};
587    ///
588    /// let angle_30 = Angle::from(Degrees(30.0));
589    /// let angle_60 = Angle::from(Degrees(60.0));
590    ///
591    /// assert_eq!(angle_30, angle_60.half());
592    /// ```
593    #[must_use]
594    pub fn half(self) -> Self {
595        Self {
596            sin: trig::UnitNegRange(libm::copysign(
597                libm::sqrt(trig::sq_sine_half(self.cos)),
598                self.sin.0,
599            )),
600            cos: trig::UnitNegRange(libm::sqrt(trig::sq_cosine_half(self.cos))),
601        }
602    }
603}
604
605impl Neg for Angle {
606    type Output = Self;
607
608    /// An implementation of Neg for Angle, i.e. -angle.
609    /// Negates the sine of the Angle, does not affect the cosine.
610    /// # Examples
611    /// ```
612    /// use angle_sc::{Angle, Degrees};
613    ///
614    /// let angle_45 = Angle::from(Degrees(45.0));
615    /// let result_m45 = -angle_45;
616    /// assert_eq!(Degrees(-45.0), Degrees::from(result_m45));
617    /// ```
618    fn neg(self) -> Self {
619        Self {
620            sin: -self.sin,
621            cos: self.cos,
622        }
623    }
624}
625
626impl Add for Angle {
627    type Output = Self;
628
629    /// Add two Angles, i.e. a + b
630    /// Uses trigonometric identity functions, see:
631    /// [angle sum and difference identities](https://en.wikipedia.org/wiki/List_of_trigonometric_identities#Angle_sum_and_difference_identities).
632    /// # Examples
633    /// ```
634    /// use angle_sc::{Angle, Degrees};
635    ///
636    /// let angle_30 = Angle::from(Degrees(30.0));
637    /// let angle_60 = Angle::from(Degrees(60.0));
638    /// let result_90 = angle_30 + angle_60;
639    /// assert_eq!(Degrees(90.0), Degrees::from(result_90));
640    /// ```
641    fn add(self, other: Self) -> Self::Output {
642        Self {
643            sin: trig::sine_sum(self.sin, self.cos, other.sin, other.cos),
644            cos: trig::cosine_sum(self.sin, self.cos, other.sin, other.cos),
645        }
646    }
647}
648
649impl AddAssign for Angle {
650    fn add_assign(&mut self, other: Self) {
651        *self = *self + other;
652    }
653}
654
655impl Sub for Angle {
656    type Output = Self;
657
658    /// Subtract two Angles, i.e. a - b
659    /// Uses trigonometric identity functions, see:
660    /// [angle sum and difference identities](https://en.wikipedia.org/wiki/List_of_trigonometric_identities#Angle_sum_and_difference_identities).
661    /// # Examples
662    /// ```
663    /// use angle_sc::{Angle, Degrees, is_within_tolerance};
664    ///
665    /// let angle_30 = Angle::from(Degrees(30.0));
666    /// let angle_60 = Angle::from(Degrees(60.0));
667    /// let result_30 = angle_60 - angle_30;
668    ///
669    /// assert!(is_within_tolerance(Degrees(30.0).0, Degrees::from(result_30).0, 32.0 * f64::EPSILON));
670    /// ```
671    fn sub(self, other: Self) -> Self::Output {
672        Self {
673            sin: trig::sine_diff(self.sin, self.cos, other.sin, other.cos),
674            cos: trig::cosine_diff(self.sin, self.cos, other.sin, other.cos),
675        }
676    }
677}
678
679impl SubAssign for Angle {
680    fn sub_assign(&mut self, other: Self) {
681        *self = *self - other;
682    }
683}
684
685impl PartialOrd for Angle {
686    /// Compare two Angles, i.e. a < b.
687    /// It compares whether an `Angle` is clockwise of the other `Angle` on the
688    /// unit circle.
689    ///
690    /// # Examples
691    /// ```
692    /// use angle_sc::{Angle, Degrees};
693    /// let degrees_120 = Angle::from(Degrees(120.0));
694    /// let degrees_m120 = -degrees_120;
695    /// assert!(degrees_120 < degrees_m120);
696    /// ```
697    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
698        let delta = *other - *self;
699        trig::UnitNegRange(0.0).partial_cmp(&delta.sin)
700    }
701}
702
703impl From<Degrees> for Angle {
704    /// Construct an `Angle` from an angle in Degrees.
705    ///
706    /// Examples:
707    /// ```
708    /// use angle_sc::{Angle, Degrees, is_within_tolerance, trig};
709    ///
710    /// let angle = Angle::from(Degrees(60.0));
711    /// assert_eq!(trig::COS_30_DEGREES, angle.sin().0);
712    /// assert_eq!(0.5, angle.cos().0);
713    /// assert_eq!(60.0, Degrees::from(angle).0);
714    /// ```
715    fn from(a: Degrees) -> Self {
716        let (sin, cos) = trig::sincosd(a);
717        Self { sin, cos }
718    }
719}
720
721impl From<(Degrees, Degrees)> for Angle {
722    /// Construct an `Angle` from the difference of a pair angles in Degrees:
723    /// a - b
724    ///
725    /// Examples:
726    /// ```
727    /// use angle_sc::{Angle, Degrees, trig};
728    ///
729    /// // Difference of Degrees(-155.0) - Degrees(175.0)
730    /// let angle = Angle::from((Degrees(-155.0), Degrees(175.0)));
731    /// assert_eq!(0.5, angle.sin().0);
732    /// assert_eq!(trig::COS_30_DEGREES, angle.cos().0);
733    /// assert_eq!(30.0, Degrees::from(angle).0);
734    /// ```
735    fn from(params: (Degrees, Degrees)) -> Self {
736        let (sin, cos) = trig::sincosd_diff(params.0, params.1);
737        Self { sin, cos }
738    }
739}
740
741impl From<Radians> for Angle {
742    /// Construct an `Angle` from an angle in Radians.
743    ///
744    /// Examples:
745    /// ```
746    /// use angle_sc::{Angle, Radians, trig};
747    ///
748    /// let angle = Angle::from(Radians(-core::f64::consts::FRAC_PI_6));
749    /// assert_eq!(-0.5, angle.sin().0);
750    /// assert_eq!(trig::COS_30_DEGREES, angle.cos().0);
751    /// assert_eq!(-core::f64::consts::FRAC_PI_6, Radians::from(angle).0);
752    /// ```
753    fn from(a: Radians) -> Self {
754        let (sin, cos) = trig::sincos(a);
755        Self { sin, cos }
756    }
757}
758
759impl From<(Radians, Radians)> for Angle {
760    /// Construct an Angle from the difference of a pair angles in Radians:
761    /// a - b
762    ///
763    /// Examples:
764    /// ```
765    /// use angle_sc::{Angle, Radians, trig};
766    ///
767    /// // 6*π - π/3 radians round trip
768    /// let angle = Angle::from((
769    ///     Radians(3.0 * core::f64::consts::TAU),
770    ///     Radians(core::f64::consts::FRAC_PI_3),
771    /// ));
772    /// assert_eq!(-core::f64::consts::FRAC_PI_3, Radians::from(angle).0);
773    /// ```
774    fn from(params: (Radians, Radians)) -> Self {
775        let (sin, cos) = trig::sincos_diff(params.0, params.1);
776        Self { sin, cos }
777    }
778}
779
780impl From<Angle> for Radians {
781    /// Convert an Angle to Radians.
782    fn from(a: Angle) -> Self {
783        trig::arctan2(a.sin, a.cos)
784    }
785}
786
787impl From<Angle> for Degrees {
788    /// Convert an Angle to Degrees.
789    fn from(a: Angle) -> Self {
790        trig::arctan2d(a.sin, a.cos)
791    }
792}
793
794impl Serialize for Angle {
795    /// Serialize an Angle to an value in Degrees.
796    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
797    where
798        S: Serializer,
799    {
800        serializer.serialize_newtype_struct("Degrees", &Degrees::from(*self))
801    }
802}
803
804impl<'de> Deserialize<'de> for Angle {
805    /// Deserialize an value in Degrees to an Angle.
806    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
807    where
808        D: Deserializer<'de>,
809    {
810        Ok(Self::from(Degrees::deserialize(deserializer)?))
811    }
812}
813
814//////////////////////////////////////////////////////////////////////////////
815
816/// Calculates floating-point sum and error.
817/// The [2Sum](https://en.wikipedia.org/wiki/2Sum) algorithm.
818///
819/// * `a`, `b` the floating-point numbers to add.
820///
821/// returns (a + b) and the floating-point error: $t = a + b - (a \oplus b)$
822/// so: $a+b=s+t$.
823#[must_use]
824pub fn two_sum<T>(a: T, b: T) -> (T, T)
825where
826    T: Copy + Add<Output = T> + Sub<Output = T>,
827{
828    let s = a + b;
829    let a_prime = s - b;
830    let b_prime = s - a_prime;
831    let delta_a = a - a_prime;
832    let delta_b = b - b_prime;
833    let t = delta_a + delta_b;
834    (s, t)
835}
836
837/// Return the minimum of a or b.
838#[must_use]
839pub fn min<T>(a: T, b: T) -> T
840where
841    T: PartialOrd + Copy,
842{
843    if b < a { b } else { a }
844}
845
846/// Return the maximum of a or b.
847#[must_use]
848pub fn max<T>(a: T, b: T) -> T
849where
850    T: PartialOrd + Copy,
851{
852    if b < a { a } else { b }
853}
854
855/// The Validate trait.
856pub trait Validate {
857    /// return true if the type is valid, false otherwise.
858    fn is_valid(&self) -> bool;
859}
860
861/// Check whether value <= tolerance.
862#[must_use]
863pub fn is_small<T>(value: T, tolerance: T) -> bool
864where
865    T: PartialOrd + Copy,
866{
867    value <= tolerance
868}
869
870/// Check whether a value is within tolerance of a reference value.
871/// * `reference` the required value
872/// * `value` the value to test
873/// * `tolerance` the permitted tolerance
874///
875/// return true if abs(reference - value) is <= tolerance
876#[must_use]
877pub fn is_within_tolerance<T>(reference: T, value: T, tolerance: T) -> bool
878where
879    T: PartialOrd + Copy + Sub<Output = T>,
880{
881    let delta = max(reference, value) - min(reference, value);
882    is_small(delta, tolerance)
883}
884
885#[cfg(test)]
886mod tests {
887    use super::*;
888
889    #[test]
890    fn test_degrees_traits() {
891        let zero = Degrees::default();
892        assert_eq!(Degrees(0.0), zero);
893        let one = Degrees(1.0);
894        let mut one_clone = one.clone();
895        assert!(one_clone == one);
896        let two = Degrees(2.0);
897        let m_one = Degrees(-1.0);
898        assert_eq!(m_one, -one);
899
900        assert_eq!(one, m_one.abs());
901
902        assert_eq!(m_one, one - two);
903        one_clone -= two;
904        assert_eq!(m_one, one_clone);
905
906        assert_eq!(one, m_one + two);
907        one_clone += two;
908        assert_eq!(one, one_clone);
909
910        let d_120 = Degrees(120.0);
911        let d_m120 = Degrees(-120.0);
912        assert_eq!(d_120, d_m120.abs());
913
914        assert_eq!(Degrees(30.0), Degrees(-155.0) - Degrees(175.0));
915
916        assert_eq!(d_m120, d_120 + d_120);
917        assert_eq!(d_120, d_m120 + d_m120);
918        assert_eq!(d_120, d_m120 - d_120);
919
920        assert_eq!(Degrees(-60.0), d_120.opposite());
921        assert_eq!(Degrees(60.0), d_m120.opposite());
922
923        let serialized = serde_json::to_string(&one).unwrap();
924        let deserialized: Degrees = serde_json::from_str(&serialized).unwrap();
925        assert_eq!(one, deserialized);
926
927        let bad_text = "junk";
928        let _serde_error = serde_json::from_str::<Degrees>(&bad_text).unwrap_err();
929
930        print!("Degrees: {:?}", one);
931    }
932
933    #[test]
934    fn test_radians_traits() {
935        let zero = Radians::default();
936        assert_eq!(Radians(0.0), zero);
937        let one = Radians(1.0);
938        let mut one_clone = one.clone();
939        assert!(one_clone == one);
940        let two = Radians(2.0);
941        let m_two = -two;
942        assert!(one < two);
943        let m_one = Radians(-1.0);
944        assert_eq!(m_one, -one);
945
946        assert_eq!(one, m_one.abs());
947
948        assert_eq!(m_one, one - two);
949        one_clone -= two;
950        assert_eq!(m_one, one_clone);
951
952        assert_eq!(one, m_one + two);
953        one_clone += two;
954        assert_eq!(one, one_clone);
955
956        let result_1 = m_two - two;
957        assert_eq!(core::f64::consts::TAU - 4.0, result_1.0);
958        assert_eq!(core::f64::consts::PI - 4.0, result_1.opposite().0);
959
960        let result_2 = two - m_two;
961        assert_eq!(4.0 - core::f64::consts::TAU, result_2.0);
962        assert_eq!(4.0 - core::f64::consts::PI, result_2.opposite().0);
963
964        let value = Radians(-f64::EPSILON);
965        assert_eq!(Radians(0.0), value.clamp(Radians(1.0)));
966        let value = Radians(0.0);
967        assert_eq!(Radians(0.0), value.clamp(Radians(1.0)));
968        let value = Radians(1.0);
969        assert_eq!(Radians(1.0), value.clamp(Radians(1.0)));
970        let value = Radians(1.0 + f64::EPSILON);
971        assert_eq!(Radians(1.0), value.clamp(Radians(1.0)));
972
973        print!("Radians: {:?}", one);
974    }
975
976    #[test]
977    fn test_angle_traits() {
978        let zero = Angle::default();
979        assert_eq!(0.0, zero.sin().0);
980        assert_eq!(1.0, zero.cos().0);
981        assert_eq!(0.0, zero.tan().unwrap());
982        assert!(zero.csc().is_none());
983        assert_eq!(1.0, zero.sec().unwrap());
984        assert!(zero.cot().is_none());
985        assert!(zero.is_valid());
986
987        let zero_clone = zero.clone();
988        assert_eq!(zero, zero_clone);
989
990        let one = Angle::from_y_x(1.0, 0.0);
991        assert_eq!(1.0, one.sin().0);
992        assert_eq!(0.0, one.cos().0);
993        assert!(one.tan().is_none());
994        assert_eq!(1.0, one.csc().unwrap());
995        assert!(one.sec().is_none());
996        assert_eq!(0.0, one.cot().unwrap());
997        assert!(one.is_valid());
998
999        let angle_m45 = Angle::from_y_x(-f64::EPSILON, f64::EPSILON);
1000        assert!(is_within_tolerance(
1001            -core::f64::consts::FRAC_1_SQRT_2,
1002            angle_m45.sin().0,
1003            f64::EPSILON
1004        ));
1005        assert!(is_within_tolerance(
1006            core::f64::consts::FRAC_1_SQRT_2,
1007            angle_m45.cos().0,
1008            f64::EPSILON
1009        ));
1010
1011        assert!(angle_m45 < zero);
1012
1013        let serialized = serde_json::to_string(&zero).unwrap();
1014        let deserialized: Angle = serde_json::from_str(&serialized).unwrap();
1015        assert_eq!(zero, deserialized);
1016
1017        let bad_text = "junk";
1018        let _serde_error = serde_json::from_str::<Angle>(&bad_text).unwrap_err();
1019
1020        print!("Angle: {:?}", angle_m45);
1021    }
1022    #[test]
1023    fn test_angle_conversion() {
1024        let zero = Angle::default();
1025
1026        let too_small = Angle::from_y_x(-f64::EPSILON / 2.0, f64::EPSILON / 2.0);
1027        assert!(too_small.is_valid());
1028        assert_eq!(zero, too_small);
1029
1030        let small = Angle::from(-trig::MAX_COS_ANGLE_IS_ONE);
1031        assert!(small.is_valid());
1032        assert_eq!(-trig::MAX_COS_ANGLE_IS_ONE.0, small.sin().0);
1033        assert_eq!(1.0, small.cos().0);
1034        assert_eq!(-trig::MAX_COS_ANGLE_IS_ONE.0, Radians::from(small).0);
1035
1036        let angle_30 = Angle::from((
1037            Radians(core::f64::consts::FRAC_PI_3),
1038            Radians(core::f64::consts::FRAC_PI_6),
1039        ));
1040        assert!(angle_30.is_valid());
1041        assert_eq!(0.5, angle_30.sin().0);
1042        assert_eq!(libm::sqrt(3.0) / 2.0, angle_30.cos().0);
1043        assert_eq!(30.0, Degrees::from(angle_30).0);
1044        assert_eq!(core::f64::consts::FRAC_PI_6, Radians::from(angle_30).0);
1045
1046        let angle_45 = Angle::from(Radians(core::f64::consts::FRAC_PI_4));
1047        assert!(angle_45.is_valid());
1048        assert_eq!(core::f64::consts::FRAC_1_SQRT_2, angle_45.sin().0);
1049        assert_eq!(core::f64::consts::FRAC_1_SQRT_2, angle_45.cos().0);
1050        assert_eq!(45.0, Degrees::from(angle_45).0);
1051        assert_eq!(core::f64::consts::FRAC_PI_4, Radians::from(angle_45).0);
1052
1053        let angle_m45 = Angle::from(Degrees(-45.0));
1054        assert!(angle_m45.is_valid());
1055        assert_eq!(-core::f64::consts::FRAC_1_SQRT_2, angle_m45.sin().0);
1056        assert_eq!(core::f64::consts::FRAC_1_SQRT_2, angle_m45.cos().0);
1057        assert_eq!(-45.0, Degrees::from(angle_m45).0);
1058        assert_eq!(-core::f64::consts::FRAC_PI_4, Radians::from(angle_m45).0);
1059
1060        let angle_60 = Angle::from((Degrees(-140.0), Degrees(160.0)));
1061        assert!(angle_60.is_valid());
1062        assert_eq!(libm::sqrt(3.0) / 2.0, angle_60.sin().0);
1063        assert_eq!(0.5, angle_60.cos().0);
1064        assert_eq!(60.0, Degrees::from(angle_60).0);
1065        // Fails because PI is irrational
1066        // assert_eq!(core::f64::consts::FRAC_PI_3, Radians::from(angle_60).0);
1067        assert!(is_within_tolerance(
1068            core::f64::consts::FRAC_PI_3,
1069            Radians::from(angle_60).0,
1070            f64::EPSILON
1071        ));
1072
1073        let angle_30 = Angle::from((Degrees(-155.0), Degrees(175.0)));
1074        // assert!(angle_30.is_valid());
1075        assert_eq!(0.5, angle_30.sin().0);
1076        assert_eq!(libm::sqrt(3.0) / 2.0, angle_30.cos().0);
1077        assert_eq!(30.0, Degrees::from(angle_30).0);
1078        assert_eq!(core::f64::consts::FRAC_PI_6, Radians::from(angle_30).0);
1079
1080        let angle_120 = Angle::from(Degrees(120.0));
1081        assert!(angle_120.is_valid());
1082        assert_eq!(libm::sqrt(3.0) / 2.0, angle_120.sin().0);
1083        assert_eq!(-0.5, angle_120.cos().0);
1084        assert_eq!(120.0, Degrees::from(angle_120).0);
1085        assert_eq!(
1086            2.0 * core::f64::consts::FRAC_PI_3,
1087            Radians::from(angle_120).0
1088        );
1089
1090        let angle_m120 = Angle::from(Degrees(-120.0));
1091        assert!(angle_m120.is_valid());
1092        assert_eq!(-libm::sqrt(3.0) / 2.0, angle_m120.sin().0);
1093        assert_eq!(-0.5, angle_m120.cos().0);
1094        assert_eq!(-120.0, Degrees::from(angle_m120).0);
1095        assert_eq!(
1096            -2.0 * core::f64::consts::FRAC_PI_3,
1097            Radians::from(angle_m120).0
1098        );
1099
1100        let angle_m140 = Angle::from(Degrees(-140.0));
1101        assert!(angle_m140.is_valid());
1102        assert!(is_within_tolerance(
1103            -0.6427876096865393,
1104            angle_m140.sin().0,
1105            f64::EPSILON
1106        ));
1107        assert!(is_within_tolerance(
1108            -0.7660444431189781,
1109            angle_m140.cos().0,
1110            f64::EPSILON
1111        ));
1112        assert_eq!(-140.0, Degrees::from(angle_m140).0);
1113
1114        let angle_180 = Angle::from(Degrees(180.0));
1115        assert!(angle_180.is_valid());
1116        assert_eq!(0.0, angle_180.sin().0);
1117        assert_eq!(-1.0, angle_180.cos().0);
1118        assert_eq!(180.0, Degrees::from(angle_180).0);
1119        assert_eq!(core::f64::consts::PI, Radians::from(angle_180).0);
1120    }
1121
1122    #[test]
1123    fn test_angle_maths() {
1124        let degrees_30 = Angle::from(Degrees(30.0));
1125        let degrees_60 = Angle::from(Degrees(60.0));
1126        let degrees_120 = Angle::from(Degrees(120.0));
1127        let degrees_m120 = -degrees_120;
1128
1129        assert!(degrees_120 < degrees_m120);
1130        assert_eq!(degrees_120, degrees_m120.abs());
1131        assert_eq!(degrees_60, degrees_m120.opposite());
1132        assert_eq!(degrees_120, degrees_30.quarter_turn_cw());
1133        assert_eq!(degrees_30, degrees_120.quarter_turn_ccw());
1134        assert_eq!(degrees_60, degrees_120.negate_cos());
1135
1136        let result = degrees_m120 - degrees_120;
1137        assert_eq!(Degrees(120.0).0, Degrees::from(result).0);
1138
1139        let mut result = degrees_m120;
1140        result -= degrees_120;
1141        assert_eq!(Degrees(120.0).0, Degrees::from(result).0);
1142
1143        let result = degrees_120 + degrees_120;
1144        assert_eq!(Degrees(-120.0).0, Degrees::from(result).0);
1145
1146        let mut result = degrees_120;
1147        result += degrees_120;
1148        assert_eq!(Degrees(-120.0).0, Degrees::from(result).0);
1149
1150        let result = degrees_60.double();
1151        assert_eq!(Degrees(120.0).0, Degrees::from(result).0);
1152
1153        let result = degrees_120.double();
1154        assert_eq!(Degrees(-120.0).0, Degrees::from(result).0);
1155
1156        assert_eq!(-degrees_60, degrees_m120.half());
1157    }
1158
1159    #[test]
1160    fn test_two_sum() {
1161        let result = two_sum(1.0, 1.0);
1162        assert_eq!(2.0, result.0);
1163        assert_eq!(0.0, result.1);
1164
1165        let result = two_sum(1.0, 1e-53);
1166        assert_eq!(1.0, result.0);
1167        assert_eq!(1e-53, result.1);
1168
1169        let result = two_sum(1.0, -1e-53);
1170        assert_eq!(1.0, result.0);
1171        assert_eq!(-1e-53, result.1);
1172    }
1173
1174    #[test]
1175    fn test_min_and_max() {
1176        // min -ve and +ve
1177        assert_eq!(min(-1.0 + f64::EPSILON, -1.0), -1.0);
1178        assert_eq!(min(1.0, 1.0 + f64::EPSILON), 1.0);
1179        // max -ve and +ve
1180        assert_eq!(max(-1.0, -1.0 - f64::EPSILON), -1.0);
1181        assert_eq!(max(1.0 - f64::EPSILON, 1.0), 1.0);
1182    }
1183
1184    #[test]
1185    fn test_is_within_tolerance() {
1186        // below minimum tolerance
1187        assert_eq!(
1188            false,
1189            is_within_tolerance(1.0 - 2.0 * f64::EPSILON, 1.0, f64::EPSILON)
1190        );
1191
1192        // within minimum tolerance
1193        assert!(is_within_tolerance(1.0 - f64::EPSILON, 1.0, f64::EPSILON));
1194
1195        // within maximum tolerance
1196        assert!(is_within_tolerance(1.0 + f64::EPSILON, 1.0, f64::EPSILON));
1197
1198        // above maximum tolerance
1199        assert_eq!(
1200            false,
1201            is_within_tolerance(1.0 + 2.0 * f64::EPSILON, 1.0, f64::EPSILON)
1202        );
1203    }
1204}