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