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_from_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(feature = "cross-unit-ops")]
417crate::impl_unit_cross_unit_ops!(
418 Degree,
419 Radian,
420 Milliradian,
421 Arcminute,
422 Arcsecond,
423 MilliArcsecond,
424 MicroArcsecond,
425 Gradian,
426 Turn,
427 HourAngle
428);
429
430#[cfg(test)]
431mod tests {
432 use super::*;
433 use approx::{assert_abs_diff_eq, assert_relative_eq};
434 use proptest::prelude::*;
435 use std::f64::consts::{PI, TAU};
436
437 #[test]
442 fn test_full_turn() {
443 assert_abs_diff_eq!(Radian::FULL_TURN, TAU, epsilon = 1e-12);
444 assert_eq!(Degree::FULL_TURN, 360.0);
445 assert_eq!(Arcsecond::FULL_TURN, 1_296_000.0);
446 }
447
448 #[test]
449 fn test_half_turn() {
450 assert_abs_diff_eq!(Radian::HALF_TURN, PI, epsilon = 1e-12);
451 assert_eq!(Degree::HALF_TURN, 180.0);
452 assert_eq!(Arcsecond::HALF_TURN, 648_000.0);
453 }
454
455 #[test]
456 fn test_quarter_turn() {
457 assert_abs_diff_eq!(Radian::QUARTED_TURN, PI / 2.0, epsilon = 1e-12);
458 assert_eq!(Degree::QUARTED_TURN, 90.0);
459 assert_eq!(Arcsecond::QUARTED_TURN, 324_000.0);
460 }
461
462 #[test]
463 fn test_quantity_constants() {
464 assert_eq!(Degrees::FULL_TURN.value(), 360.0);
465 assert_eq!(Degrees::HALF_TURN.value(), 180.0);
466 assert_eq!(Degrees::QUARTED_TURN.value(), 90.0);
467 assert_eq!(Degrees::TAU.value(), 360.0);
468 }
469
470 #[test]
475 fn conversion_degrees_to_radians() {
476 let deg = Degrees::new(180.0);
477 let rad = deg.to::<Radian>();
478 assert_abs_diff_eq!(rad.value(), PI, epsilon = 1e-12);
479 }
480
481 #[test]
482 fn conversion_radians_to_degrees() {
483 let rad = Radians::new(PI);
484 let deg = rad.to::<Degree>();
485 assert_abs_diff_eq!(deg.value(), 180.0, epsilon = 1e-12);
486 }
487
488 #[test]
489 fn conversion_degrees_to_arcseconds() {
490 let deg = Degrees::new(1.0);
491 let arcs = deg.to::<Arcsecond>();
492 assert_abs_diff_eq!(arcs.value(), 3600.0, epsilon = 1e-9);
493 }
494
495 #[test]
496 fn conversion_arcseconds_to_degrees() {
497 let arcs = Arcseconds::new(3600.0);
498 let deg = arcs.to::<Degree>();
499 assert_abs_diff_eq!(deg.value(), 1.0, epsilon = 1e-12);
500 }
501
502 #[test]
503 fn conversion_degrees_to_milliarcseconds() {
504 let deg = Degrees::new(1.0);
505 let mas = deg.to::<MilliArcsecond>();
506 assert_abs_diff_eq!(mas.value(), 3_600_000.0, epsilon = 1e-6);
507 }
508
509 #[test]
510 fn conversion_hour_angles_to_degrees() {
511 let ha = HourAngles::new(1.0);
512 let deg = ha.to::<Degree>();
513 assert_abs_diff_eq!(deg.value(), 15.0, epsilon = 1e-12);
514 }
515
516 #[test]
517 fn conversion_roundtrip() {
518 let original = Degrees::new(123.456);
519 let rad = original.to::<Radian>();
520 let back = rad.to::<Degree>();
521 assert_abs_diff_eq!(back.value(), original.value(), epsilon = 1e-12);
522 }
523
524 #[test]
525 fn from_impl_degrees_radians() {
526 let deg = Degrees::new(90.0);
527 let rad: Radians = deg.into();
528 assert_abs_diff_eq!(rad.value(), PI / 2.0, epsilon = 1e-12);
529
530 let rad2 = Radians::new(PI);
531 let deg2: Degrees = rad2.into();
532 assert_abs_diff_eq!(deg2.value(), 180.0, epsilon = 1e-12);
533 }
534
535 #[test]
540 fn test_trig() {
541 let a = Degrees::new(90.0);
542 assert!((a.sin() - 1.0).abs() < 1e-12);
543 assert!(a.cos().abs() < 1e-12);
544 }
545
546 #[test]
547 fn trig_sin_known_values() {
548 assert_abs_diff_eq!(Degrees::new(0.0).sin(), 0.0, epsilon = 1e-12);
549 assert_abs_diff_eq!(Degrees::new(30.0).sin(), 0.5, epsilon = 1e-12);
550 assert_abs_diff_eq!(Degrees::new(90.0).sin(), 1.0, epsilon = 1e-12);
551 assert_abs_diff_eq!(Degrees::new(180.0).sin(), 0.0, epsilon = 1e-12);
552 assert_abs_diff_eq!(Degrees::new(270.0).sin(), -1.0, epsilon = 1e-12);
553 }
554
555 #[test]
556 fn trig_cos_known_values() {
557 assert_abs_diff_eq!(Degrees::new(0.0).cos(), 1.0, epsilon = 1e-12);
558 assert_abs_diff_eq!(Degrees::new(60.0).cos(), 0.5, epsilon = 1e-12);
559 assert_abs_diff_eq!(Degrees::new(90.0).cos(), 0.0, epsilon = 1e-12);
560 assert_abs_diff_eq!(Degrees::new(180.0).cos(), -1.0, epsilon = 1e-12);
561 }
562
563 #[test]
564 fn trig_tan_known_values() {
565 assert_abs_diff_eq!(Degrees::new(0.0).tan(), 0.0, epsilon = 1e-12);
566 assert_abs_diff_eq!(Degrees::new(45.0).tan(), 1.0, epsilon = 1e-12);
567 assert_abs_diff_eq!(Degrees::new(180.0).tan(), 0.0, epsilon = 1e-12);
568 }
569
570 #[test]
571 fn trig_sin_cos_consistency() {
572 let angle = Degrees::new(37.5);
573 let (sin, cos) = angle.sin_cos();
574 assert_abs_diff_eq!(sin, angle.sin(), epsilon = 1e-15);
575 assert_abs_diff_eq!(cos, angle.cos(), epsilon = 1e-15);
576 }
577
578 #[test]
579 fn trig_pythagorean_identity() {
580 let angle = Degrees::new(123.456);
581 let sin = angle.sin();
582 let cos = angle.cos();
583 assert_abs_diff_eq!(sin * sin + cos * cos, 1.0, epsilon = 1e-12);
584 }
585
586 #[test]
587 fn trig_radians() {
588 assert_abs_diff_eq!(Radians::new(0.0).sin(), 0.0, epsilon = 1e-12);
589 assert_abs_diff_eq!(Radians::new(PI / 2.0).sin(), 1.0, epsilon = 1e-12);
590 assert_abs_diff_eq!(Radians::new(PI).cos(), -1.0, epsilon = 1e-12);
591 }
592
593 #[test]
598 fn signum_positive() {
599 assert_eq!(Degrees::new(45.0).signum(), 1.0);
600 }
601
602 #[test]
603 fn signum_negative() {
604 assert_eq!(Degrees::new(-45.0).signum(), -1.0);
605 }
606
607 #[test]
608 fn signum_zero() {
609 assert_eq!(Degrees::new(0.0).signum(), 1.0);
610 }
611
612 #[test]
617 fn wrap_pos_basic() {
618 assert_abs_diff_eq!(
619 Degrees::new(370.0).wrap_pos().value(),
620 10.0,
621 epsilon = 1e-12
622 );
623 assert_abs_diff_eq!(Degrees::new(720.0).wrap_pos().value(), 0.0, epsilon = 1e-12);
624 assert_abs_diff_eq!(Degrees::new(0.0).wrap_pos().value(), 0.0, epsilon = 1e-12);
625 }
626
627 #[test]
628 fn wrap_pos_negative() {
629 assert_abs_diff_eq!(
630 Degrees::new(-10.0).wrap_pos().value(),
631 350.0,
632 epsilon = 1e-12
633 );
634 assert_abs_diff_eq!(
635 Degrees::new(-370.0).wrap_pos().value(),
636 350.0,
637 epsilon = 1e-12
638 );
639 assert_abs_diff_eq!(
640 Degrees::new(-720.0).wrap_pos().value(),
641 0.0,
642 epsilon = 1e-12
643 );
644 }
645
646 #[test]
647 fn wrap_pos_boundary() {
648 assert_abs_diff_eq!(Degrees::new(360.0).wrap_pos().value(), 0.0, epsilon = 1e-12);
649 assert_abs_diff_eq!(
650 Degrees::new(-360.0).wrap_pos().value(),
651 0.0,
652 epsilon = 1e-12
653 );
654 }
655
656 #[test]
657 fn normalize_is_wrap_pos() {
658 let angle = Degrees::new(450.0);
659 assert_eq!(angle.normalize().value(), angle.wrap_pos().value());
660 }
661
662 #[test]
667 fn test_wrap_signed() {
668 let a = Degrees::new(370.0).wrap_signed();
669 assert_eq!(a.value(), 10.0);
670 let b = Degrees::new(-190.0).wrap_signed();
671 assert_eq!(b.value(), 170.0);
672 }
673
674 #[test]
675 fn wrap_signed_basic() {
676 assert_abs_diff_eq!(
677 Degrees::new(10.0).wrap_signed().value(),
678 10.0,
679 epsilon = 1e-12
680 );
681 assert_abs_diff_eq!(
682 Degrees::new(-10.0).wrap_signed().value(),
683 -10.0,
684 epsilon = 1e-12
685 );
686 }
687
688 #[test]
689 fn wrap_signed_over_180() {
690 assert_abs_diff_eq!(
691 Degrees::new(190.0).wrap_signed().value(),
692 -170.0,
693 epsilon = 1e-12
694 );
695 assert_abs_diff_eq!(
696 Degrees::new(270.0).wrap_signed().value(),
697 -90.0,
698 epsilon = 1e-12
699 );
700 }
701
702 #[test]
703 fn wrap_signed_boundary_180() {
704 assert_abs_diff_eq!(
705 Degrees::new(180.0).wrap_signed().value(),
706 180.0,
707 epsilon = 1e-12
708 );
709 assert_abs_diff_eq!(
710 Degrees::new(-180.0).wrap_signed().value(),
711 180.0,
712 epsilon = 1e-12
713 );
714 }
715
716 #[test]
717 fn wrap_signed_large_values() {
718 assert_abs_diff_eq!(
719 Degrees::new(540.0).wrap_signed().value(),
720 180.0,
721 epsilon = 1e-12
722 );
723 assert_abs_diff_eq!(
724 Degrees::new(-540.0).wrap_signed().value(),
725 180.0,
726 epsilon = 1e-12
727 );
728 }
729
730 #[test]
735 fn wrap_quarter_fold_basic() {
736 assert_abs_diff_eq!(
737 Degrees::new(0.0).wrap_quarter_fold().value(),
738 0.0,
739 epsilon = 1e-12
740 );
741 assert_abs_diff_eq!(
742 Degrees::new(45.0).wrap_quarter_fold().value(),
743 45.0,
744 epsilon = 1e-12
745 );
746 assert_abs_diff_eq!(
747 Degrees::new(-45.0).wrap_quarter_fold().value(),
748 -45.0,
749 epsilon = 1e-12
750 );
751 }
752
753 #[test]
754 fn wrap_quarter_fold_boundary() {
755 assert_abs_diff_eq!(
756 Degrees::new(90.0).wrap_quarter_fold().value(),
757 90.0,
758 epsilon = 1e-12
759 );
760 assert_abs_diff_eq!(
761 Degrees::new(-90.0).wrap_quarter_fold().value(),
762 -90.0,
763 epsilon = 1e-12
764 );
765 }
766
767 #[test]
768 fn wrap_quarter_fold_over_90() {
769 assert_abs_diff_eq!(
770 Degrees::new(100.0).wrap_quarter_fold().value(),
771 80.0,
772 epsilon = 1e-12
773 );
774 assert_abs_diff_eq!(
775 Degrees::new(135.0).wrap_quarter_fold().value(),
776 45.0,
777 epsilon = 1e-12
778 );
779 assert_abs_diff_eq!(
780 Degrees::new(180.0).wrap_quarter_fold().value(),
781 0.0,
782 epsilon = 1e-12
783 );
784 }
785
786 #[test]
791 fn signed_separation_basic() {
792 let a = Degrees::new(30.0);
793 let b = Degrees::new(50.0);
794 assert_abs_diff_eq!(a.signed_separation(b).value(), -20.0, epsilon = 1e-12);
795 assert_abs_diff_eq!(b.signed_separation(a).value(), 20.0, epsilon = 1e-12);
796 }
797
798 #[test]
799 fn signed_separation_wrap() {
800 let a = Degrees::new(10.0);
801 let b = Degrees::new(350.0);
802 assert_abs_diff_eq!(a.signed_separation(b).value(), 20.0, epsilon = 1e-12);
803 assert_abs_diff_eq!(b.signed_separation(a).value(), -20.0, epsilon = 1e-12);
804 }
805
806 #[test]
807 fn abs_separation() {
808 let a = Degrees::new(30.0);
809 let b = Degrees::new(50.0);
810 assert_abs_diff_eq!(a.abs_separation(b).value(), 20.0, epsilon = 1e-12);
811 assert_abs_diff_eq!(b.abs_separation(a).value(), 20.0, epsilon = 1e-12);
812 }
813
814 #[test]
819 fn degrees_from_dms_positive() {
820 let d = Degrees::from_dms(12, 30, 0.0);
821 assert_abs_diff_eq!(d.value(), 12.5, epsilon = 1e-12);
822 }
823
824 #[test]
825 fn degrees_from_dms_negative() {
826 let d = Degrees::from_dms(-33, 52, 0.0);
827 assert!(d.value() < 0.0);
828 assert_abs_diff_eq!(d.value(), -(33.0 + 52.0 / 60.0), epsilon = 1e-12);
829 }
830
831 #[test]
832 fn degrees_from_dms_with_seconds() {
833 let d = Degrees::from_dms(10, 20, 30.0);
834 assert_abs_diff_eq!(
835 d.value(),
836 10.0 + 20.0 / 60.0 + 30.0 / 3600.0,
837 epsilon = 1e-12
838 );
839 }
840
841 #[test]
842 fn degrees_from_dms_sign() {
843 let pos = Degrees::from_dms_sign(1, 45, 30, 0.0);
844 let neg = Degrees::from_dms_sign(-1, 45, 30, 0.0);
845 assert_abs_diff_eq!(pos.value(), 45.5, epsilon = 1e-12);
846 assert_abs_diff_eq!(neg.value(), -45.5, epsilon = 1e-12);
847 }
848
849 #[test]
850 fn hour_angles_from_hms() {
851 let ha = HourAngles::from_hms(5, 30, 0.0);
852 assert_abs_diff_eq!(ha.value(), 5.5, epsilon = 1e-12);
853 }
854
855 #[test]
856 fn hour_angles_from_hms_negative() {
857 let ha = HourAngles::from_hms(-3, 15, 0.0);
858 assert_abs_diff_eq!(ha.value(), -3.25, epsilon = 1e-12);
859 }
860
861 #[test]
862 fn hour_angles_to_degrees() {
863 let ha = HourAngles::new(6.0);
864 let deg = ha.to::<Degree>();
865 assert_abs_diff_eq!(deg.value(), 90.0, epsilon = 1e-12);
866 }
867
868 #[test]
873 fn display_degrees() {
874 let d = Degrees::new(45.5);
875 assert_eq!(format!("{}", d), "45.5 °");
876 }
877
878 #[test]
879 fn display_radians() {
880 let r = Radians::new(1.0);
881 assert_eq!(format!("{}", r), "1 rad");
882 }
883
884 #[test]
889 fn unit_constants() {
890 assert_eq!(DEG.value(), 1.0);
891 assert_eq!(RAD.value(), 1.0);
892 assert_eq!(MRAD.value(), 1.0);
893 assert_eq!(ARCM.value(), 1.0);
894 assert_eq!(ARCS.value(), 1.0);
895 assert_eq!(MAS.value(), 1.0);
896 assert_eq!(UAS.value(), 1.0);
897 assert_eq!(GON.value(), 1.0);
898 assert_eq!(TURN.value(), 1.0);
899 assert_eq!(HOUR_ANGLE.value(), 1.0);
900 }
901
902 #[test]
907 fn wrap_signed_lo_boundary_half_turn() {
908 assert_abs_diff_eq!(
910 Degrees::new(180.0).wrap_signed_lo().value(),
911 -180.0,
912 epsilon = 1e-12
913 );
914 assert_abs_diff_eq!(
915 Degrees::new(-180.0).wrap_signed_lo().value(),
916 -180.0,
917 epsilon = 1e-12
918 );
919 }
920
921 #[test]
926 fn conversion_degrees_to_arcminutes() {
927 let deg = Degrees::new(1.0);
928 let arcm = deg.to::<Arcminute>();
929 assert_abs_diff_eq!(arcm.value(), 60.0, epsilon = 1e-12);
930 }
931
932 #[test]
933 fn conversion_arcminutes_to_degrees() {
934 let arcm = Arcminutes::new(60.0);
935 let deg = arcm.to::<Degree>();
936 assert_abs_diff_eq!(deg.value(), 1.0, epsilon = 1e-12);
937 }
938
939 #[test]
940 fn conversion_arcminutes_to_arcseconds() {
941 let arcm = Arcminutes::new(1.0);
942 let arcs = arcm.to::<Arcsecond>();
943 assert_abs_diff_eq!(arcs.value(), 60.0, epsilon = 1e-12);
944 }
945
946 #[test]
947 fn conversion_arcseconds_to_microarcseconds() {
948 let arcs = Arcseconds::new(1.0);
949 let uas = arcs.to::<MicroArcsecond>();
950 assert_abs_diff_eq!(uas.value(), 1_000_000.0, epsilon = 1e-6);
951 }
952
953 #[test]
954 fn conversion_microarcseconds_to_degrees() {
955 let uas = MicroArcseconds::new(3_600_000_000.0);
956 let deg = uas.to::<Degree>();
957 assert_abs_diff_eq!(deg.value(), 1.0, epsilon = 1e-9);
958 }
959
960 #[test]
961 fn conversion_degrees_to_gradians() {
962 let deg = Degrees::new(90.0);
963 let gon = deg.to::<Gradian>();
964 assert_abs_diff_eq!(gon.value(), 100.0, epsilon = 1e-12);
965 }
966
967 #[test]
968 fn conversion_gradians_to_degrees() {
969 let gon = Gradians::new(400.0);
970 let deg = gon.to::<Degree>();
971 assert_abs_diff_eq!(deg.value(), 360.0, epsilon = 1e-12);
972 }
973
974 #[test]
975 fn conversion_gradians_to_radians() {
976 let gon = Gradians::new(200.0);
977 let rad = gon.to::<Radian>();
978 assert_abs_diff_eq!(rad.value(), PI, epsilon = 1e-12);
979 }
980
981 #[test]
982 fn conversion_degrees_to_turns() {
983 let deg = Degrees::new(360.0);
984 let turn = deg.to::<Turn>();
985 assert_abs_diff_eq!(turn.value(), 1.0, epsilon = 1e-12);
986 }
987
988 #[test]
989 fn conversion_milliradians_to_radians() {
990 let mrad = Milliradians::new(1_000.0);
991 let rad = mrad.to::<Radian>();
992 assert_abs_diff_eq!(rad.value(), 1.0, epsilon = 1e-12);
993 }
994
995 #[test]
996 fn conversion_turns_to_degrees() {
997 let turn = Turns::new(2.5);
998 let deg = turn.to::<Degree>();
999 assert_abs_diff_eq!(deg.value(), 900.0, epsilon = 1e-12);
1000 }
1001
1002 #[test]
1003 fn conversion_turns_to_radians() {
1004 let turn = Turns::new(1.0);
1005 let rad = turn.to::<Radian>();
1006 assert_abs_diff_eq!(rad.value(), TAU, epsilon = 1e-12);
1007 }
1008
1009 #[test]
1010 fn from_impl_new_units() {
1011 let deg = Degrees::new(1.0);
1013 let arcm: Arcminutes = deg.into();
1014 assert_abs_diff_eq!(arcm.value(), 60.0, epsilon = 1e-12);
1015
1016 let gon = Gradians::new(100.0);
1017 let deg2: Degrees = gon.into();
1018 assert_abs_diff_eq!(deg2.value(), 90.0, epsilon = 1e-12);
1019
1020 let turn = Turns::new(0.25);
1021 let deg3: Degrees = turn.into();
1022 assert_abs_diff_eq!(deg3.value(), 90.0, epsilon = 1e-12);
1023 }
1024
1025 #[test]
1026 fn roundtrip_arcminute_arcsecond() {
1027 let original = Arcminutes::new(5.0);
1028 let arcs = original.to::<Arcsecond>();
1029 let back = arcs.to::<Arcminute>();
1030 assert_abs_diff_eq!(back.value(), original.value(), epsilon = 1e-12);
1031 }
1032
1033 #[test]
1034 fn roundtrip_gradian_degree() {
1035 let original = Gradians::new(123.456);
1036 let deg = original.to::<Degree>();
1037 let back = deg.to::<Gradian>();
1038 assert_abs_diff_eq!(back.value(), original.value(), epsilon = 1e-12);
1039 }
1040
1041 #[test]
1042 fn roundtrip_turn_radian() {
1043 let original = Turns::new(2.717);
1044 let rad = original.to::<Radian>();
1045 let back = rad.to::<Turn>();
1046 assert_abs_diff_eq!(back.value(), original.value(), epsilon = 1e-12);
1047 }
1048
1049 #[test]
1050 fn gradian_full_turn() {
1051 assert_abs_diff_eq!(Gradian::FULL_TURN, 400.0, epsilon = 1e-12);
1052 }
1053
1054 #[test]
1055 fn turn_full_turn() {
1056 assert_abs_diff_eq!(Turn::FULL_TURN, 1.0, epsilon = 1e-12);
1057 }
1058
1059 #[test]
1060 fn arcminute_full_turn() {
1061 assert_abs_diff_eq!(Arcminute::FULL_TURN, 21_600.0, epsilon = 1e-9);
1062 }
1063
1064 #[test]
1065 fn microarcsecond_conversion_chain() {
1066 let uas = MicroArcseconds::new(1e9);
1068 let mas = uas.to::<MilliArcsecond>();
1069 let arcs = mas.to::<Arcsecond>();
1070 let arcm = arcs.to::<Arcminute>();
1071 let deg = arcm.to::<Degree>();
1072
1073 assert_abs_diff_eq!(mas.value(), 1_000_000.0, epsilon = 1e-6);
1074 assert_abs_diff_eq!(arcs.value(), 1_000.0, epsilon = 1e-9);
1075 assert_abs_diff_eq!(arcm.value(), 1_000.0 / 60.0, epsilon = 1e-9);
1076 assert_relative_eq!(deg.value(), 1_000.0 / 3600.0, max_relative = 1e-9);
1077 }
1078
1079 #[test]
1080 fn wrap_pos_with_turns() {
1081 let turn = Turns::new(2.7);
1082 let wrapped = turn.wrap_pos();
1083 assert_abs_diff_eq!(wrapped.value(), 0.7, epsilon = 1e-12);
1084 }
1085
1086 #[test]
1087 fn wrap_signed_with_gradians() {
1088 let gon = Gradians::new(350.0);
1089 let wrapped = gon.wrap_signed();
1090 assert_abs_diff_eq!(wrapped.value(), -50.0, epsilon = 1e-12);
1091 }
1092
1093 #[test]
1094 fn trig_with_gradians() {
1095 let gon = Gradians::new(100.0); assert_abs_diff_eq!(gon.sin(), 1.0, epsilon = 1e-12);
1097 assert_abs_diff_eq!(gon.cos(), 0.0, epsilon = 1e-12);
1098 }
1099
1100 #[test]
1101 fn trig_with_turns() {
1102 let turn = Turns::new(0.25); assert_abs_diff_eq!(turn.sin(), 1.0, epsilon = 1e-12);
1104 assert_abs_diff_eq!(turn.cos(), 0.0, epsilon = 1e-12);
1105 }
1106
1107 #[test]
1108 fn all_units_to_degrees() {
1109 assert_abs_diff_eq!(
1111 Radians::new(PI).to::<Degree>().value(),
1112 180.0,
1113 epsilon = 1e-12
1114 );
1115 assert_abs_diff_eq!(
1116 Arcminutes::new(60.0).to::<Degree>().value(),
1117 1.0,
1118 epsilon = 1e-12
1119 );
1120 assert_abs_diff_eq!(
1121 Arcseconds::new(3600.0).to::<Degree>().value(),
1122 1.0,
1123 epsilon = 1e-12
1124 );
1125 assert_abs_diff_eq!(
1126 MilliArcseconds::new(3_600_000.0).to::<Degree>().value(),
1127 1.0,
1128 epsilon = 1e-9
1129 );
1130 assert_abs_diff_eq!(
1131 MicroArcseconds::new(3_600_000_000.0).to::<Degree>().value(),
1132 1.0,
1133 epsilon = 1e-6
1134 );
1135 assert_abs_diff_eq!(
1136 Gradians::new(100.0).to::<Degree>().value(),
1137 90.0,
1138 epsilon = 1e-12
1139 );
1140 assert_abs_diff_eq!(
1141 Turns::new(1.0).to::<Degree>().value(),
1142 360.0,
1143 epsilon = 1e-12
1144 );
1145 assert_abs_diff_eq!(
1146 HourAngles::new(1.0).to::<Degree>().value(),
1147 15.0,
1148 epsilon = 1e-12
1149 );
1150 }
1151
1152 proptest! {
1157 #[test]
1158 fn prop_wrap_pos_range(angle in -1e6..1e6f64) {
1159 let wrapped = Degrees::new(angle).wrap_pos();
1160 prop_assert!(wrapped.value() >= 0.0);
1161 prop_assert!(wrapped.value() < 360.0);
1162 }
1163
1164 #[test]
1165 fn prop_wrap_signed_range(angle in -1e6..1e6f64) {
1166 let wrapped = Degrees::new(angle).wrap_signed();
1167 prop_assert!(wrapped.value() > -180.0);
1168 prop_assert!(wrapped.value() <= 180.0);
1169 }
1170
1171 #[test]
1172 fn prop_wrap_quarter_fold_range(angle in -1e6..1e6f64) {
1173 let wrapped = Degrees::new(angle).wrap_quarter_fold();
1174 prop_assert!(wrapped.value() >= -90.0);
1175 prop_assert!(wrapped.value() <= 90.0);
1176 }
1177
1178 #[test]
1179 fn prop_pythagorean_identity(angle in -360.0..360.0f64) {
1180 let a = Degrees::new(angle);
1181 let sin = a.sin();
1182 let cos = a.cos();
1183 assert_abs_diff_eq!(sin * sin + cos * cos, 1.0, epsilon = 1e-12);
1184 }
1185
1186 #[test]
1187 fn prop_conversion_roundtrip(angle in -1e6..1e6f64) {
1188 let deg = Degrees::new(angle);
1189 let rad = deg.to::<Radian>();
1190 let back = rad.to::<Degree>();
1191 assert_relative_eq!(back.value(), deg.value(), max_relative = 1e-12);
1192 }
1193
1194 #[test]
1195 fn prop_abs_separation_symmetric(a in -360.0..360.0f64, b in -360.0..360.0f64) {
1196 let da = Degrees::new(a);
1197 let db = Degrees::new(b);
1198 assert_abs_diff_eq!(
1199 da.abs_separation(db).value(),
1200 db.abs_separation(da).value(),
1201 epsilon = 1e-12
1202 );
1203 }
1204 }
1205}