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