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