1use crate::scalar::Transcendental;
52use crate::{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 use crate::dimension::Angular;
75
76pub trait AngularUnit: Unit<Dim = Angular> {
83 const FULL_TURN: f64;
85 const HALF_TURN: f64;
87 const QUARTER_TURN: f64;
89}
90impl<T: Unit<Dim = Angular>> AngularUnit for T {
91 const FULL_TURN: f64 = Radians::new(TAU).to_const::<T>().value();
93 const HALF_TURN: f64 = Radians::new(TAU).to_const::<T>().value() * 0.5;
95 const QUARTER_TURN: f64 = Radians::new(TAU).to_const::<T>().value() * 0.25;
97}
98
99impl<U: AngularUnit + Copy> Quantity<U> {
100 pub const TAU: Quantity<U> = Quantity::<U>::new(U::FULL_TURN);
104 pub const FULL_TURN: Quantity<U> = Quantity::<U>::new(U::FULL_TURN);
106 pub const HALF_TURN: Quantity<U> = Quantity::<U>::new(U::HALF_TURN);
108 pub const QUARTER_TURN: Quantity<U> = Quantity::<U>::new(U::QUARTER_TURN);
110
111 #[inline]
113 pub const fn signum_const(self) -> f64 {
114 self.value().signum()
115 }
116
117 #[inline]
121 pub fn normalize(self) -> Self {
122 self.wrap_pos()
123 }
124
125 #[inline]
129 pub fn wrap_pos(self) -> Self {
130 Self::new(rem_euclid(self.value(), U::FULL_TURN))
131 }
132
133 #[inline]
139 pub fn wrap_signed(self) -> Self {
140 let full = U::FULL_TURN;
141 let half = 0.5 * full;
142 let x = self.value();
143 let y = rem_euclid(x + half, full) - half;
144 let norm = if y <= -half { y + full } else { y };
145 Self::new(norm)
146 }
147
148 #[inline]
154 pub fn wrap_signed_lo(self) -> Self {
155 let mut y = self.wrap_signed().value(); let half = 0.5 * U::FULL_TURN;
157 if y >= half {
158 y -= U::FULL_TURN;
160 }
161 Self::new(y)
162 }
163
164 #[inline]
170 pub fn wrap_quarter_fold(self) -> Self {
171 let full = U::FULL_TURN;
172 let half = 0.5 * full;
173 let quarter = 0.25 * full;
174 let y = rem_euclid(self.value() + quarter, full);
175 Self::new(quarter - (y - half).abs())
177 }
178
179 #[inline]
181 pub fn signed_separation(self, other: Self) -> Self {
182 (self - other).wrap_signed()
183 }
184
185 #[inline]
187 pub fn abs_separation(self, other: Self) -> Self {
188 let sep = self.signed_separation(other);
189 Self::new(sep.value().abs())
190 }
191}
192
193impl<U: AngularUnit + Copy, S: Transcendental> Quantity<U, S> {
198 #[inline]
203 pub fn sin(self) -> S {
204 let x_rad = self.to::<Radian>().value();
205 x_rad.sin()
206 }
207
208 #[inline]
213 pub fn cos(self) -> S {
214 let x_rad = self.to::<Radian>().value();
215 x_rad.cos()
216 }
217
218 #[inline]
223 pub fn tan(self) -> S {
224 let x_rad = self.to::<Radian>().value();
225 x_rad.tan()
226 }
227
228 #[inline]
233 pub fn sin_cos(self) -> (S, S) {
234 let x_rad = self.to::<Radian>().value();
235 x_rad.sin_cos()
236 }
237}
238
239#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
241#[unit(symbol = "°", dimension = Angular, ratio = 1.0)]
242pub struct Degree;
243pub type Deg = Degree;
245pub type Degrees = Quantity<Deg>;
247pub const DEG: Degrees = Degrees::new(1.0);
249
250#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
252#[unit(symbol = "rad", dimension = Angular, ratio = 180.0 / core::f64::consts::PI)]
253pub struct Radian;
254pub type Rad = Radian;
256pub type Radians = Quantity<Rad>;
258pub const RAD: Radians = Radians::new(1.0);
260
261#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
263#[unit(symbol = "mrad", dimension = Angular, ratio = (180.0 / core::f64::consts::PI) / 1_000.0)]
264pub struct Milliradian;
265pub type Mrad = Milliradian;
267pub type Milliradians = Quantity<Mrad>;
269pub const MRAD: Milliradians = Milliradians::new(1.0);
271
272#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
274#[unit(symbol = "′", dimension = Angular, ratio = 1.0 / 60.0)]
275pub struct Arcminute;
276pub type MOA = Arcminute;
278pub type Arcm = Arcminute;
280pub type Arcminutes = Quantity<Arcm>;
282pub const ARCM: Arcminutes = Arcminutes::new(1.0);
284
285#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
287#[unit(symbol = "″", dimension = Angular, ratio = 1.0 / 3600.0)]
288pub struct Arcsecond;
289pub type Arcs = Arcsecond;
291pub type Arcseconds = Quantity<Arcs>;
293pub const ARCS: Arcseconds = Arcseconds::new(1.0);
295
296#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
298#[unit(symbol = "mas", dimension = Angular, ratio = 1.0 / 3_600_000.0)]
299pub struct MilliArcsecond;
300pub type Mas = MilliArcsecond;
302pub type MilliArcseconds = Quantity<Mas>;
304pub const MAS: MilliArcseconds = MilliArcseconds::new(1.0);
306
307#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
309#[unit(symbol = "μas", dimension = Angular, ratio = 1.0 / 3_600_000_000.0)]
310pub struct MicroArcsecond;
311pub type Uas = MicroArcsecond;
313pub type MicroArcseconds = Quantity<Uas>;
315pub const UAS: MicroArcseconds = MicroArcseconds::new(1.0);
317
318#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
320#[unit(symbol = "gon", dimension = Angular, ratio = 0.9)]
321pub struct Gradian;
322pub type Gon = Gradian;
324pub type Gradians = Quantity<Gon>;
326pub const GON: Gradians = Gradians::new(1.0);
328
329#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
331#[unit(symbol = "turn", dimension = Angular, ratio = 360.0)]
332pub struct Turn;
333pub type Turns = Quantity<Turn>;
335pub const TURN: Turns = Turns::new(1.0);
337
338#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
340#[unit(symbol = "h", dimension = Angular, ratio = 15.0)]
341pub struct HourAngle;
342pub type Hms = HourAngle;
344pub type HourAngles = Quantity<Hms>;
346pub const HOUR_ANGLE: HourAngles = HourAngles::new(1.0);
348
349impl HourAngles {
350 pub const fn from_hms(hours: i32, minutes: u32, seconds: f64) -> Self {
360 let sign = if hours < 0 { -1.0 } else { 1.0 };
361 let h_abs = if hours < 0 { -hours } else { hours } as f64;
362 let m = minutes as f64 / 60.0;
363 let s = seconds / 3600.0;
364 let total_hours = sign * (h_abs + m + s);
365 Self::new(total_hours)
366 }
367}
368
369impl Degrees {
370 pub const fn from_dms(deg: i32, min: u32, sec: f64) -> Self {
381 let sign = if deg < 0 { -1.0 } else { 1.0 };
382 let d_abs = if deg < 0 { -deg } else { deg } as f64;
383 let m = min as f64 / 60.0;
384 let s = sec / 3600.0;
385 let total = sign * (d_abs + m + s);
386 Self::new(total)
387 }
388
389 pub const fn from_dms_sign(sign: i8, deg: u32, min: u32, sec: f64) -> Self {
393 let s = if sign < 0 { -1.0 } else { 1.0 };
394 let total = (deg as f64) + (min as f64) / 60.0 + (sec / 3600.0);
395 Self::new(s * total)
396 }
397}
398
399crate::impl_unit_from_conversions!(
401 Degree,
402 Radian,
403 Milliradian,
404 Arcminute,
405 Arcsecond,
406 MilliArcsecond,
407 MicroArcsecond,
408 Gradian,
409 Turn,
410 HourAngle
411);
412
413#[cfg(feature = "cross-unit-ops")]
415crate::impl_unit_cross_unit_ops!(
416 Degree,
417 Radian,
418 Milliradian,
419 Arcminute,
420 Arcsecond,
421 MilliArcsecond,
422 MicroArcsecond,
423 Gradian,
424 Turn,
425 HourAngle
426);
427
428#[cfg(test)]
429mod tests {
430 use super::*;
431 use approx::{assert_abs_diff_eq, assert_relative_eq};
432 use proptest::prelude::*;
433 use std::f64::consts::{PI, TAU};
434
435 #[test]
440 fn test_full_turn() {
441 assert_abs_diff_eq!(Radian::FULL_TURN, TAU, epsilon = 1e-12);
442 assert_eq!(Degree::FULL_TURN, 360.0);
443 assert_eq!(Arcsecond::FULL_TURN, 1_296_000.0);
444 }
445
446 #[test]
447 fn test_half_turn() {
448 assert_abs_diff_eq!(Radian::HALF_TURN, PI, epsilon = 1e-12);
449 assert_eq!(Degree::HALF_TURN, 180.0);
450 assert_eq!(Arcsecond::HALF_TURN, 648_000.0);
451 }
452
453 #[test]
454 fn test_quarter_turn() {
455 assert_abs_diff_eq!(Radian::QUARTER_TURN, PI / 2.0, epsilon = 1e-12);
456 assert_eq!(Degree::QUARTER_TURN, 90.0);
457 assert_eq!(Arcsecond::QUARTER_TURN, 324_000.0);
458 }
459
460 #[test]
461 fn test_quantity_constants() {
462 assert_eq!(Degrees::FULL_TURN.value(), 360.0);
463 assert_eq!(Degrees::HALF_TURN.value(), 180.0);
464 assert_eq!(Degrees::QUARTER_TURN.value(), 90.0);
465 assert_eq!(Degrees::TAU.value(), 360.0);
466 }
467
468 #[test]
473 fn conversion_degrees_to_radians() {
474 let deg = Degrees::new(180.0);
475 let rad = deg.to::<Radian>();
476 assert_abs_diff_eq!(rad.value(), PI, epsilon = 1e-12);
477 }
478
479 #[test]
480 fn conversion_radians_to_degrees() {
481 let rad = Radians::new(PI);
482 let deg = rad.to::<Degree>();
483 assert_abs_diff_eq!(deg.value(), 180.0, epsilon = 1e-12);
484 }
485
486 #[test]
487 fn conversion_degrees_to_arcseconds() {
488 let deg = Degrees::new(1.0);
489 let arcs = deg.to::<Arcsecond>();
490 assert_abs_diff_eq!(arcs.value(), 3600.0, epsilon = 1e-9);
491 }
492
493 #[test]
494 fn conversion_arcseconds_to_degrees() {
495 let arcs = Arcseconds::new(3600.0);
496 let deg = arcs.to::<Degree>();
497 assert_abs_diff_eq!(deg.value(), 1.0, epsilon = 1e-12);
498 }
499
500 #[test]
501 fn conversion_degrees_to_milliarcseconds() {
502 let deg = Degrees::new(1.0);
503 let mas = deg.to::<MilliArcsecond>();
504 assert_abs_diff_eq!(mas.value(), 3_600_000.0, epsilon = 1e-6);
505 }
506
507 #[test]
508 fn conversion_hour_angles_to_degrees() {
509 let ha = HourAngles::new(1.0);
510 let deg = ha.to::<Degree>();
511 assert_abs_diff_eq!(deg.value(), 15.0, epsilon = 1e-12);
512 }
513
514 #[test]
515 fn conversion_roundtrip() {
516 let original = Degrees::new(123.456);
517 let rad = original.to::<Radian>();
518 let back = rad.to::<Degree>();
519 assert_abs_diff_eq!(back.value(), original.value(), epsilon = 1e-12);
520 }
521
522 #[test]
523 fn from_impl_degrees_radians() {
524 let deg = Degrees::new(90.0);
525 let rad: Radians = deg.into();
526 assert_abs_diff_eq!(rad.value(), PI / 2.0, epsilon = 1e-12);
527
528 let rad2 = Radians::new(PI);
529 let deg2: Degrees = rad2.into();
530 assert_abs_diff_eq!(deg2.value(), 180.0, epsilon = 1e-12);
531 }
532
533 #[test]
538 fn test_trig() {
539 let a = Degrees::new(90.0);
540 assert!((a.sin() - 1.0).abs() < 1e-12);
541 assert!(a.cos().abs() < 1e-12);
542 }
543
544 #[test]
545 fn trig_sin_known_values() {
546 assert_abs_diff_eq!(Degrees::new(0.0).sin(), 0.0, epsilon = 1e-12);
547 assert_abs_diff_eq!(Degrees::new(30.0).sin(), 0.5, epsilon = 1e-12);
548 assert_abs_diff_eq!(Degrees::new(90.0).sin(), 1.0, epsilon = 1e-12);
549 assert_abs_diff_eq!(Degrees::new(180.0).sin(), 0.0, epsilon = 1e-12);
550 assert_abs_diff_eq!(Degrees::new(270.0).sin(), -1.0, epsilon = 1e-12);
551 }
552
553 #[test]
554 fn trig_cos_known_values() {
555 assert_abs_diff_eq!(Degrees::new(0.0).cos(), 1.0, epsilon = 1e-12);
556 assert_abs_diff_eq!(Degrees::new(60.0).cos(), 0.5, epsilon = 1e-12);
557 assert_abs_diff_eq!(Degrees::new(90.0).cos(), 0.0, epsilon = 1e-12);
558 assert_abs_diff_eq!(Degrees::new(180.0).cos(), -1.0, epsilon = 1e-12);
559 }
560
561 #[test]
562 fn trig_tan_known_values() {
563 assert_abs_diff_eq!(Degrees::new(0.0).tan(), 0.0, epsilon = 1e-12);
564 assert_abs_diff_eq!(Degrees::new(45.0).tan(), 1.0, epsilon = 1e-12);
565 assert_abs_diff_eq!(Degrees::new(180.0).tan(), 0.0, epsilon = 1e-12);
566 }
567
568 #[test]
569 fn trig_sin_cos_consistency() {
570 let angle = Degrees::new(37.5);
571 let (sin, cos) = angle.sin_cos();
572 assert_abs_diff_eq!(sin, angle.sin(), epsilon = 1e-15);
573 assert_abs_diff_eq!(cos, angle.cos(), epsilon = 1e-15);
574 }
575
576 #[test]
577 fn trig_pythagorean_identity() {
578 let angle = Degrees::new(123.456);
579 let sin = angle.sin();
580 let cos = angle.cos();
581 assert_abs_diff_eq!(sin * sin + cos * cos, 1.0, epsilon = 1e-12);
582 }
583
584 #[test]
585 fn trig_radians() {
586 assert_abs_diff_eq!(Radians::new(0.0).sin(), 0.0, epsilon = 1e-12);
587 assert_abs_diff_eq!(Radians::new(PI / 2.0).sin(), 1.0, epsilon = 1e-12);
588 assert_abs_diff_eq!(Radians::new(PI).cos(), -1.0, epsilon = 1e-12);
589 }
590
591 #[test]
596 fn signum_positive() {
597 assert_eq!(Degrees::new(45.0).signum(), 1.0);
598 }
599
600 #[test]
601 fn signum_negative() {
602 assert_eq!(Degrees::new(-45.0).signum(), -1.0);
603 }
604
605 #[test]
606 fn signum_zero() {
607 assert_eq!(Degrees::new(0.0).signum(), 1.0);
608 }
609
610 #[test]
615 fn wrap_pos_basic() {
616 assert_abs_diff_eq!(
617 Degrees::new(370.0).wrap_pos().value(),
618 10.0,
619 epsilon = 1e-12
620 );
621 assert_abs_diff_eq!(Degrees::new(720.0).wrap_pos().value(), 0.0, epsilon = 1e-12);
622 assert_abs_diff_eq!(Degrees::new(0.0).wrap_pos().value(), 0.0, epsilon = 1e-12);
623 }
624
625 #[test]
626 fn wrap_pos_negative() {
627 assert_abs_diff_eq!(
628 Degrees::new(-10.0).wrap_pos().value(),
629 350.0,
630 epsilon = 1e-12
631 );
632 assert_abs_diff_eq!(
633 Degrees::new(-370.0).wrap_pos().value(),
634 350.0,
635 epsilon = 1e-12
636 );
637 assert_abs_diff_eq!(
638 Degrees::new(-720.0).wrap_pos().value(),
639 0.0,
640 epsilon = 1e-12
641 );
642 }
643
644 #[test]
645 fn wrap_pos_boundary() {
646 assert_abs_diff_eq!(Degrees::new(360.0).wrap_pos().value(), 0.0, epsilon = 1e-12);
647 assert_abs_diff_eq!(
648 Degrees::new(-360.0).wrap_pos().value(),
649 0.0,
650 epsilon = 1e-12
651 );
652 }
653
654 #[test]
655 fn normalize_is_wrap_pos() {
656 let angle = Degrees::new(450.0);
657 assert_eq!(angle.normalize().value(), angle.wrap_pos().value());
658 }
659
660 #[test]
665 fn test_wrap_signed() {
666 let a = Degrees::new(370.0).wrap_signed();
667 assert_eq!(a.value(), 10.0);
668 let b = Degrees::new(-190.0).wrap_signed();
669 assert_eq!(b.value(), 170.0);
670 }
671
672 #[test]
673 fn wrap_signed_basic() {
674 assert_abs_diff_eq!(
675 Degrees::new(10.0).wrap_signed().value(),
676 10.0,
677 epsilon = 1e-12
678 );
679 assert_abs_diff_eq!(
680 Degrees::new(-10.0).wrap_signed().value(),
681 -10.0,
682 epsilon = 1e-12
683 );
684 }
685
686 #[test]
687 fn wrap_signed_over_180() {
688 assert_abs_diff_eq!(
689 Degrees::new(190.0).wrap_signed().value(),
690 -170.0,
691 epsilon = 1e-12
692 );
693 assert_abs_diff_eq!(
694 Degrees::new(270.0).wrap_signed().value(),
695 -90.0,
696 epsilon = 1e-12
697 );
698 }
699
700 #[test]
701 fn wrap_signed_boundary_180() {
702 assert_abs_diff_eq!(
703 Degrees::new(180.0).wrap_signed().value(),
704 180.0,
705 epsilon = 1e-12
706 );
707 assert_abs_diff_eq!(
708 Degrees::new(-180.0).wrap_signed().value(),
709 180.0,
710 epsilon = 1e-12
711 );
712 }
713
714 #[test]
715 fn wrap_signed_large_values() {
716 assert_abs_diff_eq!(
717 Degrees::new(540.0).wrap_signed().value(),
718 180.0,
719 epsilon = 1e-12
720 );
721 assert_abs_diff_eq!(
722 Degrees::new(-540.0).wrap_signed().value(),
723 180.0,
724 epsilon = 1e-12
725 );
726 }
727
728 #[test]
733 fn wrap_quarter_fold_basic() {
734 assert_abs_diff_eq!(
735 Degrees::new(0.0).wrap_quarter_fold().value(),
736 0.0,
737 epsilon = 1e-12
738 );
739 assert_abs_diff_eq!(
740 Degrees::new(45.0).wrap_quarter_fold().value(),
741 45.0,
742 epsilon = 1e-12
743 );
744 assert_abs_diff_eq!(
745 Degrees::new(-45.0).wrap_quarter_fold().value(),
746 -45.0,
747 epsilon = 1e-12
748 );
749 }
750
751 #[test]
752 fn wrap_quarter_fold_boundary() {
753 assert_abs_diff_eq!(
754 Degrees::new(90.0).wrap_quarter_fold().value(),
755 90.0,
756 epsilon = 1e-12
757 );
758 assert_abs_diff_eq!(
759 Degrees::new(-90.0).wrap_quarter_fold().value(),
760 -90.0,
761 epsilon = 1e-12
762 );
763 }
764
765 #[test]
766 fn wrap_quarter_fold_over_90() {
767 assert_abs_diff_eq!(
768 Degrees::new(100.0).wrap_quarter_fold().value(),
769 80.0,
770 epsilon = 1e-12
771 );
772 assert_abs_diff_eq!(
773 Degrees::new(135.0).wrap_quarter_fold().value(),
774 45.0,
775 epsilon = 1e-12
776 );
777 assert_abs_diff_eq!(
778 Degrees::new(180.0).wrap_quarter_fold().value(),
779 0.0,
780 epsilon = 1e-12
781 );
782 }
783
784 #[test]
789 fn signed_separation_basic() {
790 let a = Degrees::new(30.0);
791 let b = Degrees::new(50.0);
792 assert_abs_diff_eq!(a.signed_separation(b).value(), -20.0, epsilon = 1e-12);
793 assert_abs_diff_eq!(b.signed_separation(a).value(), 20.0, epsilon = 1e-12);
794 }
795
796 #[test]
797 fn signed_separation_wrap() {
798 let a = Degrees::new(10.0);
799 let b = Degrees::new(350.0);
800 assert_abs_diff_eq!(a.signed_separation(b).value(), 20.0, epsilon = 1e-12);
801 assert_abs_diff_eq!(b.signed_separation(a).value(), -20.0, epsilon = 1e-12);
802 }
803
804 #[test]
805 fn abs_separation() {
806 let a = Degrees::new(30.0);
807 let b = Degrees::new(50.0);
808 assert_abs_diff_eq!(a.abs_separation(b).value(), 20.0, epsilon = 1e-12);
809 assert_abs_diff_eq!(b.abs_separation(a).value(), 20.0, epsilon = 1e-12);
810 }
811
812 #[test]
817 fn degrees_from_dms_positive() {
818 let d = Degrees::from_dms(12, 30, 0.0);
819 assert_abs_diff_eq!(d.value(), 12.5, epsilon = 1e-12);
820 }
821
822 #[test]
823 fn degrees_from_dms_negative() {
824 let d = Degrees::from_dms(-33, 52, 0.0);
825 assert!(d.value() < 0.0);
826 assert_abs_diff_eq!(d.value(), -(33.0 + 52.0 / 60.0), epsilon = 1e-12);
827 }
828
829 #[test]
830 fn degrees_from_dms_with_seconds() {
831 let d = Degrees::from_dms(10, 20, 30.0);
832 assert_abs_diff_eq!(
833 d.value(),
834 10.0 + 20.0 / 60.0 + 30.0 / 3600.0,
835 epsilon = 1e-12
836 );
837 }
838
839 #[test]
840 fn degrees_from_dms_sign() {
841 let pos = Degrees::from_dms_sign(1, 45, 30, 0.0);
842 let neg = Degrees::from_dms_sign(-1, 45, 30, 0.0);
843 assert_abs_diff_eq!(pos.value(), 45.5, epsilon = 1e-12);
844 assert_abs_diff_eq!(neg.value(), -45.5, epsilon = 1e-12);
845 }
846
847 #[test]
848 fn hour_angles_from_hms() {
849 let ha = HourAngles::from_hms(5, 30, 0.0);
850 assert_abs_diff_eq!(ha.value(), 5.5, epsilon = 1e-12);
851 }
852
853 #[test]
854 fn hour_angles_from_hms_negative() {
855 let ha = HourAngles::from_hms(-3, 15, 0.0);
856 assert_abs_diff_eq!(ha.value(), -3.25, epsilon = 1e-12);
857 }
858
859 #[test]
860 fn hour_angles_to_degrees() {
861 let ha = HourAngles::new(6.0);
862 let deg = ha.to::<Degree>();
863 assert_abs_diff_eq!(deg.value(), 90.0, epsilon = 1e-12);
864 }
865
866 #[test]
871 fn display_degrees() {
872 let d = Degrees::new(45.5);
873 assert_eq!(format!("{}", d), "45.5 °");
874 }
875
876 #[test]
877 fn display_radians() {
878 let r = Radians::new(1.0);
879 assert_eq!(format!("{}", r), "1 rad");
880 }
881
882 #[test]
887 fn unit_constants() {
888 assert_eq!(DEG.value(), 1.0);
889 assert_eq!(RAD.value(), 1.0);
890 assert_eq!(MRAD.value(), 1.0);
891 assert_eq!(ARCM.value(), 1.0);
892 assert_eq!(ARCS.value(), 1.0);
893 assert_eq!(MAS.value(), 1.0);
894 assert_eq!(UAS.value(), 1.0);
895 assert_eq!(GON.value(), 1.0);
896 assert_eq!(TURN.value(), 1.0);
897 assert_eq!(HOUR_ANGLE.value(), 1.0);
898 }
899
900 #[test]
905 fn wrap_signed_lo_boundary_half_turn() {
906 assert_abs_diff_eq!(
908 Degrees::new(180.0).wrap_signed_lo().value(),
909 -180.0,
910 epsilon = 1e-12
911 );
912 assert_abs_diff_eq!(
913 Degrees::new(-180.0).wrap_signed_lo().value(),
914 -180.0,
915 epsilon = 1e-12
916 );
917 }
918
919 #[test]
924 fn conversion_degrees_to_arcminutes() {
925 let deg = Degrees::new(1.0);
926 let arcm = deg.to::<Arcminute>();
927 assert_abs_diff_eq!(arcm.value(), 60.0, epsilon = 1e-12);
928 }
929
930 #[test]
931 fn conversion_arcminutes_to_degrees() {
932 let arcm = Arcminutes::new(60.0);
933 let deg = arcm.to::<Degree>();
934 assert_abs_diff_eq!(deg.value(), 1.0, epsilon = 1e-12);
935 }
936
937 #[test]
938 fn conversion_arcminutes_to_arcseconds() {
939 let arcm = Arcminutes::new(1.0);
940 let arcs = arcm.to::<Arcsecond>();
941 assert_abs_diff_eq!(arcs.value(), 60.0, epsilon = 1e-12);
942 }
943
944 #[test]
945 fn conversion_arcseconds_to_microarcseconds() {
946 let arcs = Arcseconds::new(1.0);
947 let uas = arcs.to::<MicroArcsecond>();
948 assert_abs_diff_eq!(uas.value(), 1_000_000.0, epsilon = 1e-6);
949 }
950
951 #[test]
952 fn conversion_microarcseconds_to_degrees() {
953 let uas = MicroArcseconds::new(3_600_000_000.0);
954 let deg = uas.to::<Degree>();
955 assert_abs_diff_eq!(deg.value(), 1.0, epsilon = 1e-9);
956 }
957
958 #[test]
959 fn conversion_degrees_to_gradians() {
960 let deg = Degrees::new(90.0);
961 let gon = deg.to::<Gradian>();
962 assert_abs_diff_eq!(gon.value(), 100.0, epsilon = 1e-12);
963 }
964
965 #[test]
966 fn conversion_gradians_to_degrees() {
967 let gon = Gradians::new(400.0);
968 let deg = gon.to::<Degree>();
969 assert_abs_diff_eq!(deg.value(), 360.0, epsilon = 1e-12);
970 }
971
972 #[test]
973 fn conversion_gradians_to_radians() {
974 let gon = Gradians::new(200.0);
975 let rad = gon.to::<Radian>();
976 assert_abs_diff_eq!(rad.value(), PI, epsilon = 1e-12);
977 }
978
979 #[test]
980 fn conversion_degrees_to_turns() {
981 let deg = Degrees::new(360.0);
982 let turn = deg.to::<Turn>();
983 assert_abs_diff_eq!(turn.value(), 1.0, epsilon = 1e-12);
984 }
985
986 #[test]
987 fn conversion_milliradians_to_radians() {
988 let mrad = Milliradians::new(1_000.0);
989 let rad = mrad.to::<Radian>();
990 assert_abs_diff_eq!(rad.value(), 1.0, epsilon = 1e-12);
991 }
992
993 #[test]
994 fn conversion_turns_to_degrees() {
995 let turn = Turns::new(2.5);
996 let deg = turn.to::<Degree>();
997 assert_abs_diff_eq!(deg.value(), 900.0, epsilon = 1e-12);
998 }
999
1000 #[test]
1001 fn conversion_turns_to_radians() {
1002 let turn = Turns::new(1.0);
1003 let rad = turn.to::<Radian>();
1004 assert_abs_diff_eq!(rad.value(), TAU, epsilon = 1e-12);
1005 }
1006
1007 #[test]
1008 fn from_impl_new_units() {
1009 let deg = Degrees::new(1.0);
1011 let arcm: Arcminutes = deg.into();
1012 assert_abs_diff_eq!(arcm.value(), 60.0, epsilon = 1e-12);
1013
1014 let gon = Gradians::new(100.0);
1015 let deg2: Degrees = gon.into();
1016 assert_abs_diff_eq!(deg2.value(), 90.0, epsilon = 1e-12);
1017
1018 let turn = Turns::new(0.25);
1019 let deg3: Degrees = turn.into();
1020 assert_abs_diff_eq!(deg3.value(), 90.0, epsilon = 1e-12);
1021 }
1022
1023 #[test]
1024 fn roundtrip_arcminute_arcsecond() {
1025 let original = Arcminutes::new(5.0);
1026 let arcs = original.to::<Arcsecond>();
1027 let back = arcs.to::<Arcminute>();
1028 assert_abs_diff_eq!(back.value(), original.value(), epsilon = 1e-12);
1029 }
1030
1031 #[test]
1032 fn roundtrip_gradian_degree() {
1033 let original = Gradians::new(123.456);
1034 let deg = original.to::<Degree>();
1035 let back = deg.to::<Gradian>();
1036 assert_abs_diff_eq!(back.value(), original.value(), epsilon = 1e-12);
1037 }
1038
1039 #[test]
1040 fn roundtrip_turn_radian() {
1041 let original = Turns::new(2.717);
1042 let rad = original.to::<Radian>();
1043 let back = rad.to::<Turn>();
1044 assert_abs_diff_eq!(back.value(), original.value(), epsilon = 1e-12);
1045 }
1046
1047 #[test]
1048 fn gradian_full_turn() {
1049 assert_abs_diff_eq!(Gradian::FULL_TURN, 400.0, epsilon = 1e-12);
1050 }
1051
1052 #[test]
1053 fn turn_full_turn() {
1054 assert_abs_diff_eq!(Turn::FULL_TURN, 1.0, epsilon = 1e-12);
1055 }
1056
1057 #[test]
1058 fn arcminute_full_turn() {
1059 assert_abs_diff_eq!(Arcminute::FULL_TURN, 21_600.0, epsilon = 1e-9);
1060 }
1061
1062 #[test]
1063 fn microarcsecond_conversion_chain() {
1064 let uas = MicroArcseconds::new(1e9);
1066 let mas = uas.to::<MilliArcsecond>();
1067 let arcs = mas.to::<Arcsecond>();
1068 let arcm = arcs.to::<Arcminute>();
1069 let deg = arcm.to::<Degree>();
1070
1071 assert_abs_diff_eq!(mas.value(), 1_000_000.0, epsilon = 1e-6);
1072 assert_abs_diff_eq!(arcs.value(), 1_000.0, epsilon = 1e-9);
1073 assert_abs_diff_eq!(arcm.value(), 1_000.0 / 60.0, epsilon = 1e-9);
1074 assert_relative_eq!(deg.value(), 1_000.0 / 3600.0, max_relative = 1e-9);
1075 }
1076
1077 #[test]
1078 fn wrap_pos_with_turns() {
1079 let turn = Turns::new(2.7);
1080 let wrapped = turn.wrap_pos();
1081 assert_abs_diff_eq!(wrapped.value(), 0.7, epsilon = 1e-12);
1082 }
1083
1084 #[test]
1085 fn wrap_signed_with_gradians() {
1086 let gon = Gradians::new(350.0);
1087 let wrapped = gon.wrap_signed();
1088 assert_abs_diff_eq!(wrapped.value(), -50.0, epsilon = 1e-12);
1089 }
1090
1091 #[test]
1092 fn trig_with_gradians() {
1093 let gon = Gradians::new(100.0); assert_abs_diff_eq!(gon.sin(), 1.0, epsilon = 1e-12);
1095 assert_abs_diff_eq!(gon.cos(), 0.0, epsilon = 1e-12);
1096 }
1097
1098 #[test]
1099 fn trig_with_turns() {
1100 let turn = Turns::new(0.25); assert_abs_diff_eq!(turn.sin(), 1.0, epsilon = 1e-12);
1102 assert_abs_diff_eq!(turn.cos(), 0.0, epsilon = 1e-12);
1103 }
1104
1105 #[test]
1106 fn all_units_to_degrees() {
1107 assert_abs_diff_eq!(
1109 Radians::new(PI).to::<Degree>().value(),
1110 180.0,
1111 epsilon = 1e-12
1112 );
1113 assert_abs_diff_eq!(
1114 Arcminutes::new(60.0).to::<Degree>().value(),
1115 1.0,
1116 epsilon = 1e-12
1117 );
1118 assert_abs_diff_eq!(
1119 Arcseconds::new(3600.0).to::<Degree>().value(),
1120 1.0,
1121 epsilon = 1e-12
1122 );
1123 assert_abs_diff_eq!(
1124 MilliArcseconds::new(3_600_000.0).to::<Degree>().value(),
1125 1.0,
1126 epsilon = 1e-9
1127 );
1128 assert_abs_diff_eq!(
1129 MicroArcseconds::new(3_600_000_000.0).to::<Degree>().value(),
1130 1.0,
1131 epsilon = 1e-6
1132 );
1133 assert_abs_diff_eq!(
1134 Gradians::new(100.0).to::<Degree>().value(),
1135 90.0,
1136 epsilon = 1e-12
1137 );
1138 assert_abs_diff_eq!(
1139 Turns::new(1.0).to::<Degree>().value(),
1140 360.0,
1141 epsilon = 1e-12
1142 );
1143 assert_abs_diff_eq!(
1144 HourAngles::new(1.0).to::<Degree>().value(),
1145 15.0,
1146 epsilon = 1e-12
1147 );
1148 }
1149
1150 proptest! {
1155 #[test]
1156 fn prop_wrap_pos_range(angle in -1e6..1e6f64) {
1157 let wrapped = Degrees::new(angle).wrap_pos();
1158 prop_assert!(wrapped.value() >= 0.0);
1159 prop_assert!(wrapped.value() < 360.0);
1160 }
1161
1162 #[test]
1163 fn prop_wrap_signed_range(angle in -1e6..1e6f64) {
1164 let wrapped = Degrees::new(angle).wrap_signed();
1165 prop_assert!(wrapped.value() > -180.0);
1166 prop_assert!(wrapped.value() <= 180.0);
1167 }
1168
1169 #[test]
1170 fn prop_wrap_quarter_fold_range(angle in -1e6..1e6f64) {
1171 let wrapped = Degrees::new(angle).wrap_quarter_fold();
1172 prop_assert!(wrapped.value() >= -90.0);
1173 prop_assert!(wrapped.value() <= 90.0);
1174 }
1175
1176 #[test]
1177 fn prop_pythagorean_identity(angle in -360.0..360.0f64) {
1178 let a = Degrees::new(angle);
1179 let sin = a.sin();
1180 let cos = a.cos();
1181 assert_abs_diff_eq!(sin * sin + cos * cos, 1.0, epsilon = 1e-12);
1182 }
1183
1184 #[test]
1185 fn prop_conversion_roundtrip(angle in -1e6..1e6f64) {
1186 let deg = Degrees::new(angle);
1187 let rad = deg.to::<Radian>();
1188 let back = rad.to::<Degree>();
1189 assert_relative_eq!(back.value(), deg.value(), max_relative = 1e-12);
1190 }
1191
1192 #[test]
1193 fn prop_abs_separation_symmetric(a in -360.0..360.0f64, b in -360.0..360.0f64) {
1194 let da = Degrees::new(a);
1195 let db = Degrees::new(b);
1196 assert_abs_diff_eq!(
1197 da.abs_separation(db).value(),
1198 db.abs_separation(da).value(),
1199 epsilon = 1e-12
1200 );
1201 }
1202 }
1203
1204 #[test]
1207 fn derive_coverage_unit_structs() {
1208 assert!(Degree == Degree);
1211 assert!(Radian == Radian);
1212 assert!(Milliradian == Milliradian);
1213 assert!(Arcminute == Arcminute);
1214 assert!(Arcsecond == Arcsecond);
1215 assert!(MilliArcsecond == MilliArcsecond);
1216 assert!(MicroArcsecond == MicroArcsecond);
1217 assert!(Gradian == Gradian);
1218 assert!(Turn == Turn);
1219 assert!(HourAngle == HourAngle);
1220 let pos = Degrees::new(90.0);
1222 let neg = Degrees::new(-45.0);
1223 assert_eq!(pos.signum_const(), 1.0);
1224 assert_eq!(neg.signum_const(), -1.0);
1225 }
1226}