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