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