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