1use crate::{Dimension, Quantity, Unit};
53use core::f64::consts::TAU;
54use qtty_derive::Unit;
55
56#[inline]
57fn rem_euclid(x: f64, modulus: f64) -> f64 {
58 #[cfg(feature = "std")]
59 {
60 x.rem_euclid(modulus)
61 }
62 #[cfg(not(feature = "std"))]
63 {
64 let r = crate::libm::fmod(x, modulus);
65 if r < 0.0 {
66 r + modulus
67 } else {
68 r
69 }
70 }
71}
72
73pub enum Angular {}
75impl Dimension for Angular {}
76
77pub trait AngularUnit: Unit<Dim = Angular> {
86 const FULL_TURN: f64;
88 const HALF_TURN: f64;
90 const QUARTED_TURN: f64;
92}
93impl<T: Unit<Dim = Angular>> AngularUnit for T {
94 const FULL_TURN: f64 = Radians::new(TAU).to::<T>().value();
96 const HALF_TURN: f64 = Radians::new(TAU).to::<T>().value() * 0.5;
98 const QUARTED_TURN: f64 = Radians::new(TAU).to::<T>().value() * 0.25;
100}
101
102impl<U: AngularUnit + Copy> Quantity<U> {
103 pub const TAU: Quantity<U> = Quantity::<U>::new(U::FULL_TURN);
107 pub const FULL_TURN: Quantity<U> = Quantity::<U>::new(U::FULL_TURN);
109 pub const HALF_TURN: Quantity<U> = Quantity::<U>::new(U::HALF_TURN);
111 pub const QUARTED_TURN: Quantity<U> = Quantity::<U>::new(U::QUARTED_TURN);
113
114 #[inline]
118 pub fn sin(&self) -> f64 {
119 let x = self.to::<Radian>().value();
120 #[cfg(feature = "std")]
121 {
122 x.sin()
123 }
124 #[cfg(not(feature = "std"))]
125 {
126 crate::libm::sin(x)
127 }
128 }
129
130 #[inline]
134 pub fn cos(&self) -> f64 {
135 let x = self.to::<Radian>().value();
136 #[cfg(feature = "std")]
137 {
138 x.cos()
139 }
140 #[cfg(not(feature = "std"))]
141 {
142 crate::libm::cos(x)
143 }
144 }
145
146 #[inline]
150 pub fn tan(&self) -> f64 {
151 let x = self.to::<Radian>().value();
152 #[cfg(feature = "std")]
153 {
154 x.tan()
155 }
156 #[cfg(not(feature = "std"))]
157 {
158 crate::libm::tan(x)
159 }
160 }
161
162 #[inline]
166 pub fn sin_cos(&self) -> (f64, f64) {
167 let x = self.to::<Radian>().value();
168 #[cfg(feature = "std")]
169 {
170 x.sin_cos()
171 }
172 #[cfg(not(feature = "std"))]
173 {
174 (crate::libm::sin(x), crate::libm::cos(x))
175 }
176 }
177
178 #[inline]
180 pub const fn signum(self) -> f64 {
181 self.value().signum()
182 }
183
184 #[inline]
188 pub fn normalize(self) -> Self {
189 self.wrap_pos()
190 }
191
192 #[inline]
196 pub fn wrap_pos(self) -> Self {
197 Self::new(rem_euclid(self.value(), U::FULL_TURN))
198 }
199
200 #[inline]
206 pub fn wrap_signed(self) -> Self {
207 let full = U::FULL_TURN;
208 let half = 0.5 * full;
209 let x = self.value();
210 let y = rem_euclid(x + half, full) - half;
211 let norm = if y <= -half { y + full } else { y };
212 Self::new(norm)
213 }
214
215 #[inline]
221 pub fn wrap_signed_lo(self) -> Self {
222 let mut y = self.wrap_signed().value(); let half = 0.5 * U::FULL_TURN;
224 if y >= half {
225 y -= U::FULL_TURN;
227 }
228 Self::new(y)
229 }
230
231 #[inline]
237 pub fn wrap_quarter_fold(self) -> Self {
238 let full = U::FULL_TURN;
239 let half = 0.5 * full;
240 let quarter = 0.25 * full;
241 let y = rem_euclid(self.value() + quarter, full);
242 Self::new(quarter - (y - half).abs())
244 }
245
246 #[inline]
248 pub fn signed_separation(self, other: Self) -> Self {
249 (self - other).wrap_signed()
250 }
251
252 #[inline]
254 pub fn abs_separation(self, other: Self) -> Self {
255 let sep = self.signed_separation(other);
256 Self::new(sep.value().abs())
257 }
258}
259
260#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
262#[unit(symbol = "Deg", dimension = Angular, ratio = 1.0)]
263pub struct Degree;
264pub type Deg = Degree;
266pub type Degrees = Quantity<Deg>;
268pub const DEG: Degrees = Degrees::new(1.0);
270
271#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
273#[unit(symbol = "Rad", dimension = Angular, ratio = 180.0 / core::f64::consts::PI)]
274pub struct Radian;
275pub type Rad = Radian;
277pub type Radians = Quantity<Rad>;
279pub const RAD: Radians = Radians::new(1.0);
281
282#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
284#[unit(symbol = "mrad", dimension = Angular, ratio = (180.0 / core::f64::consts::PI) / 1_000.0)]
285pub struct Milliradian;
286pub type Mrad = Milliradian;
288pub type Milliradians = Quantity<Mrad>;
290pub const MRAD: Milliradians = Milliradians::new(1.0);
292
293#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
295#[unit(symbol = "Arcm", dimension = Angular, ratio = 1.0 / 60.0)]
296pub struct Arcminute;
297pub type MOA = Arcminute;
299pub type Arcm = Arcminute;
301pub type Arcminutes = Quantity<Arcm>;
303pub const ARCM: Arcminutes = Arcminutes::new(1.0);
305
306#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
308#[unit(symbol = "Arcs", dimension = Angular, ratio = 1.0 / 3600.0)]
309pub struct Arcsecond;
310pub type Arcs = Arcsecond;
312pub type Arcseconds = Quantity<Arcs>;
314pub const ARCS: Arcseconds = Arcseconds::new(1.0);
316
317#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
319#[unit(symbol = "Mas", dimension = Angular, ratio = 1.0 / 3_600_000.0)]
320pub struct MilliArcsecond;
321pub type Mas = MilliArcsecond;
323pub type MilliArcseconds = Quantity<Mas>;
325pub const MAS: MilliArcseconds = MilliArcseconds::new(1.0);
327
328#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
330#[unit(symbol = "μas", dimension = Angular, ratio = 1.0 / 3_600_000_000.0)]
331pub struct MicroArcsecond;
332pub type Uas = MicroArcsecond;
334pub type MicroArcseconds = Quantity<Uas>;
336pub const UAS: MicroArcseconds = MicroArcseconds::new(1.0);
338
339#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
341#[unit(symbol = "Gon", dimension = Angular, ratio = 0.9)]
342pub struct Gradian;
343pub type Gon = Gradian;
345pub type Gradians = Quantity<Gon>;
347pub const GON: Gradians = Gradians::new(1.0);
349
350#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
352#[unit(symbol = "Turn", dimension = Angular, ratio = 360.0)]
353pub struct Turn;
354pub type Turns = Quantity<Turn>;
356pub const TURN: Turns = Turns::new(1.0);
358
359#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
361#[unit(symbol = "Hms", dimension = Angular, ratio = 15.0)]
362pub struct HourAngle;
363pub type Hms = HourAngle;
365pub type HourAngles = Quantity<Hms>;
367pub const HOUR_ANGLE: HourAngles = HourAngles::new(1.0);
369
370impl HourAngles {
371 pub const fn from_hms(hours: i32, minutes: u32, seconds: f64) -> Self {
381 let sign = if hours < 0 { -1.0 } else { 1.0 };
382 let h_abs = if hours < 0 { -hours } else { hours } as f64;
383 let m = minutes as f64 / 60.0;
384 let s = seconds / 3600.0;
385 let total_hours = sign * (h_abs + m + s);
386 Self::new(total_hours)
387 }
388}
389
390impl Degrees {
391 pub const fn from_dms(deg: i32, min: u32, sec: f64) -> Self {
402 let sign = if deg < 0 { -1.0 } else { 1.0 };
403 let d_abs = if deg < 0 { -deg } else { deg } as f64;
404 let m = min as f64 / 60.0;
405 let s = sec / 3600.0;
406 let total = sign * (d_abs + m + s);
407 Self::new(total)
408 }
409
410 pub const fn from_dms_sign(sign: i8, deg: u32, min: u32, sec: f64) -> Self {
414 let s = if sign < 0 { -1.0 } else { 1.0 };
415 let total = (deg as f64) + (min as f64) / 60.0 + (sec / 3600.0);
416 Self::new(s * total)
417 }
418}
419
420crate::impl_unit_conversions!(
422 Degree,
423 Radian,
424 Milliradian,
425 Arcminute,
426 Arcsecond,
427 MilliArcsecond,
428 MicroArcsecond,
429 Gradian,
430 Turn,
431 HourAngle
432);
433
434#[cfg(test)]
435mod tests {
436 use super::*;
437 use approx::{assert_abs_diff_eq, assert_relative_eq};
438 use proptest::prelude::*;
439 use std::f64::consts::{PI, TAU};
440
441 #[test]
446 fn test_full_turn() {
447 assert_abs_diff_eq!(Radian::FULL_TURN, TAU, epsilon = 1e-12);
448 assert_eq!(Degree::FULL_TURN, 360.0);
449 assert_eq!(Arcsecond::FULL_TURN, 1_296_000.0);
450 }
451
452 #[test]
453 fn test_half_turn() {
454 assert_abs_diff_eq!(Radian::HALF_TURN, PI, epsilon = 1e-12);
455 assert_eq!(Degree::HALF_TURN, 180.0);
456 assert_eq!(Arcsecond::HALF_TURN, 648_000.0);
457 }
458
459 #[test]
460 fn test_quarter_turn() {
461 assert_abs_diff_eq!(Radian::QUARTED_TURN, PI / 2.0, epsilon = 1e-12);
462 assert_eq!(Degree::QUARTED_TURN, 90.0);
463 assert_eq!(Arcsecond::QUARTED_TURN, 324_000.0);
464 }
465
466 #[test]
467 fn test_quantity_constants() {
468 assert_eq!(Degrees::FULL_TURN.value(), 360.0);
469 assert_eq!(Degrees::HALF_TURN.value(), 180.0);
470 assert_eq!(Degrees::QUARTED_TURN.value(), 90.0);
471 assert_eq!(Degrees::TAU.value(), 360.0);
472 }
473
474 #[test]
479 fn conversion_degrees_to_radians() {
480 let deg = Degrees::new(180.0);
481 let rad = deg.to::<Radian>();
482 assert_abs_diff_eq!(rad.value(), PI, epsilon = 1e-12);
483 }
484
485 #[test]
486 fn conversion_radians_to_degrees() {
487 let rad = Radians::new(PI);
488 let deg = rad.to::<Degree>();
489 assert_abs_diff_eq!(deg.value(), 180.0, epsilon = 1e-12);
490 }
491
492 #[test]
493 fn conversion_degrees_to_arcseconds() {
494 let deg = Degrees::new(1.0);
495 let arcs = deg.to::<Arcsecond>();
496 assert_abs_diff_eq!(arcs.value(), 3600.0, epsilon = 1e-9);
497 }
498
499 #[test]
500 fn conversion_arcseconds_to_degrees() {
501 let arcs = Arcseconds::new(3600.0);
502 let deg = arcs.to::<Degree>();
503 assert_abs_diff_eq!(deg.value(), 1.0, epsilon = 1e-12);
504 }
505
506 #[test]
507 fn conversion_degrees_to_milliarcseconds() {
508 let deg = Degrees::new(1.0);
509 let mas = deg.to::<MilliArcsecond>();
510 assert_abs_diff_eq!(mas.value(), 3_600_000.0, epsilon = 1e-6);
511 }
512
513 #[test]
514 fn conversion_hour_angles_to_degrees() {
515 let ha = HourAngles::new(1.0);
516 let deg = ha.to::<Degree>();
517 assert_abs_diff_eq!(deg.value(), 15.0, epsilon = 1e-12);
518 }
519
520 #[test]
521 fn conversion_roundtrip() {
522 let original = Degrees::new(123.456);
523 let rad = original.to::<Radian>();
524 let back = rad.to::<Degree>();
525 assert_abs_diff_eq!(back.value(), original.value(), epsilon = 1e-12);
526 }
527
528 #[test]
529 fn from_impl_degrees_radians() {
530 let deg = Degrees::new(90.0);
531 let rad: Radians = deg.into();
532 assert_abs_diff_eq!(rad.value(), PI / 2.0, epsilon = 1e-12);
533
534 let rad2 = Radians::new(PI);
535 let deg2: Degrees = rad2.into();
536 assert_abs_diff_eq!(deg2.value(), 180.0, epsilon = 1e-12);
537 }
538
539 #[test]
544 fn test_trig() {
545 let a = Degrees::new(90.0);
546 assert!((a.sin() - 1.0).abs() < 1e-12);
547 assert!(a.cos().abs() < 1e-12);
548 }
549
550 #[test]
551 fn trig_sin_known_values() {
552 assert_abs_diff_eq!(Degrees::new(0.0).sin(), 0.0, epsilon = 1e-12);
553 assert_abs_diff_eq!(Degrees::new(30.0).sin(), 0.5, epsilon = 1e-12);
554 assert_abs_diff_eq!(Degrees::new(90.0).sin(), 1.0, epsilon = 1e-12);
555 assert_abs_diff_eq!(Degrees::new(180.0).sin(), 0.0, epsilon = 1e-12);
556 assert_abs_diff_eq!(Degrees::new(270.0).sin(), -1.0, epsilon = 1e-12);
557 }
558
559 #[test]
560 fn trig_cos_known_values() {
561 assert_abs_diff_eq!(Degrees::new(0.0).cos(), 1.0, epsilon = 1e-12);
562 assert_abs_diff_eq!(Degrees::new(60.0).cos(), 0.5, epsilon = 1e-12);
563 assert_abs_diff_eq!(Degrees::new(90.0).cos(), 0.0, epsilon = 1e-12);
564 assert_abs_diff_eq!(Degrees::new(180.0).cos(), -1.0, epsilon = 1e-12);
565 }
566
567 #[test]
568 fn trig_tan_known_values() {
569 assert_abs_diff_eq!(Degrees::new(0.0).tan(), 0.0, epsilon = 1e-12);
570 assert_abs_diff_eq!(Degrees::new(45.0).tan(), 1.0, epsilon = 1e-12);
571 assert_abs_diff_eq!(Degrees::new(180.0).tan(), 0.0, epsilon = 1e-12);
572 }
573
574 #[test]
575 fn trig_sin_cos_consistency() {
576 let angle = Degrees::new(37.5);
577 let (sin, cos) = angle.sin_cos();
578 assert_abs_diff_eq!(sin, angle.sin(), epsilon = 1e-15);
579 assert_abs_diff_eq!(cos, angle.cos(), epsilon = 1e-15);
580 }
581
582 #[test]
583 fn trig_pythagorean_identity() {
584 let angle = Degrees::new(123.456);
585 let sin = angle.sin();
586 let cos = angle.cos();
587 assert_abs_diff_eq!(sin * sin + cos * cos, 1.0, epsilon = 1e-12);
588 }
589
590 #[test]
591 fn trig_radians() {
592 assert_abs_diff_eq!(Radians::new(0.0).sin(), 0.0, epsilon = 1e-12);
593 assert_abs_diff_eq!(Radians::new(PI / 2.0).sin(), 1.0, epsilon = 1e-12);
594 assert_abs_diff_eq!(Radians::new(PI).cos(), -1.0, epsilon = 1e-12);
595 }
596
597 #[test]
602 fn signum_positive() {
603 assert_eq!(Degrees::new(45.0).signum(), 1.0);
604 }
605
606 #[test]
607 fn signum_negative() {
608 assert_eq!(Degrees::new(-45.0).signum(), -1.0);
609 }
610
611 #[test]
612 fn signum_zero() {
613 assert_eq!(Degrees::new(0.0).signum(), 1.0);
614 }
615
616 #[test]
621 fn wrap_pos_basic() {
622 assert_abs_diff_eq!(
623 Degrees::new(370.0).wrap_pos().value(),
624 10.0,
625 epsilon = 1e-12
626 );
627 assert_abs_diff_eq!(Degrees::new(720.0).wrap_pos().value(), 0.0, epsilon = 1e-12);
628 assert_abs_diff_eq!(Degrees::new(0.0).wrap_pos().value(), 0.0, epsilon = 1e-12);
629 }
630
631 #[test]
632 fn wrap_pos_negative() {
633 assert_abs_diff_eq!(
634 Degrees::new(-10.0).wrap_pos().value(),
635 350.0,
636 epsilon = 1e-12
637 );
638 assert_abs_diff_eq!(
639 Degrees::new(-370.0).wrap_pos().value(),
640 350.0,
641 epsilon = 1e-12
642 );
643 assert_abs_diff_eq!(
644 Degrees::new(-720.0).wrap_pos().value(),
645 0.0,
646 epsilon = 1e-12
647 );
648 }
649
650 #[test]
651 fn wrap_pos_boundary() {
652 assert_abs_diff_eq!(Degrees::new(360.0).wrap_pos().value(), 0.0, epsilon = 1e-12);
653 assert_abs_diff_eq!(
654 Degrees::new(-360.0).wrap_pos().value(),
655 0.0,
656 epsilon = 1e-12
657 );
658 }
659
660 #[test]
661 fn normalize_is_wrap_pos() {
662 let angle = Degrees::new(450.0);
663 assert_eq!(angle.normalize().value(), angle.wrap_pos().value());
664 }
665
666 #[test]
671 fn test_wrap_signed() {
672 let a = Degrees::new(370.0).wrap_signed();
673 assert_eq!(a.value(), 10.0);
674 let b = Degrees::new(-190.0).wrap_signed();
675 assert_eq!(b.value(), 170.0);
676 }
677
678 #[test]
679 fn wrap_signed_basic() {
680 assert_abs_diff_eq!(
681 Degrees::new(10.0).wrap_signed().value(),
682 10.0,
683 epsilon = 1e-12
684 );
685 assert_abs_diff_eq!(
686 Degrees::new(-10.0).wrap_signed().value(),
687 -10.0,
688 epsilon = 1e-12
689 );
690 }
691
692 #[test]
693 fn wrap_signed_over_180() {
694 assert_abs_diff_eq!(
695 Degrees::new(190.0).wrap_signed().value(),
696 -170.0,
697 epsilon = 1e-12
698 );
699 assert_abs_diff_eq!(
700 Degrees::new(270.0).wrap_signed().value(),
701 -90.0,
702 epsilon = 1e-12
703 );
704 }
705
706 #[test]
707 fn wrap_signed_boundary_180() {
708 assert_abs_diff_eq!(
709 Degrees::new(180.0).wrap_signed().value(),
710 180.0,
711 epsilon = 1e-12
712 );
713 assert_abs_diff_eq!(
714 Degrees::new(-180.0).wrap_signed().value(),
715 180.0,
716 epsilon = 1e-12
717 );
718 }
719
720 #[test]
721 fn wrap_signed_large_values() {
722 assert_abs_diff_eq!(
723 Degrees::new(540.0).wrap_signed().value(),
724 180.0,
725 epsilon = 1e-12
726 );
727 assert_abs_diff_eq!(
728 Degrees::new(-540.0).wrap_signed().value(),
729 180.0,
730 epsilon = 1e-12
731 );
732 }
733
734 #[test]
739 fn wrap_quarter_fold_basic() {
740 assert_abs_diff_eq!(
741 Degrees::new(0.0).wrap_quarter_fold().value(),
742 0.0,
743 epsilon = 1e-12
744 );
745 assert_abs_diff_eq!(
746 Degrees::new(45.0).wrap_quarter_fold().value(),
747 45.0,
748 epsilon = 1e-12
749 );
750 assert_abs_diff_eq!(
751 Degrees::new(-45.0).wrap_quarter_fold().value(),
752 -45.0,
753 epsilon = 1e-12
754 );
755 }
756
757 #[test]
758 fn wrap_quarter_fold_boundary() {
759 assert_abs_diff_eq!(
760 Degrees::new(90.0).wrap_quarter_fold().value(),
761 90.0,
762 epsilon = 1e-12
763 );
764 assert_abs_diff_eq!(
765 Degrees::new(-90.0).wrap_quarter_fold().value(),
766 -90.0,
767 epsilon = 1e-12
768 );
769 }
770
771 #[test]
772 fn wrap_quarter_fold_over_90() {
773 assert_abs_diff_eq!(
774 Degrees::new(100.0).wrap_quarter_fold().value(),
775 80.0,
776 epsilon = 1e-12
777 );
778 assert_abs_diff_eq!(
779 Degrees::new(135.0).wrap_quarter_fold().value(),
780 45.0,
781 epsilon = 1e-12
782 );
783 assert_abs_diff_eq!(
784 Degrees::new(180.0).wrap_quarter_fold().value(),
785 0.0,
786 epsilon = 1e-12
787 );
788 }
789
790 #[test]
795 fn signed_separation_basic() {
796 let a = Degrees::new(30.0);
797 let b = Degrees::new(50.0);
798 assert_abs_diff_eq!(a.signed_separation(b).value(), -20.0, epsilon = 1e-12);
799 assert_abs_diff_eq!(b.signed_separation(a).value(), 20.0, epsilon = 1e-12);
800 }
801
802 #[test]
803 fn signed_separation_wrap() {
804 let a = Degrees::new(10.0);
805 let b = Degrees::new(350.0);
806 assert_abs_diff_eq!(a.signed_separation(b).value(), 20.0, epsilon = 1e-12);
807 assert_abs_diff_eq!(b.signed_separation(a).value(), -20.0, epsilon = 1e-12);
808 }
809
810 #[test]
811 fn abs_separation() {
812 let a = Degrees::new(30.0);
813 let b = Degrees::new(50.0);
814 assert_abs_diff_eq!(a.abs_separation(b).value(), 20.0, epsilon = 1e-12);
815 assert_abs_diff_eq!(b.abs_separation(a).value(), 20.0, epsilon = 1e-12);
816 }
817
818 #[test]
823 fn degrees_from_dms_positive() {
824 let d = Degrees::from_dms(12, 30, 0.0);
825 assert_abs_diff_eq!(d.value(), 12.5, epsilon = 1e-12);
826 }
827
828 #[test]
829 fn degrees_from_dms_negative() {
830 let d = Degrees::from_dms(-33, 52, 0.0);
831 assert!(d.value() < 0.0);
832 assert_abs_diff_eq!(d.value(), -(33.0 + 52.0 / 60.0), epsilon = 1e-12);
833 }
834
835 #[test]
836 fn degrees_from_dms_with_seconds() {
837 let d = Degrees::from_dms(10, 20, 30.0);
838 assert_abs_diff_eq!(
839 d.value(),
840 10.0 + 20.0 / 60.0 + 30.0 / 3600.0,
841 epsilon = 1e-12
842 );
843 }
844
845 #[test]
846 fn degrees_from_dms_sign() {
847 let pos = Degrees::from_dms_sign(1, 45, 30, 0.0);
848 let neg = Degrees::from_dms_sign(-1, 45, 30, 0.0);
849 assert_abs_diff_eq!(pos.value(), 45.5, epsilon = 1e-12);
850 assert_abs_diff_eq!(neg.value(), -45.5, epsilon = 1e-12);
851 }
852
853 #[test]
854 fn hour_angles_from_hms() {
855 let ha = HourAngles::from_hms(5, 30, 0.0);
856 assert_abs_diff_eq!(ha.value(), 5.5, epsilon = 1e-12);
857 }
858
859 #[test]
860 fn hour_angles_from_hms_negative() {
861 let ha = HourAngles::from_hms(-3, 15, 0.0);
862 assert_abs_diff_eq!(ha.value(), -3.25, epsilon = 1e-12);
863 }
864
865 #[test]
866 fn hour_angles_to_degrees() {
867 let ha = HourAngles::new(6.0);
868 let deg = ha.to::<Degree>();
869 assert_abs_diff_eq!(deg.value(), 90.0, epsilon = 1e-12);
870 }
871
872 #[test]
877 fn display_degrees() {
878 let d = Degrees::new(45.5);
879 assert_eq!(format!("{}", d), "45.5 Deg");
880 }
881
882 #[test]
883 fn display_radians() {
884 let r = Radians::new(1.0);
885 assert_eq!(format!("{}", r), "1 Rad");
886 }
887
888 #[test]
893 fn unit_constants() {
894 assert_eq!(DEG.value(), 1.0);
895 assert_eq!(RAD.value(), 1.0);
896 assert_eq!(MRAD.value(), 1.0);
897 assert_eq!(ARCM.value(), 1.0);
898 assert_eq!(ARCS.value(), 1.0);
899 assert_eq!(MAS.value(), 1.0);
900 assert_eq!(UAS.value(), 1.0);
901 assert_eq!(GON.value(), 1.0);
902 assert_eq!(TURN.value(), 1.0);
903 assert_eq!(HOUR_ANGLE.value(), 1.0);
904 }
905
906 #[test]
911 fn wrap_signed_lo_boundary_half_turn() {
912 assert_abs_diff_eq!(
914 Degrees::new(180.0).wrap_signed_lo().value(),
915 -180.0,
916 epsilon = 1e-12
917 );
918 assert_abs_diff_eq!(
919 Degrees::new(-180.0).wrap_signed_lo().value(),
920 -180.0,
921 epsilon = 1e-12
922 );
923 }
924
925 #[test]
930 fn conversion_degrees_to_arcminutes() {
931 let deg = Degrees::new(1.0);
932 let arcm = deg.to::<Arcminute>();
933 assert_abs_diff_eq!(arcm.value(), 60.0, epsilon = 1e-12);
934 }
935
936 #[test]
937 fn conversion_arcminutes_to_degrees() {
938 let arcm = Arcminutes::new(60.0);
939 let deg = arcm.to::<Degree>();
940 assert_abs_diff_eq!(deg.value(), 1.0, epsilon = 1e-12);
941 }
942
943 #[test]
944 fn conversion_arcminutes_to_arcseconds() {
945 let arcm = Arcminutes::new(1.0);
946 let arcs = arcm.to::<Arcsecond>();
947 assert_abs_diff_eq!(arcs.value(), 60.0, epsilon = 1e-12);
948 }
949
950 #[test]
951 fn conversion_arcseconds_to_microarcseconds() {
952 let arcs = Arcseconds::new(1.0);
953 let uas = arcs.to::<MicroArcsecond>();
954 assert_abs_diff_eq!(uas.value(), 1_000_000.0, epsilon = 1e-6);
955 }
956
957 #[test]
958 fn conversion_microarcseconds_to_degrees() {
959 let uas = MicroArcseconds::new(3_600_000_000.0);
960 let deg = uas.to::<Degree>();
961 assert_abs_diff_eq!(deg.value(), 1.0, epsilon = 1e-9);
962 }
963
964 #[test]
965 fn conversion_degrees_to_gradians() {
966 let deg = Degrees::new(90.0);
967 let gon = deg.to::<Gradian>();
968 assert_abs_diff_eq!(gon.value(), 100.0, epsilon = 1e-12);
969 }
970
971 #[test]
972 fn conversion_gradians_to_degrees() {
973 let gon = Gradians::new(400.0);
974 let deg = gon.to::<Degree>();
975 assert_abs_diff_eq!(deg.value(), 360.0, epsilon = 1e-12);
976 }
977
978 #[test]
979 fn conversion_gradians_to_radians() {
980 let gon = Gradians::new(200.0);
981 let rad = gon.to::<Radian>();
982 assert_abs_diff_eq!(rad.value(), PI, epsilon = 1e-12);
983 }
984
985 #[test]
986 fn conversion_degrees_to_turns() {
987 let deg = Degrees::new(360.0);
988 let turn = deg.to::<Turn>();
989 assert_abs_diff_eq!(turn.value(), 1.0, epsilon = 1e-12);
990 }
991
992 #[test]
993 fn conversion_milliradians_to_radians() {
994 let mrad = Milliradians::new(1_000.0);
995 let rad = mrad.to::<Radian>();
996 assert_abs_diff_eq!(rad.value(), 1.0, epsilon = 1e-12);
997 }
998
999 #[test]
1000 fn conversion_turns_to_degrees() {
1001 let turn = Turns::new(2.5);
1002 let deg = turn.to::<Degree>();
1003 assert_abs_diff_eq!(deg.value(), 900.0, epsilon = 1e-12);
1004 }
1005
1006 #[test]
1007 fn conversion_turns_to_radians() {
1008 let turn = Turns::new(1.0);
1009 let rad = turn.to::<Radian>();
1010 assert_abs_diff_eq!(rad.value(), TAU, epsilon = 1e-12);
1011 }
1012
1013 #[test]
1014 fn from_impl_new_units() {
1015 let deg = Degrees::new(1.0);
1017 let arcm: Arcminutes = deg.into();
1018 assert_abs_diff_eq!(arcm.value(), 60.0, epsilon = 1e-12);
1019
1020 let gon = Gradians::new(100.0);
1021 let deg2: Degrees = gon.into();
1022 assert_abs_diff_eq!(deg2.value(), 90.0, epsilon = 1e-12);
1023
1024 let turn = Turns::new(0.25);
1025 let deg3: Degrees = turn.into();
1026 assert_abs_diff_eq!(deg3.value(), 90.0, epsilon = 1e-12);
1027 }
1028
1029 #[test]
1030 fn roundtrip_arcminute_arcsecond() {
1031 let original = Arcminutes::new(5.0);
1032 let arcs = original.to::<Arcsecond>();
1033 let back = arcs.to::<Arcminute>();
1034 assert_abs_diff_eq!(back.value(), original.value(), epsilon = 1e-12);
1035 }
1036
1037 #[test]
1038 fn roundtrip_gradian_degree() {
1039 let original = Gradians::new(123.456);
1040 let deg = original.to::<Degree>();
1041 let back = deg.to::<Gradian>();
1042 assert_abs_diff_eq!(back.value(), original.value(), epsilon = 1e-12);
1043 }
1044
1045 #[test]
1046 fn roundtrip_turn_radian() {
1047 let original = Turns::new(2.717);
1048 let rad = original.to::<Radian>();
1049 let back = rad.to::<Turn>();
1050 assert_abs_diff_eq!(back.value(), original.value(), epsilon = 1e-12);
1051 }
1052
1053 #[test]
1054 fn gradian_full_turn() {
1055 assert_abs_diff_eq!(Gradian::FULL_TURN, 400.0, epsilon = 1e-12);
1056 }
1057
1058 #[test]
1059 fn turn_full_turn() {
1060 assert_abs_diff_eq!(Turn::FULL_TURN, 1.0, epsilon = 1e-12);
1061 }
1062
1063 #[test]
1064 fn arcminute_full_turn() {
1065 assert_abs_diff_eq!(Arcminute::FULL_TURN, 21_600.0, epsilon = 1e-9);
1066 }
1067
1068 #[test]
1069 fn microarcsecond_conversion_chain() {
1070 let uas = MicroArcseconds::new(1e9);
1072 let mas = uas.to::<MilliArcsecond>();
1073 let arcs = mas.to::<Arcsecond>();
1074 let arcm = arcs.to::<Arcminute>();
1075 let deg = arcm.to::<Degree>();
1076
1077 assert_abs_diff_eq!(mas.value(), 1_000_000.0, epsilon = 1e-6);
1078 assert_abs_diff_eq!(arcs.value(), 1_000.0, epsilon = 1e-9);
1079 assert_abs_diff_eq!(arcm.value(), 1_000.0 / 60.0, epsilon = 1e-9);
1080 assert_relative_eq!(deg.value(), 1_000.0 / 3600.0, max_relative = 1e-9);
1081 }
1082
1083 #[test]
1084 fn wrap_pos_with_turns() {
1085 let turn = Turns::new(2.7);
1086 let wrapped = turn.wrap_pos();
1087 assert_abs_diff_eq!(wrapped.value(), 0.7, epsilon = 1e-12);
1088 }
1089
1090 #[test]
1091 fn wrap_signed_with_gradians() {
1092 let gon = Gradians::new(350.0);
1093 let wrapped = gon.wrap_signed();
1094 assert_abs_diff_eq!(wrapped.value(), -50.0, epsilon = 1e-12);
1095 }
1096
1097 #[test]
1098 fn trig_with_gradians() {
1099 let gon = Gradians::new(100.0); assert_abs_diff_eq!(gon.sin(), 1.0, epsilon = 1e-12);
1101 assert_abs_diff_eq!(gon.cos(), 0.0, epsilon = 1e-12);
1102 }
1103
1104 #[test]
1105 fn trig_with_turns() {
1106 let turn = Turns::new(0.25); assert_abs_diff_eq!(turn.sin(), 1.0, epsilon = 1e-12);
1108 assert_abs_diff_eq!(turn.cos(), 0.0, epsilon = 1e-12);
1109 }
1110
1111 #[test]
1112 fn all_units_to_degrees() {
1113 assert_abs_diff_eq!(
1115 Radians::new(PI).to::<Degree>().value(),
1116 180.0,
1117 epsilon = 1e-12
1118 );
1119 assert_abs_diff_eq!(
1120 Arcminutes::new(60.0).to::<Degree>().value(),
1121 1.0,
1122 epsilon = 1e-12
1123 );
1124 assert_abs_diff_eq!(
1125 Arcseconds::new(3600.0).to::<Degree>().value(),
1126 1.0,
1127 epsilon = 1e-12
1128 );
1129 assert_abs_diff_eq!(
1130 MilliArcseconds::new(3_600_000.0).to::<Degree>().value(),
1131 1.0,
1132 epsilon = 1e-9
1133 );
1134 assert_abs_diff_eq!(
1135 MicroArcseconds::new(3_600_000_000.0).to::<Degree>().value(),
1136 1.0,
1137 epsilon = 1e-6
1138 );
1139 assert_abs_diff_eq!(
1140 Gradians::new(100.0).to::<Degree>().value(),
1141 90.0,
1142 epsilon = 1e-12
1143 );
1144 assert_abs_diff_eq!(
1145 Turns::new(1.0).to::<Degree>().value(),
1146 360.0,
1147 epsilon = 1e-12
1148 );
1149 assert_abs_diff_eq!(
1150 HourAngles::new(1.0).to::<Degree>().value(),
1151 15.0,
1152 epsilon = 1e-12
1153 );
1154 }
1155
1156 proptest! {
1161 #[test]
1162 fn prop_wrap_pos_range(angle in -1e6..1e6f64) {
1163 let wrapped = Degrees::new(angle).wrap_pos();
1164 prop_assert!(wrapped.value() >= 0.0);
1165 prop_assert!(wrapped.value() < 360.0);
1166 }
1167
1168 #[test]
1169 fn prop_wrap_signed_range(angle in -1e6..1e6f64) {
1170 let wrapped = Degrees::new(angle).wrap_signed();
1171 prop_assert!(wrapped.value() > -180.0);
1172 prop_assert!(wrapped.value() <= 180.0);
1173 }
1174
1175 #[test]
1176 fn prop_wrap_quarter_fold_range(angle in -1e6..1e6f64) {
1177 let wrapped = Degrees::new(angle).wrap_quarter_fold();
1178 prop_assert!(wrapped.value() >= -90.0);
1179 prop_assert!(wrapped.value() <= 90.0);
1180 }
1181
1182 #[test]
1183 fn prop_pythagorean_identity(angle in -360.0..360.0f64) {
1184 let a = Degrees::new(angle);
1185 let sin = a.sin();
1186 let cos = a.cos();
1187 assert_abs_diff_eq!(sin * sin + cos * cos, 1.0, epsilon = 1e-12);
1188 }
1189
1190 #[test]
1191 fn prop_conversion_roundtrip(angle in -1e6..1e6f64) {
1192 let deg = Degrees::new(angle);
1193 let rad = deg.to::<Radian>();
1194 let back = rad.to::<Degree>();
1195 assert_relative_eq!(back.value(), deg.value(), max_relative = 1e-12);
1196 }
1197
1198 #[test]
1199 fn prop_abs_separation_symmetric(a in -360.0..360.0f64, b in -360.0..360.0f64) {
1200 let da = Degrees::new(a);
1201 let db = Degrees::new(b);
1202 assert_abs_diff_eq!(
1203 da.abs_separation(db).value(),
1204 db.abs_separation(da).value(),
1205 epsilon = 1e-12
1206 );
1207 }
1208 }
1209}