Skip to main content

angle_sc/
lib.rs

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