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//! [](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 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}