1use core::{
9 f64::consts::{FRAC_PI_2, FRAC_PI_4, PI, TAU},
10 fmt::{Display, Formatter, Result},
11 ops::{Add, AddAssign, Mul, Neg, Sub, SubAssign},
12};
13
14use glam::DMat3;
15use lox_test_utils::ApproxEq;
16
17use crate::f64::consts::SECONDS_PER_DAY;
18use crate::math::float::{
19 abs, acos, acosh, asin, asinh, atan, atan2, atanh, cos, cosh, log10, powf, round, sin, sin_cos,
20 sinh, tan, tanh, to_degrees, to_radians,
21};
22
23pub const DEGREES_IN_CIRCLE: f64 = 360.0;
25
26pub const ARCSECONDS_IN_CIRCLE: f64 = DEGREES_IN_CIRCLE * 60.0 * 60.0;
28
29pub const RADIANS_IN_ARCSECOND: f64 = TAU / ARCSECONDS_IN_CIRCLE;
31
32const HMS_DMS_ROUNDING_ARCSEC: f64 = 1e-9;
36
37#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
39#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
40pub enum Sign {
41 Positive,
43 Negative,
45}
46
47impl Sign {
48 pub const fn as_f64(&self) -> f64 {
52 match self {
53 Sign::Positive => 1.0,
54 Sign::Negative => -1.0,
55 }
56 }
57}
58
59impl Display for Sign {
60 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
61 write!(
62 f,
63 "{}",
64 match self {
65 Sign::Positive => "+",
66 Sign::Negative => "-",
67 }
68 )
69 }
70}
71
72impl From<f64> for Sign {
73 fn from(x: f64) -> Self {
74 if x.is_sign_negative() {
77 Sign::Negative
78 } else {
79 Sign::Positive
80 }
81 }
82}
83
84impl From<Sign> for f64 {
85 fn from(s: Sign) -> f64 {
86 s.as_f64()
87 }
88}
89
90macro_rules! impl_sign_from_signed_int {
91 ($($t:ty),+ $(,)?) => {
92 $(
93 impl From<$t> for Sign {
94 fn from(x: $t) -> Self {
95 if x < 0 { Sign::Negative } else { Sign::Positive }
96 }
97 }
98 )+
99 };
100}
101impl_sign_from_signed_int!(i8, i16, i32, i64, isize);
102
103type Radians = f64;
104
105fn decompose_signed_arcseconds(total_arcsec: f64, unit_arcsec: f64) -> (Sign, u32, u8, f64) {
111 let sign = Sign::from(total_arcsec);
112 let abs_arcsec = abs(total_arcsec);
113 let abs_arcsec = round(abs_arcsec / HMS_DMS_ROUNDING_ARCSEC) * HMS_DMS_ROUNDING_ARCSEC;
116 let major = (abs_arcsec / unit_arcsec) as u32;
117 let rem = abs_arcsec - (major as f64) * unit_arcsec;
118 let minutes = (rem / 60.0) as u8;
120 let seconds = rem - (minutes as f64) * 60.0;
121 (sign, major, minutes, seconds)
122}
123
124#[derive(Copy, Clone, Debug, Default, PartialEq, PartialOrd, ApproxEq)]
126#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
127#[repr(transparent)]
128pub struct Angle(Radians);
129
130impl Angle {
131 pub const ZERO: Self = Self(0.0);
133 pub const PI: Self = Self(PI);
135 pub const TAU: Self = Self(TAU);
137 pub const FRAC_PI_2: Self = Self(FRAC_PI_2);
139 pub const FRAC_PI_4: Self = Self(FRAC_PI_4);
141
142 pub const fn new(rad: f64) -> Self {
144 Self(rad)
145 }
146
147 pub const fn radians(rad: f64) -> Self {
149 Self(rad)
150 }
151
152 pub const fn radians_normalized(rad: f64) -> Self {
155 Self(rad).mod_two_pi()
156 }
157
158 pub const fn radians_normalized_signed(rad: f64) -> Self {
161 Self(rad).mod_two_pi_signed()
162 }
163
164 pub const fn degrees(deg: f64) -> Self {
166 Self(to_radians(deg))
167 }
168
169 pub const fn from_hms(sign: Sign, hours: u32, minutes: u8, seconds: f64) -> Self {
181 let mag = 15.0 * (hours as f64 + minutes as f64 / 60.0 + seconds / 3600.0);
182 Self::degrees(sign.as_f64() * mag)
183 }
184
185 pub const fn from_dms(sign: Sign, degrees: u32, minutes: u8, seconds: f64) -> Self {
194 let mag = degrees as f64 + minutes as f64 / 60.0 + seconds / 3600.0;
195 Self::degrees(sign.as_f64() * mag)
196 }
197
198 pub const fn degrees_normalized(deg: f64) -> Self {
201 Self(to_radians(deg % DEGREES_IN_CIRCLE)).mod_two_pi()
202 }
203
204 pub const fn degrees_normalized_signed(deg: f64) -> Self {
207 Self(to_radians(deg % DEGREES_IN_CIRCLE))
208 }
209
210 pub const fn arcseconds(asec: f64) -> Self {
212 Self(asec * RADIANS_IN_ARCSECOND)
213 }
214
215 pub const fn arcseconds_normalized(asec: f64) -> Self {
218 Self((asec % ARCSECONDS_IN_CIRCLE) * RADIANS_IN_ARCSECOND).mod_two_pi()
219 }
220
221 pub const fn arcseconds_normalized_signed(asec: f64) -> Self {
224 Self((asec % ARCSECONDS_IN_CIRCLE) * RADIANS_IN_ARCSECOND)
225 }
226
227 pub fn is_zero(&self) -> bool {
229 self.0 == 0.0
230 }
231
232 pub fn abs(&self) -> Self {
234 Self(abs(self.0))
235 }
236
237 pub fn from_asin(value: f64) -> Self {
239 Self(asin(value))
240 }
241
242 pub fn from_asinh(value: f64) -> Self {
244 Self(asinh(value))
245 }
246
247 pub fn from_acos(value: f64) -> Self {
249 Self(acos(value))
250 }
251
252 pub fn from_acosh(value: f64) -> Self {
254 Self(acosh(value))
255 }
256
257 pub fn from_atan(value: f64) -> Self {
259 Self(atan(value))
260 }
261
262 pub fn from_atanh(value: f64) -> Self {
264 Self(atanh(value))
265 }
266
267 pub fn from_atan2(y: f64, x: f64) -> Self {
269 Self(atan2(y, x))
270 }
271
272 pub fn cos(&self) -> f64 {
274 cos(self.0)
275 }
276
277 pub fn cosh(&self) -> f64 {
279 cosh(self.0)
280 }
281
282 pub fn sin(&self) -> f64 {
284 sin(self.0)
285 }
286
287 pub fn sinh(&self) -> f64 {
289 sinh(self.0)
290 }
291
292 pub fn sin_cos(&self) -> (f64, f64) {
294 sin_cos(self.0)
295 }
296
297 pub fn tan(&self) -> f64 {
299 tan(self.0)
300 }
301
302 pub fn tanh(&self) -> f64 {
304 tanh(self.0)
305 }
306
307 pub const fn mod_two_pi(&self) -> Self {
309 let mut a = self.0 % TAU;
310 if a < 0.0 {
311 a += TAU
312 }
313 Self(a)
314 }
315
316 pub const fn mod_two_pi_signed(&self) -> Self {
318 Self(self.0 % TAU)
319 }
320
321 pub const fn normalize_two_pi(&self, center: Self) -> Self {
324 let q = (self.0 + PI - center.0) / TAU;
327 let i = q as i64 as f64;
328 let floor_q = if q < i { i - 1.0 } else { i };
329 Self(self.0 - TAU * floor_q)
330 }
331
332 pub const fn as_f64(&self) -> f64 {
334 self.0
335 }
336
337 pub const fn to_radians(&self) -> f64 {
339 self.0
340 }
341
342 pub const fn to_degrees(&self) -> f64 {
344 to_degrees(self.0)
345 }
346
347 pub const fn to_arcseconds(&self) -> f64 {
349 self.0 / RADIANS_IN_ARCSECOND
350 }
351
352 pub fn rotation_x(&self) -> DMat3 {
354 DMat3::from_rotation_x(-self.to_radians())
355 }
356
357 pub fn rotation_y(&self) -> DMat3 {
359 DMat3::from_rotation_y(-self.to_radians())
360 }
361
362 pub fn rotation_z(&self) -> DMat3 {
364 DMat3::from_rotation_z(-self.to_radians())
365 }
366
367 pub fn to_hms(&self) -> (Sign, u32, u8, f64) {
379 decompose_signed_arcseconds(self.to_arcseconds() / 15.0, 3600.0)
383 }
384
385 pub fn to_dms(&self) -> (Sign, u32, u8, f64) {
397 decompose_signed_arcseconds(self.to_arcseconds(), 3600.0)
398 }
399}
400
401impl Display for Angle {
402 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
403 to_degrees(self.0).fmt(f)?;
404 write!(f, " deg")
405 }
406}
407
408pub trait AngleUnits {
421 fn rad(&self) -> Angle;
423 fn deg(&self) -> Angle;
425 fn arcsec(&self) -> Angle;
427 fn mas(&self) -> Angle;
429 fn uas(&self) -> Angle;
431}
432
433impl AngleUnits for f64 {
434 fn rad(&self) -> Angle {
435 Angle::radians(*self)
436 }
437
438 fn deg(&self) -> Angle {
439 Angle::degrees(*self)
440 }
441
442 fn arcsec(&self) -> Angle {
443 Angle::arcseconds(*self)
444 }
445
446 fn mas(&self) -> Angle {
447 Angle::arcseconds(self * 1e-3)
448 }
449
450 fn uas(&self) -> Angle {
451 Angle::arcseconds(self * 1e-6)
452 }
453}
454
455impl AngleUnits for i64 {
456 fn rad(&self) -> Angle {
457 Angle::radians(*self as f64)
458 }
459
460 fn deg(&self) -> Angle {
461 Angle::degrees(*self as f64)
462 }
463
464 fn arcsec(&self) -> Angle {
465 Angle::arcseconds(*self as f64)
466 }
467
468 fn mas(&self) -> Angle {
469 Angle::arcseconds(*self as f64 * 1e-3)
470 }
471
472 fn uas(&self) -> Angle {
473 Angle::arcseconds(*self as f64 * 1e-6)
474 }
475}
476
477pub const ASTRONOMICAL_UNIT: f64 = 1.495978707e11;
479
480type Meters = f64;
481
482#[derive(Copy, Clone, Debug, Default, PartialEq, PartialOrd, ApproxEq)]
484#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
485#[repr(transparent)]
486pub struct Distance(Meters);
487
488impl Distance {
489 pub const fn new(m: f64) -> Self {
491 Self(m)
492 }
493
494 pub const fn meters(m: f64) -> Self {
496 Self(m)
497 }
498
499 pub const fn kilometers(m: f64) -> Self {
501 Self(m * 1e3)
502 }
503
504 pub const fn astronomical_units(au: f64) -> Self {
506 Self(au * ASTRONOMICAL_UNIT)
507 }
508
509 pub const fn as_f64(&self) -> f64 {
511 self.0
512 }
513
514 pub const fn to_meters(&self) -> f64 {
516 self.0
517 }
518
519 pub const fn to_kilometers(&self) -> f64 {
521 self.0 * 1e-3
522 }
523
524 pub const fn to_astronomical_units(&self) -> f64 {
526 self.0 / ASTRONOMICAL_UNIT
527 }
528}
529
530impl Display for Distance {
531 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
532 (1e-3 * self.0).fmt(f)?;
533 write!(f, " km")
534 }
535}
536
537pub trait DistanceUnits {
550 fn m(&self) -> Distance;
552 fn km(&self) -> Distance;
554 fn au(&self) -> Distance;
556}
557
558impl DistanceUnits for f64 {
559 fn m(&self) -> Distance {
560 Distance::meters(*self)
561 }
562
563 fn km(&self) -> Distance {
564 Distance::kilometers(*self)
565 }
566
567 fn au(&self) -> Distance {
568 Distance::astronomical_units(*self)
569 }
570}
571
572impl DistanceUnits for i64 {
573 fn m(&self) -> Distance {
574 Distance::meters(*self as f64)
575 }
576
577 fn km(&self) -> Distance {
578 Distance::kilometers(*self as f64)
579 }
580
581 fn au(&self) -> Distance {
582 Distance::astronomical_units(*self as f64)
583 }
584}
585
586type MetersPerSecond = f64;
587
588#[derive(Copy, Clone, Debug, Default, PartialEq, PartialOrd, ApproxEq)]
590#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
591#[repr(transparent)]
592pub struct Velocity(MetersPerSecond);
593
594impl Velocity {
595 pub const fn new(mps: f64) -> Self {
597 Self(mps)
598 }
599
600 pub const fn meters_per_second(mps: f64) -> Self {
602 Self(mps)
603 }
604
605 pub const fn kilometers_per_second(mps: f64) -> Self {
607 Self(mps * 1e3)
608 }
609
610 pub const fn astronomical_units_per_day(aud: f64) -> Self {
612 Self(aud * ASTRONOMICAL_UNIT / SECONDS_PER_DAY)
613 }
614
615 pub const fn fraction_of_speed_of_light(c: f64) -> Self {
617 Self(c * SPEED_OF_LIGHT)
618 }
619
620 pub const fn as_f64(&self) -> f64 {
622 self.0
623 }
624
625 pub const fn to_meters_per_second(&self) -> f64 {
627 self.0
628 }
629
630 pub const fn to_kilometers_per_second(&self) -> f64 {
632 self.0 * 1e-3
633 }
634
635 pub const fn to_astronomical_units_per_day(&self) -> f64 {
637 self.0 * SECONDS_PER_DAY / ASTRONOMICAL_UNIT
638 }
639
640 pub const fn to_fraction_of_speed_of_light(&self) -> f64 {
642 self.0 / SPEED_OF_LIGHT
643 }
644}
645
646impl Display for Velocity {
647 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
648 (1e-3 * self.0).fmt(f)?;
649 write!(f, " km/s")
650 }
651}
652
653pub trait VelocityUnits {
666 fn mps(&self) -> Velocity;
668 fn kps(&self) -> Velocity;
670 fn aud(&self) -> Velocity;
672 fn c(&self) -> Velocity;
674}
675
676impl VelocityUnits for f64 {
677 fn mps(&self) -> Velocity {
678 Velocity::meters_per_second(*self)
679 }
680
681 fn kps(&self) -> Velocity {
682 Velocity::kilometers_per_second(*self)
683 }
684
685 fn aud(&self) -> Velocity {
686 Velocity::astronomical_units_per_day(*self)
687 }
688
689 fn c(&self) -> Velocity {
690 Velocity::fraction_of_speed_of_light(*self)
691 }
692}
693
694impl VelocityUnits for i64 {
695 fn mps(&self) -> Velocity {
696 Velocity::meters_per_second(*self as f64)
697 }
698
699 fn kps(&self) -> Velocity {
700 Velocity::kilometers_per_second(*self as f64)
701 }
702
703 fn aud(&self) -> Velocity {
704 Velocity::astronomical_units_per_day(*self as f64)
705 }
706
707 fn c(&self) -> Velocity {
708 Velocity::fraction_of_speed_of_light(*self as f64)
709 }
710}
711
712pub const SPEED_OF_LIGHT: f64 = 299792458.0;
714
715#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
717#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
718pub enum FrequencyBand {
719 HF,
721 VHF,
723 UHF,
725 L,
727 S,
729 C,
731 X,
733 Ku,
735 K,
737 Ka,
739 V,
741 W,
743 G,
745}
746
747type Hertz = f64;
748
749#[derive(Copy, Clone, Debug, Default, PartialEq, PartialOrd, ApproxEq)]
751#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
752#[repr(transparent)]
753pub struct Frequency(Hertz);
754
755impl Frequency {
756 pub const fn new(hz: Hertz) -> Self {
758 Self(hz)
759 }
760
761 pub const fn hertz(hz: Hertz) -> Self {
763 Self(hz)
764 }
765
766 pub const fn kilohertz(hz: Hertz) -> Self {
768 Self(hz * 1e3)
769 }
770
771 pub const fn megahertz(hz: Hertz) -> Self {
773 Self(hz * 1e6)
774 }
775
776 pub const fn gigahertz(hz: Hertz) -> Self {
778 Self(hz * 1e9)
779 }
780
781 pub const fn terahertz(hz: Hertz) -> Self {
783 Self(hz * 1e12)
784 }
785
786 pub const fn to_hertz(&self) -> f64 {
788 self.0
789 }
790
791 pub const fn to_kilohertz(&self) -> f64 {
793 self.0 * 1e-3
794 }
795
796 pub const fn to_megahertz(&self) -> f64 {
798 self.0 * 1e-6
799 }
800
801 pub const fn to_gigahertz(&self) -> f64 {
803 self.0 * 1e-9
804 }
805
806 pub const fn to_terahertz(&self) -> f64 {
808 self.0 * 1e-12
809 }
810
811 pub fn wavelength(&self) -> Distance {
813 Distance(SPEED_OF_LIGHT / self.0)
814 }
815
816 pub fn band(&self) -> Option<FrequencyBand> {
818 match self.0 {
819 f if f < 3e6 => None,
820 f if f < 30e6 => Some(FrequencyBand::HF),
821 f if f < 300e6 => Some(FrequencyBand::VHF),
822 f if f < 1e9 => Some(FrequencyBand::UHF),
823 f if f < 2e9 => Some(FrequencyBand::L),
824 f if f < 4e9 => Some(FrequencyBand::S),
825 f if f < 8e9 => Some(FrequencyBand::C),
826 f if f < 12e9 => Some(FrequencyBand::X),
827 f if f < 18e9 => Some(FrequencyBand::Ku),
828 f if f < 27e9 => Some(FrequencyBand::K),
829 f if f < 40e9 => Some(FrequencyBand::Ka),
830 f if f < 75e9 => Some(FrequencyBand::V),
831 f if f < 110e9 => Some(FrequencyBand::W),
832 f if f < 300e9 => Some(FrequencyBand::G),
833 _ => None,
834 }
835 }
836}
837
838impl Display for Frequency {
839 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
840 (1e-9 * self.0).fmt(f)?;
841 write!(f, " GHz")
842 }
843}
844
845pub trait FrequencyUnits {
858 fn hz(&self) -> Frequency;
860 fn khz(&self) -> Frequency;
862 fn mhz(&self) -> Frequency;
864 fn ghz(&self) -> Frequency;
866 fn thz(&self) -> Frequency;
868}
869
870impl FrequencyUnits for f64 {
871 fn hz(&self) -> Frequency {
872 Frequency::hertz(*self)
873 }
874
875 fn khz(&self) -> Frequency {
876 Frequency::kilohertz(*self)
877 }
878
879 fn mhz(&self) -> Frequency {
880 Frequency::megahertz(*self)
881 }
882
883 fn ghz(&self) -> Frequency {
884 Frequency::gigahertz(*self)
885 }
886
887 fn thz(&self) -> Frequency {
888 Frequency::terahertz(*self)
889 }
890}
891
892impl FrequencyUnits for i64 {
893 fn hz(&self) -> Frequency {
894 Frequency::hertz(*self as f64)
895 }
896
897 fn khz(&self) -> Frequency {
898 Frequency::kilohertz(*self as f64)
899 }
900
901 fn mhz(&self) -> Frequency {
902 Frequency::megahertz(*self as f64)
903 }
904
905 fn ghz(&self) -> Frequency {
906 Frequency::gigahertz(*self as f64)
907 }
908
909 fn thz(&self) -> Frequency {
910 Frequency::terahertz(*self as f64)
911 }
912}
913
914type Kilograms = f64;
915
916#[derive(Copy, Clone, Debug, Default, PartialEq, PartialOrd, ApproxEq)]
918#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
919#[repr(transparent)]
920pub struct Mass(Kilograms);
921
922impl Mass {
923 pub const fn new(kg: f64) -> Self {
925 Self(kg)
926 }
927
928 pub const fn kilograms(kg: f64) -> Self {
930 Self(kg)
931 }
932
933 pub const fn grams(g: f64) -> Self {
935 Self(g * 1e-3)
936 }
937
938 pub const fn metric_tons(t: f64) -> Self {
940 Self(t * 1e3)
941 }
942
943 pub const fn as_f64(&self) -> f64 {
945 self.0
946 }
947
948 pub const fn to_kilograms(&self) -> f64 {
950 self.0
951 }
952
953 pub const fn to_grams(&self) -> f64 {
955 self.0 * 1e3
956 }
957
958 pub const fn to_metric_tons(&self) -> f64 {
960 self.0 * 1e-3
961 }
962}
963
964impl Display for Mass {
965 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
966 self.0.fmt(f)?;
967 write!(f, " kg")
968 }
969}
970
971pub trait MassUnits {
984 fn kg(&self) -> Mass;
986 fn g(&self) -> Mass;
988 fn t(&self) -> Mass;
990}
991
992impl MassUnits for f64 {
993 fn kg(&self) -> Mass {
994 Mass::kilograms(*self)
995 }
996
997 fn g(&self) -> Mass {
998 Mass::grams(*self)
999 }
1000
1001 fn t(&self) -> Mass {
1002 Mass::metric_tons(*self)
1003 }
1004}
1005
1006impl MassUnits for i64 {
1007 fn kg(&self) -> Mass {
1008 Mass::kilograms(*self as f64)
1009 }
1010
1011 fn g(&self) -> Mass {
1012 Mass::grams(*self as f64)
1013 }
1014
1015 fn t(&self) -> Mass {
1016 Mass::metric_tons(*self as f64)
1017 }
1018}
1019
1020type SquareMeters = f64;
1021
1022#[derive(Copy, Clone, Debug, Default, PartialEq, PartialOrd, ApproxEq)]
1024#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1025#[repr(transparent)]
1026pub struct Area(SquareMeters);
1027
1028impl Area {
1029 pub const fn new(m2: f64) -> Self {
1031 Self(m2)
1032 }
1033
1034 pub const fn square_meters(m2: f64) -> Self {
1036 Self(m2)
1037 }
1038
1039 pub const fn square_kilometers(km2: f64) -> Self {
1041 Self(km2 * 1e6)
1042 }
1043
1044 pub const fn as_f64(&self) -> f64 {
1046 self.0
1047 }
1048
1049 pub const fn to_square_meters(&self) -> f64 {
1051 self.0
1052 }
1053
1054 pub const fn to_square_kilometers(&self) -> f64 {
1056 self.0 * 1e-6
1057 }
1058}
1059
1060impl Display for Area {
1061 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
1062 self.0.fmt(f)?;
1063 write!(f, " m²")
1064 }
1065}
1066
1067pub trait AreaUnits {
1080 fn m2(&self) -> Area;
1082 fn km2(&self) -> Area;
1084}
1085
1086impl AreaUnits for f64 {
1087 fn m2(&self) -> Area {
1088 Area::square_meters(*self)
1089 }
1090
1091 fn km2(&self) -> Area {
1092 Area::square_kilometers(*self)
1093 }
1094}
1095
1096impl AreaUnits for i64 {
1097 fn m2(&self) -> Area {
1098 Area::square_meters(*self as f64)
1099 }
1100
1101 fn km2(&self) -> Area {
1102 Area::square_kilometers(*self as f64)
1103 }
1104}
1105
1106type SquareMetersPerKilogram = f64;
1107
1108#[derive(Copy, Clone, Debug, Default, PartialEq, PartialOrd, ApproxEq)]
1113#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1114#[repr(transparent)]
1115pub struct AreaToMass(SquareMetersPerKilogram);
1116
1117impl AreaToMass {
1118 pub const fn new(m2_per_kg: f64) -> Self {
1120 Self(m2_per_kg)
1121 }
1122
1123 pub const fn square_meters_per_kilogram(m2_per_kg: f64) -> Self {
1125 Self(m2_per_kg)
1126 }
1127
1128 pub const fn as_f64(&self) -> f64 {
1130 self.0
1131 }
1132
1133 pub const fn to_square_meters_per_kilogram(&self) -> f64 {
1135 self.0
1136 }
1137}
1138
1139impl Display for AreaToMass {
1140 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
1141 self.0.fmt(f)?;
1142 write!(f, " m²/kg")
1143 }
1144}
1145
1146pub trait AreaToMassUnits {
1159 fn m2_per_kg(&self) -> AreaToMass;
1161}
1162
1163impl AreaToMassUnits for f64 {
1164 fn m2_per_kg(&self) -> AreaToMass {
1165 AreaToMass::square_meters_per_kilogram(*self)
1166 }
1167}
1168
1169impl AreaToMassUnits for i64 {
1170 fn m2_per_kg(&self) -> AreaToMass {
1171 AreaToMass::square_meters_per_kilogram(*self as f64)
1172 }
1173}
1174
1175pub type Kelvin = f64;
1177
1178type KelvinValue = f64;
1179
1180#[derive(Copy, Clone, Debug, Default, PartialEq, PartialOrd, ApproxEq)]
1182#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1183#[repr(transparent)]
1184pub struct Temperature(KelvinValue);
1185
1186impl Temperature {
1187 pub const fn new(k: f64) -> Self {
1189 Self(k)
1190 }
1191
1192 pub const fn kelvin(k: f64) -> Self {
1194 Self(k)
1195 }
1196
1197 pub const fn as_f64(&self) -> f64 {
1199 self.0
1200 }
1201
1202 pub const fn to_kelvin(&self) -> f64 {
1204 self.0
1205 }
1206}
1207
1208impl Display for Temperature {
1209 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
1210 self.0.fmt(f)?;
1211 write!(f, " K")
1212 }
1213}
1214
1215type Pascals = f64;
1216
1217#[derive(Copy, Clone, Debug, Default, PartialEq, PartialOrd, ApproxEq)]
1219#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1220#[repr(transparent)]
1221pub struct Pressure(Pascals);
1222
1223impl Pressure {
1224 pub const fn new(pa: f64) -> Self {
1226 Self(pa)
1227 }
1228
1229 pub const fn pa(pa: f64) -> Self {
1231 Self(pa)
1232 }
1233
1234 pub const fn hpa(hpa: f64) -> Self {
1236 Self(hpa * 100.0)
1237 }
1238
1239 pub const fn as_f64(&self) -> f64 {
1241 self.0
1242 }
1243
1244 pub const fn to_pa(&self) -> f64 {
1246 self.0
1247 }
1248
1249 pub const fn to_hpa(&self) -> f64 {
1251 self.0 * 0.01
1252 }
1253}
1254
1255impl Display for Pressure {
1256 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
1257 self.0.fmt(f)?;
1258 write!(f, " Pa")
1259 }
1260}
1261
1262type Watts = f64;
1263
1264#[derive(Copy, Clone, Debug, Default, PartialEq, PartialOrd, ApproxEq)]
1266#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1267#[repr(transparent)]
1268pub struct Power(Watts);
1269
1270impl Power {
1271 pub const fn new(w: f64) -> Self {
1273 Self(w)
1274 }
1275
1276 pub const fn watts(w: f64) -> Self {
1278 Self(w)
1279 }
1280
1281 pub const fn kilowatts(kw: f64) -> Self {
1283 Self(kw * 1e3)
1284 }
1285
1286 pub const fn as_f64(&self) -> f64 {
1288 self.0
1289 }
1290
1291 pub const fn to_watts(&self) -> f64 {
1293 self.0
1294 }
1295
1296 pub const fn to_kilowatts(&self) -> f64 {
1298 self.0 * 1e-3
1299 }
1300
1301 pub fn to_dbw(&self) -> f64 {
1303 10.0 * log10(self.0)
1304 }
1305}
1306
1307impl Display for Power {
1308 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
1309 self.0.fmt(f)?;
1310 write!(f, " W")
1311 }
1312}
1313
1314type RadiansPerSecond = f64;
1315
1316#[derive(Copy, Clone, Debug, Default, PartialEq, PartialOrd, ApproxEq)]
1318#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1319#[repr(transparent)]
1320pub struct AngularRate(RadiansPerSecond);
1321
1322impl AngularRate {
1323 pub const fn new(rps: f64) -> Self {
1325 Self(rps)
1326 }
1327
1328 pub const fn radians_per_second(rps: f64) -> Self {
1330 Self(rps)
1331 }
1332
1333 pub const fn degrees_per_second(dps: f64) -> Self {
1335 Self(to_radians(dps))
1336 }
1337
1338 pub const fn as_f64(&self) -> f64 {
1340 self.0
1341 }
1342
1343 pub const fn to_radians_per_second(&self) -> f64 {
1345 self.0
1346 }
1347
1348 pub const fn to_degrees_per_second(&self) -> f64 {
1350 to_degrees(self.0)
1351 }
1352}
1353
1354impl Display for AngularRate {
1355 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
1356 to_degrees(self.0).fmt(f)?;
1357 write!(f, " deg/s")
1358 }
1359}
1360
1361type DecibelValue = f64;
1362
1363#[derive(Copy, Clone, Debug, Default, PartialEq, PartialOrd, ApproxEq)]
1365#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1366#[repr(transparent)]
1367pub struct Decibel(DecibelValue);
1368
1369impl Decibel {
1370 pub const fn new(db: f64) -> Self {
1372 Self(db)
1373 }
1374
1375 pub fn from_linear(val: f64) -> Self {
1377 Self(10.0 * log10(val))
1378 }
1379
1380 pub fn to_linear(self) -> f64 {
1382 powf(10.0_f64, self.0 / 10.0)
1383 }
1384
1385 pub const fn as_f64(self) -> f64 {
1387 self.0
1388 }
1389}
1390
1391impl Display for Decibel {
1392 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
1393 self.0.fmt(f)?;
1394 write!(f, " dB")
1395 }
1396}
1397
1398pub trait DecibelUnits {
1411 fn db(&self) -> Decibel;
1413}
1414
1415impl DecibelUnits for f64 {
1416 fn db(&self) -> Decibel {
1417 Decibel::new(*self)
1418 }
1419}
1420
1421impl DecibelUnits for i64 {
1422 fn db(&self) -> Decibel {
1423 Decibel::new(*self as f64)
1424 }
1425}
1426
1427macro_rules! trait_impls {
1428 ($($unit:ident),*) => {
1429 $(
1430 impl Neg for $unit {
1431 type Output = Self;
1432
1433 fn neg(self) -> Self::Output {
1434 Self(-self.0)
1435 }
1436 }
1437
1438 impl Add for $unit {
1439 type Output = Self;
1440
1441 fn add(self, rhs: Self) -> Self::Output {
1442 Self(self.0 + rhs.0)
1443 }
1444 }
1445
1446 impl AddAssign for $unit {
1447 fn add_assign(&mut self, rhs: Self) {
1448 self.0 = self.0 + rhs.0;
1449 }
1450 }
1451
1452 impl Sub for $unit {
1453 type Output = Self;
1454
1455 fn sub(self, rhs: Self) -> Self::Output {
1456 Self(self.0 - rhs.0)
1457 }
1458 }
1459
1460 impl SubAssign for $unit {
1461 fn sub_assign(&mut self, rhs: Self) {
1462 self.0 = self.0 - rhs.0
1463 }
1464 }
1465
1466 impl Mul<$unit> for f64 {
1467 type Output = $unit;
1468
1469 fn mul(self, rhs: $unit) -> Self::Output {
1470 $unit(self * rhs.0)
1471 }
1472 }
1473
1474 impl From<$unit> for f64 {
1475 fn from(val: $unit) -> Self {
1476 val.0
1477 }
1478 }
1479 )*
1480 };
1481}
1482
1483trait_impls!(
1484 Angle,
1485 AngularRate,
1486 Area,
1487 AreaToMass,
1488 Decibel,
1489 Distance,
1490 Frequency,
1491 Mass,
1492 Power,
1493 Pressure,
1494 Temperature,
1495 Velocity
1496);
1497
1498#[cfg(test)]
1499mod tests {
1500 use alloc::format;
1501 use core::f64::consts::{FRAC_PI_2, PI};
1502
1503 use lox_test_utils::assert_approx_eq;
1504 use rstest::rstest;
1505
1506 extern crate alloc;
1507
1508 use super::*;
1509
1510 #[test]
1511 fn test_angle_deg() {
1512 let angle = 90.0.deg();
1513 assert_approx_eq!(angle.0, FRAC_PI_2, rtol <= 1e-10);
1514 }
1515
1516 #[test]
1517 fn test_angle_rad() {
1518 let angle = PI.rad();
1519 assert_approx_eq!(angle.0, PI, rtol <= 1e-10);
1520 }
1521
1522 #[test]
1523 fn test_angle_conversions() {
1524 let angle_deg = 180.0.deg();
1525 let angle_rad = PI.rad();
1526 assert_approx_eq!(angle_deg.0, angle_rad.0, rtol <= 1e-10);
1527 }
1528
1529 #[test]
1530 fn test_angle_display() {
1531 let angle = 90.123456.deg();
1532 assert_eq!(format!("{:.2}", angle), "90.12 deg")
1533 }
1534
1535 #[test]
1536 fn test_angle_neg() {
1537 assert_eq!(Angle(-1.0), -1.0.rad())
1538 }
1539
1540 const TOLERANCE: f64 = f64::EPSILON;
1541
1542 #[rstest]
1543 #[case(Angle::ZERO, Angle::ZERO, 0.0)]
1545 #[case(Angle::PI, Angle::ZERO, -PI)]
1546 #[case(-Angle::PI, Angle::ZERO, -PI)]
1547 #[case(Angle::TAU, Angle::ZERO, 0.0)]
1548 #[case(Angle::FRAC_PI_2, Angle::ZERO, FRAC_PI_2)]
1549 #[case(-Angle::FRAC_PI_2, Angle::ZERO, -FRAC_PI_2)]
1550 #[case(Angle::ZERO, Angle::PI, 0.0)]
1552 #[case(Angle::PI, Angle::PI, PI)]
1553 #[case(-Angle::PI, Angle::PI, PI)]
1554 #[case(Angle::TAU, Angle::PI, 0.0)]
1555 #[case(Angle::FRAC_PI_2, Angle::PI, FRAC_PI_2)]
1556 #[case(-Angle::FRAC_PI_2, Angle::PI, 3.0 * PI / 2.0)]
1557 #[case(Angle::ZERO, -Angle::PI, -TAU)]
1559 #[case(Angle::PI, -Angle::PI, -PI)]
1560 #[case(-Angle::PI, -Angle::PI, -PI)]
1561 #[case(Angle::TAU, -Angle::PI, -TAU)]
1562 #[case(Angle::FRAC_PI_2, -Angle::PI, -3.0 * PI / 2.0)]
1563 #[case(-Angle::FRAC_PI_2, -Angle::PI, -FRAC_PI_2)]
1564 fn test_angle_normalize_two_pi(#[case] angle: Angle, #[case] center: Angle, #[case] exp: f64) {
1565 if exp == 0.0 {
1568 assert_approx_eq!(angle.normalize_two_pi(center).0, exp, atol <= TOLERANCE);
1569 } else {
1570 assert_approx_eq!(angle.normalize_two_pi(center).0, exp, rtol <= TOLERANCE);
1571 }
1572 }
1573
1574 #[test]
1575 fn test_distance_m() {
1576 let distance = 1000.0.m();
1577 assert_eq!(distance.0, 1000.0);
1578 }
1579
1580 #[test]
1581 fn test_distance_km() {
1582 let distance = 1.0.km();
1583 assert_eq!(distance.0, 1000.0);
1584 }
1585
1586 #[test]
1587 fn test_distance_au() {
1588 let distance = 1.0.au();
1589 assert_eq!(distance.0, ASTRONOMICAL_UNIT);
1590 }
1591
1592 #[test]
1593 fn test_distance_conversions() {
1594 let d1 = 1.5e11.m();
1595 let d2 = (1.5e11 / ASTRONOMICAL_UNIT).au();
1596 assert_approx_eq!(d1.0, d2.0, rtol <= 1e-9);
1597 }
1598
1599 #[test]
1600 fn test_distance_display() {
1601 let distance = 9.123456.km();
1602 assert_eq!(format!("{:.2}", distance), "9.12 km")
1603 }
1604
1605 #[test]
1606 fn test_distance_neg() {
1607 assert_eq!(Distance(-1.0), -1.0.m())
1608 }
1609
1610 #[test]
1611 fn test_velocity_mps() {
1612 let velocity = 1000.0.mps();
1613 assert_eq!(velocity.0, 1000.0);
1614 }
1615
1616 #[test]
1617 fn test_velocity_kps() {
1618 let velocity = 1.0.kps();
1619 assert_eq!(velocity.0, 1000.0);
1620 }
1621
1622 #[test]
1623 fn test_velocity_conversions() {
1624 let v1 = 7500.0.mps();
1625 let v2 = 7.5.kps();
1626 assert_eq!(v1.0, v2.0);
1627 }
1628
1629 #[test]
1630 fn test_velocity_display() {
1631 let velocity = 9.123456.kps();
1632 assert_eq!(format!("{:.2}", velocity), "9.12 km/s")
1633 }
1634
1635 #[test]
1636 fn test_velocity_neg() {
1637 assert_eq!(Velocity(-1.0), -1.0.mps())
1638 }
1639
1640 #[test]
1641 fn test_frequency_hz() {
1642 let frequency = 1000.0.hz();
1643 assert_eq!(frequency.0, 1000.0);
1644 }
1645
1646 #[test]
1647 fn test_frequency_khz() {
1648 let frequency = 1.0.khz();
1649 assert_eq!(frequency.0, 1000.0);
1650 }
1651
1652 #[test]
1653 fn test_frequency_mhz() {
1654 let frequency = 1.0.mhz();
1655 assert_eq!(frequency.0, 1_000_000.0);
1656 }
1657
1658 #[test]
1659 fn test_frequency_ghz() {
1660 let frequency = 1.0.ghz();
1661 assert_eq!(frequency.0, 1_000_000_000.0);
1662 }
1663
1664 #[test]
1665 fn test_frequency_thz() {
1666 let frequency = 1.0.thz();
1667 assert_eq!(frequency.0, 1_000_000_000_000.0);
1668 }
1669
1670 #[test]
1671 fn test_frequency_conversions() {
1672 let f1 = 2.4.ghz();
1673 let f2 = 2400.0.mhz();
1674 assert_eq!(f1.0, f2.0);
1675 }
1676
1677 #[test]
1678 fn test_frequency_wavelength() {
1679 let f = 1.0.ghz();
1680 let wavelength = f.wavelength();
1681 assert_approx_eq!(wavelength.0, 0.299792458, rtol <= 1e-9);
1682 }
1683
1684 #[test]
1685 fn test_frequency_wavelength_speed_of_light() {
1686 let f = 299792458.0.hz(); let wavelength = f.wavelength();
1688 assert_approx_eq!(wavelength.0, 1.0, rtol <= 1e-10);
1689 }
1690
1691 #[test]
1692 fn test_frequency_display() {
1693 let frequency = 2.4123456.ghz();
1694 assert_eq!(format!("{:.2}", frequency), "2.41 GHz");
1695 }
1696
1697 #[rstest]
1698 #[case(0.0.hz(), None)]
1699 #[case(3.0.mhz(), Some(FrequencyBand::HF))]
1700 #[case(30.0.mhz(), Some(FrequencyBand::VHF))]
1701 #[case(300.0.mhz(), Some(FrequencyBand::UHF))]
1702 #[case(1.0.ghz(), Some(FrequencyBand::L))]
1703 #[case(2.0.ghz(), Some(FrequencyBand::S))]
1704 #[case(4.0.ghz(), Some(FrequencyBand::C))]
1705 #[case(8.0.ghz(), Some(FrequencyBand::X))]
1706 #[case(12.0.ghz(), Some(FrequencyBand::Ku))]
1707 #[case(18.0.ghz(), Some(FrequencyBand::K))]
1708 #[case(27.0.ghz(), Some(FrequencyBand::Ka))]
1709 #[case(40.0.ghz(), Some(FrequencyBand::V))]
1710 #[case(75.0.ghz(), Some(FrequencyBand::W))]
1711 #[case(110.0.ghz(), Some(FrequencyBand::G))]
1712 #[case(1.0.thz(), None)]
1713 fn test_frequency_band(#[case] f: Frequency, #[case] exp: Option<FrequencyBand>) {
1714 assert_eq!(f.band(), exp)
1715 }
1716
1717 #[test]
1718 fn test_decibel_db() {
1719 let d = 3.0.db();
1720 assert_eq!(d.as_f64(), 3.0);
1721 }
1722
1723 #[test]
1724 fn test_decibel_from_linear() {
1725 let d = Decibel::from_linear(100.0);
1726 assert_approx_eq!(d.0, 20.0, rtol <= 1e-10);
1727 }
1728
1729 #[test]
1730 fn test_decibel_to_linear() {
1731 let d = Decibel::new(20.0);
1732 assert_approx_eq!(d.to_linear(), 100.0, rtol <= 1e-10);
1733 }
1734
1735 #[test]
1736 fn test_decibel_roundtrip() {
1737 let val = 42.5;
1738 let d = Decibel::new(val);
1739 let roundtripped = Decibel::from_linear(d.to_linear());
1740 assert_approx_eq!(roundtripped.0, val, rtol <= 1e-10);
1741 }
1742
1743 #[test]
1744 fn test_decibel_add() {
1745 let sum = 3.0.db() + 3.0.db();
1746 assert_approx_eq!(sum.0, 6.0, rtol <= 1e-10);
1747 }
1748
1749 #[test]
1750 fn test_decibel_sub() {
1751 let diff = 6.0.db() - 3.0.db();
1752 assert_approx_eq!(diff.0, 3.0, rtol <= 1e-10);
1753 }
1754
1755 #[test]
1756 fn test_decibel_neg() {
1757 assert_eq!(-3.0.db(), Decibel::new(-3.0));
1758 }
1759
1760 #[test]
1761 fn test_decibel_display() {
1762 let d = 3.0.db();
1763 assert_eq!(format!("{:.1}", d), "3.0 dB");
1764 }
1765
1766 #[test]
1769 fn test_temperature_new() {
1770 let t = Temperature::new(290.0);
1771 assert_eq!(t.as_f64(), 290.0);
1772 }
1773
1774 #[test]
1775 fn test_temperature_kelvin() {
1776 let t = Temperature::kelvin(300.0);
1777 assert_eq!(t.to_kelvin(), 300.0);
1778 }
1779
1780 #[test]
1781 fn test_temperature_display() {
1782 let t = Temperature::new(290.0);
1783 assert_eq!(format!("{}", t), "290 K");
1784 }
1785
1786 #[test]
1787 fn test_temperature_arithmetic() {
1788 let a = Temperature::new(100.0);
1789 let b = Temperature::new(200.0);
1790 assert_eq!((a + b).as_f64(), 300.0);
1791 assert_eq!((b - a).as_f64(), 100.0);
1792 assert_eq!((-a).as_f64(), -100.0);
1793 assert_eq!((2.0 * a).as_f64(), 200.0);
1794 }
1795
1796 #[test]
1799 fn test_power_watts() {
1800 let p = Power::watts(100.0);
1801 assert_eq!(p.to_watts(), 100.0);
1802 }
1803
1804 #[test]
1805 fn test_power_kilowatts() {
1806 let p = Power::kilowatts(1.0);
1807 assert_eq!(p.to_watts(), 1000.0);
1808 assert_eq!(p.to_kilowatts(), 1.0);
1809 }
1810
1811 #[test]
1812 fn test_power_dbw() {
1813 let p = Power::watts(100.0);
1814 assert_approx_eq!(p.to_dbw(), 20.0, rtol <= 1e-10);
1815 }
1816
1817 #[test]
1818 fn test_power_display() {
1819 let p = Power::watts(100.0);
1820 assert_eq!(format!("{}", p), "100 W");
1821 }
1822
1823 #[test]
1824 fn test_power_arithmetic() {
1825 let a = Power::watts(50.0);
1826 let b = Power::watts(150.0);
1827 assert_eq!((a + b).as_f64(), 200.0);
1828 assert_eq!((b - a).as_f64(), 100.0);
1829 assert_eq!((-a).as_f64(), -50.0);
1830 }
1831
1832 #[test]
1835 fn test_angular_rate_rps() {
1836 let ar = AngularRate::radians_per_second(1.0);
1837 assert_eq!(ar.to_radians_per_second(), 1.0);
1838 assert_approx_eq!(ar.to_degrees_per_second(), 57.29577951308232, rtol <= 1e-10);
1839 }
1840
1841 #[test]
1842 fn test_angular_rate_dps() {
1843 let ar = AngularRate::degrees_per_second(180.0);
1844 assert_approx_eq!(
1845 ar.to_radians_per_second(),
1846 core::f64::consts::PI,
1847 rtol <= 1e-10
1848 );
1849 }
1850
1851 #[test]
1852 fn test_angular_rate_display() {
1853 let ar = AngularRate::radians_per_second(1.0);
1854 let s = format!("{}", ar);
1855 assert!(s.contains("deg/s"));
1856 }
1857
1858 #[test]
1859 fn test_angular_rate_arithmetic() {
1860 let a = AngularRate::new(1.0);
1861 let b = AngularRate::new(2.0);
1862 assert_eq!((a + b).as_f64(), 3.0);
1863 assert_eq!((b - a).as_f64(), 1.0);
1864 assert_eq!((-a).as_f64(), -1.0);
1865 assert_eq!((3.0 * a).as_f64(), 3.0);
1866 }
1867
1868 #[test]
1871 fn test_pressure_hpa() {
1872 let p = Pressure::hpa(1013.25);
1873 assert_eq!(p.to_hpa(), 1013.25);
1874 assert_approx_eq!(p.to_pa(), 101325.0, rtol <= 1e-10);
1875 }
1876
1877 #[test]
1878 fn test_pressure_pa() {
1879 let p = Pressure::pa(101325.0);
1880 assert_approx_eq!(p.to_hpa(), 1013.25, rtol <= 1e-10);
1881 }
1882
1883 #[test]
1884 fn test_pressure_display() {
1885 let p = Pressure::pa(101325.0);
1886 let s = format!("{}", p);
1887 assert!(s.contains("Pa"));
1888 }
1889
1890 #[test]
1891 fn test_mass_kilograms() {
1892 let m = Mass::kilograms(1.5);
1893 assert_eq!(m.to_kilograms(), 1.5);
1894 }
1895
1896 #[test]
1897 fn test_mass_grams() {
1898 let m = Mass::grams(2500.0);
1899 assert_approx_eq!(m.to_kilograms(), 2.5, rtol <= 1e-12);
1900 }
1901
1902 #[test]
1903 fn test_mass_metric_tons() {
1904 let m = Mass::metric_tons(0.5);
1905 assert_approx_eq!(m.to_kilograms(), 500.0, rtol <= 1e-12);
1906 }
1907
1908 #[test]
1909 fn test_mass_units_kg() {
1910 let m = 1.5.kg();
1911 assert_eq!(m.to_kilograms(), 1.5);
1912 }
1913
1914 #[test]
1915 fn test_mass_units_g() {
1916 let m = 500.0.g();
1917 assert_approx_eq!(m.to_kilograms(), 0.5, rtol <= 1e-12);
1918 }
1919
1920 #[test]
1921 fn test_mass_display() {
1922 let m = 12.5.kg();
1923 assert_eq!(format!("{:.2}", m), "12.50 kg");
1924 }
1925
1926 #[test]
1927 fn test_mass_neg() {
1928 assert_eq!(Mass(-1.0), -1.0.kg())
1929 }
1930
1931 #[test]
1932 fn test_area_square_meters() {
1933 let a = Area::square_meters(2.5);
1934 assert_eq!(a.to_square_meters(), 2.5);
1935 }
1936
1937 #[test]
1938 fn test_area_square_kilometers() {
1939 let a = Area::square_kilometers(1.0);
1940 assert_approx_eq!(a.to_square_meters(), 1e6, rtol <= 1e-12);
1941 }
1942
1943 #[test]
1944 fn test_area_units_m2() {
1945 let a = 9.0.m2();
1946 assert_eq!(a.to_square_meters(), 9.0);
1947 }
1948
1949 #[test]
1950 fn test_area_units_km2() {
1951 let a = 2.0.km2();
1952 assert_approx_eq!(a.to_square_meters(), 2e6, rtol <= 1e-12);
1953 }
1954
1955 #[test]
1956 fn test_area_display() {
1957 let a = 4.5.m2();
1958 assert_eq!(format!("{:.2}", a), "4.50 m²");
1959 }
1960
1961 #[test]
1962 fn test_area_neg() {
1963 assert_eq!(Area(-1.0), -1.0.m2())
1964 }
1965
1966 #[test]
1967 fn test_area_to_mass_square_meters_per_kilogram() {
1968 let r = AreaToMass::square_meters_per_kilogram(0.025);
1969 assert_eq!(r.to_square_meters_per_kilogram(), 0.025);
1970 }
1971
1972 #[test]
1973 fn test_area_to_mass_units_shorthand() {
1974 let r = 0.05.m2_per_kg();
1975 assert_eq!(r.to_square_meters_per_kilogram(), 0.05);
1976 }
1977
1978 #[test]
1979 fn test_area_to_mass_display() {
1980 let r = 0.05.m2_per_kg();
1981 assert_eq!(format!("{:.2}", r), "0.05 m²/kg");
1982 }
1983
1984 #[test]
1985 fn test_area_to_mass_neg() {
1986 assert_eq!(AreaToMass(-1.0), -1.0.m2_per_kg())
1987 }
1988
1989 #[test]
1994 fn test_angle_from_hms_erfa_tf2a() {
1995 let a = Angle::from_hms(Sign::Positive, 4, 58, 20.2);
1997 assert_approx_eq!(a.to_radians(), 1.301_739_278_189_537_4, atol <= 1e-12);
1998 }
1999
2000 #[test]
2001 fn test_angle_from_hms_negative_within_one_hour() {
2002 let a = Angle::from_hms(Sign::Negative, 0, 30, 0.0);
2004 assert!(a.to_radians() < 0.0);
2005 assert_approx_eq!(a.to_degrees(), -7.5, atol <= 1e-12);
2006 }
2007
2008 #[test]
2009 fn test_angle_arcseconds_roundtrip() {
2010 let a = Angle::arcseconds(3600.0); assert_approx_eq!(a.to_degrees(), 1.0, rtol <= 1e-10);
2012 assert_approx_eq!(a.to_arcseconds(), 3600.0, rtol <= 1e-10);
2013 }
2014
2015 #[test]
2016 fn test_angle_arcseconds_normalized() {
2017 let a = Angle::arcseconds_normalized(ARCSECONDS_IN_CIRCLE + 3600.0);
2019 assert_approx_eq!(a.to_arcseconds(), 3600.0, rtol <= 1e-10);
2020 }
2021
2022 #[test]
2023 fn test_angle_arcseconds_normalized_signed() {
2024 let a = Angle::arcseconds_normalized_signed(-ARCSECONDS_IN_CIRCLE - 3600.0);
2025 let deg = a.to_degrees();
2027 assert!(deg < 0.0);
2028 }
2029
2030 #[test]
2031 fn test_angle_degrees_normalized() {
2032 let a = Angle::degrees_normalized(370.0); assert_approx_eq!(a.to_degrees(), 10.0, rtol <= 1e-10);
2034 }
2035
2036 #[test]
2037 fn test_angle_degrees_normalized_signed() {
2038 let a = Angle::degrees_normalized_signed(-10.0);
2039 assert_approx_eq!(a.to_degrees(), -10.0, rtol <= 1e-10);
2040 }
2041
2042 #[test]
2043 fn test_angle_radians_normalized() {
2044 use core::f64::consts::TAU;
2045 let a = Angle::radians_normalized(TAU + 0.5);
2046 assert_approx_eq!(a.as_f64(), 0.5, rtol <= 1e-10);
2047 }
2048
2049 #[test]
2050 fn test_angle_radians_normalized_signed() {
2051 use core::f64::consts::TAU;
2052 let a = Angle::radians_normalized_signed(-TAU - 0.5);
2053 assert!(a.as_f64() < 0.0);
2054 }
2055
2056 #[test]
2057 fn test_angle_is_zero() {
2058 assert!(Angle::ZERO.is_zero());
2059 assert!(!Angle::PI.is_zero());
2060 }
2061
2062 #[test]
2063 fn test_angle_abs() {
2064 let a = Angle::radians(-1.5);
2065 assert_approx_eq!(a.abs().as_f64(), 1.5, rtol <= 1e-10);
2066 }
2067
2068 #[test]
2069 fn test_angle_from_asin() {
2070 let a = Angle::from_asin(1.0);
2071 assert_approx_eq!(a.to_degrees(), 90.0, rtol <= 1e-10);
2072 }
2073
2074 #[test]
2075 fn test_angle_from_acos() {
2076 let a = Angle::from_acos(1.0);
2077 assert_approx_eq!(a.to_degrees(), 0.0, atol <= 1e-10);
2078 }
2079
2080 #[test]
2081 fn test_angle_from_atan() {
2082 let a = Angle::from_atan(1.0);
2083 assert_approx_eq!(a.to_degrees(), 45.0, rtol <= 1e-10);
2084 }
2085
2086 #[test]
2087 fn test_angle_from_atan2() {
2088 let a = Angle::from_atan2(1.0, 1.0); assert_approx_eq!(a.to_degrees(), 45.0, rtol <= 1e-10);
2090 }
2091
2092 #[test]
2093 fn test_angle_from_asinh() {
2094 let a = Angle::from_asinh(0.0);
2095 assert_approx_eq!(a.as_f64(), 0.0, atol <= 1e-10);
2096 }
2097
2098 #[test]
2099 fn test_angle_from_acosh() {
2100 let a = Angle::from_acosh(1.0);
2101 assert_approx_eq!(a.as_f64(), 0.0, atol <= 1e-10);
2102 }
2103
2104 #[test]
2105 fn test_angle_from_atanh() {
2106 let a = Angle::from_atanh(0.0);
2107 assert_approx_eq!(a.as_f64(), 0.0, atol <= 1e-10);
2108 }
2109
2110 #[test]
2111 fn test_angle_trig_functions() {
2112 let a = Angle::FRAC_PI_2;
2113 assert_approx_eq!(a.sin(), 1.0, rtol <= 1e-10);
2114 assert_approx_eq!(a.cos(), 0.0, atol <= 1e-10);
2115 let (s, c) = a.sin_cos();
2116 assert_approx_eq!(s, 1.0, rtol <= 1e-10);
2117 assert_approx_eq!(c, 0.0, atol <= 1e-10);
2118 }
2119
2120 #[test]
2121 fn test_angle_tan() {
2122 let a = Angle::degrees(45.0);
2123 assert_approx_eq!(a.tan(), 1.0, rtol <= 1e-10);
2124 }
2125
2126 #[test]
2127 fn test_angle_hyperbolic_trig() {
2128 let a = Angle::radians(1.0);
2129 assert_approx_eq!(a.sinh(), sinh(1.0_f64), rtol <= 1e-10);
2130 assert_approx_eq!(a.cosh(), cosh(1.0_f64), rtol <= 1e-10);
2131 assert_approx_eq!(a.tanh(), tanh(1.0_f64), rtol <= 1e-10);
2132 }
2133
2134 #[test]
2135 fn test_angle_mod_two_pi() {
2136 use core::f64::consts::TAU;
2137 let a = Angle::radians(TAU + 1.0).mod_two_pi();
2138 assert_approx_eq!(a.as_f64(), 1.0, rtol <= 1e-10);
2139 }
2140
2141 #[test]
2142 fn test_angle_mod_two_pi_signed() {
2143 use core::f64::consts::TAU;
2144 let a = Angle::radians(TAU + 1.0).mod_two_pi_signed();
2145 assert_approx_eq!(a.as_f64(), 1.0, rtol <= 1e-10);
2146 }
2147
2148 #[test]
2149 fn test_angle_rotation_matrices() {
2150 let a = Angle::ZERO;
2151 let rx = a.rotation_x();
2153 let ry = a.rotation_y();
2154 let rz = a.rotation_z();
2155 for i in 0..3 {
2156 for j in 0..3 {
2157 let expected = if i == j { 1.0 } else { 0.0 };
2158 assert_approx_eq!(rx.col(i)[j], expected, atol <= 1e-10);
2159 assert_approx_eq!(ry.col(i)[j], expected, atol <= 1e-10);
2160 assert_approx_eq!(rz.col(i)[j], expected, atol <= 1e-10);
2161 }
2162 }
2163 }
2164
2165 #[test]
2166 fn test_angle_arithmetic() {
2167 let a = Angle::degrees(30.0);
2168 let b = Angle::degrees(60.0);
2169 assert_approx_eq!((a + b).to_degrees(), 90.0, rtol <= 1e-10);
2170 assert_approx_eq!((b - a).to_degrees(), 30.0, rtol <= 1e-10);
2171 let mut c = a;
2172 c += b;
2173 assert_approx_eq!(c.to_degrees(), 90.0, rtol <= 1e-10);
2174 let mut d = b;
2175 d -= a;
2176 assert_approx_eq!(d.to_degrees(), 30.0, rtol <= 1e-10);
2177 let scaled = 2.0 * a;
2178 assert_approx_eq!(scaled.to_degrees(), 60.0, rtol <= 1e-10);
2179 let f: f64 = a.into();
2180 assert_approx_eq!(f, a.as_f64(), rtol <= 1e-10);
2181 }
2182
2183 #[test]
2184 fn test_angle_i64_units() {
2185 let a = 90_i64.deg();
2186 assert_approx_eq!(a.to_degrees(), 90.0, rtol <= 1e-10);
2187 let b = 1_i64.rad();
2188 assert_approx_eq!(b.as_f64(), 1.0, rtol <= 1e-10);
2189 let c = 3600_i64.arcsec();
2190 assert_approx_eq!(c.to_degrees(), 1.0, rtol <= 1e-10);
2191 let d = 1000_i64.mas();
2192 assert_approx_eq!(d.to_degrees(), 1.0 / 3600.0, rtol <= 1e-8);
2193 let e = 1_000_000_i64.uas();
2194 assert_approx_eq!(e.to_degrees(), 1.0 / 3600.0, rtol <= 1e-8);
2195 }
2196
2197 #[test]
2198 fn test_angle_f64_mas_uas() {
2199 let a = 1000.0_f64.mas();
2200 assert_approx_eq!(a.to_degrees(), 1.0 / 3600.0, rtol <= 1e-8);
2201 let b = 1_000_000.0_f64.uas();
2202 assert_approx_eq!(b.to_degrees(), 1.0 / 3600.0, rtol <= 1e-8);
2203 }
2204
2205 #[test]
2210 fn test_distance_to_astronomical_units() {
2211 let d = Distance::astronomical_units(1.0);
2212 assert_approx_eq!(d.to_astronomical_units(), 1.0, rtol <= 1e-10);
2213 }
2214
2215 #[test]
2216 fn test_distance_as_f64() {
2217 let d = Distance::meters(5000.0);
2218 assert_eq!(d.as_f64(), 5000.0);
2219 }
2220
2221 #[test]
2222 fn test_distance_new() {
2223 let d = Distance::new(1234.0);
2224 assert_eq!(d.to_meters(), 1234.0);
2225 }
2226
2227 #[test]
2228 fn test_distance_to_meters() {
2229 let d = Distance::kilometers(1.0);
2230 assert_eq!(d.to_meters(), 1000.0);
2231 }
2232
2233 #[test]
2234 fn test_distance_i64_units() {
2235 let d = 7_i64.km();
2236 assert_eq!(d.to_meters(), 7000.0);
2237 let e = 1000_i64.m();
2238 assert_eq!(e.to_meters(), 1000.0);
2239 let f = 1_i64.au();
2240 assert_approx_eq!(f.to_meters(), ASTRONOMICAL_UNIT, rtol <= 1e-10);
2241 }
2242
2243 #[test]
2244 fn test_distance_arithmetic() {
2245 let a = Distance::meters(100.0);
2246 let b = Distance::meters(50.0);
2247 assert_eq!((a + b).to_meters(), 150.0);
2248 assert_eq!((a - b).to_meters(), 50.0);
2249 let mut c = a;
2250 c += b;
2251 assert_eq!(c.to_meters(), 150.0);
2252 let mut d = a;
2253 d -= b;
2254 assert_eq!(d.to_meters(), 50.0);
2255 let scaled = 2.0 * a;
2256 assert_eq!(scaled.to_meters(), 200.0);
2257 let f: f64 = a.into();
2258 assert_eq!(f, 100.0);
2259 }
2260
2261 #[test]
2266 fn test_velocity_astronomical_units_per_day() {
2267 let v = Velocity::astronomical_units_per_day(1.0);
2268 let expected = ASTRONOMICAL_UNIT / SECONDS_PER_DAY;
2269 assert_approx_eq!(v.to_meters_per_second(), expected, rtol <= 1e-10);
2270 assert_approx_eq!(v.to_astronomical_units_per_day(), 1.0, rtol <= 1e-10);
2271 }
2272
2273 #[test]
2274 fn test_velocity_fraction_of_speed_of_light() {
2275 let v = Velocity::fraction_of_speed_of_light(1.0);
2276 assert_approx_eq!(v.to_meters_per_second(), SPEED_OF_LIGHT, rtol <= 1e-10);
2277 assert_approx_eq!(v.to_fraction_of_speed_of_light(), 1.0, rtol <= 1e-10);
2278 }
2279
2280 #[test]
2281 fn test_velocity_new() {
2282 let v = Velocity::new(300.0);
2283 assert_eq!(v.as_f64(), 300.0);
2284 }
2285
2286 #[test]
2287 fn test_velocity_to_km_per_second() {
2288 let v = Velocity::meters_per_second(3000.0);
2289 assert_approx_eq!(v.to_kilometers_per_second(), 3.0, rtol <= 1e-10);
2290 }
2291
2292 #[test]
2293 fn test_velocity_i64_units() {
2294 let a = 7_i64.kps();
2295 assert_eq!(a.to_meters_per_second(), 7000.0);
2296 let b = 1_i64.mps();
2297 assert_eq!(b.to_meters_per_second(), 1.0);
2298 let c = 1_i64.aud();
2299 assert_approx_eq!(
2300 c.to_meters_per_second(),
2301 ASTRONOMICAL_UNIT / SECONDS_PER_DAY,
2302 rtol <= 1e-10
2303 );
2304 let d = 1_i64.c();
2305 assert_approx_eq!(d.to_meters_per_second(), SPEED_OF_LIGHT, rtol <= 1e-10);
2306 }
2307
2308 #[test]
2309 fn test_velocity_arithmetic() {
2310 let a = Velocity::meters_per_second(500.0);
2311 let b = Velocity::meters_per_second(250.0);
2312 assert_eq!((a + b).to_meters_per_second(), 750.0);
2313 assert_eq!((a - b).to_meters_per_second(), 250.0);
2314 let scaled = 3.0 * b;
2315 assert_eq!(scaled.to_meters_per_second(), 750.0);
2316 let f: f64 = a.into();
2317 assert_eq!(f, 500.0);
2318 }
2319
2320 #[test]
2325 fn test_mass_new() {
2326 let m = Mass::new(1.5);
2327 assert_eq!(m.as_f64(), 1.5);
2328 }
2329
2330 #[test]
2331 fn test_mass_to_grams() {
2332 let m = Mass::kilograms(2.0);
2333 assert_approx_eq!(m.to_grams(), 2000.0, rtol <= 1e-12);
2334 }
2335
2336 #[test]
2337 fn test_mass_to_metric_tons() {
2338 let m = Mass::kilograms(1000.0);
2339 assert_approx_eq!(m.to_metric_tons(), 1.0, rtol <= 1e-12);
2340 }
2341
2342 #[test]
2343 fn test_mass_i64_metric_tons() {
2344 let m = 1_i64.t();
2345 assert_approx_eq!(m.to_kilograms(), 1000.0, rtol <= 1e-12);
2346 }
2347
2348 #[test]
2349 fn test_mass_arithmetic() {
2350 let a = Mass::kilograms(100.0);
2351 let b = Mass::kilograms(50.0);
2352 assert_eq!((a + b).to_kilograms(), 150.0);
2353 assert_eq!((a - b).to_kilograms(), 50.0);
2354 let mut c = a;
2355 c += b;
2356 assert_eq!(c.to_kilograms(), 150.0);
2357 let scaled = 2.0 * b;
2358 assert_eq!(scaled.to_kilograms(), 100.0);
2359 let f: f64 = a.into();
2360 assert_eq!(f, 100.0);
2361 }
2362
2363 #[test]
2368 fn test_area_new() {
2369 let a = Area::new(5.0);
2370 assert_eq!(a.as_f64(), 5.0);
2371 }
2372
2373 #[test]
2374 fn test_area_to_square_kilometers() {
2375 let a = Area::square_meters(1_000_000.0);
2376 assert_approx_eq!(a.to_square_kilometers(), 1.0, rtol <= 1e-12);
2377 }
2378
2379 #[test]
2380 fn test_area_i64_units() {
2381 let a = 4_i64.m2();
2382 assert_eq!(a.to_square_meters(), 4.0);
2383 let b = 2_i64.km2();
2384 assert_approx_eq!(b.to_square_meters(), 2e6, rtol <= 1e-12);
2385 }
2386
2387 #[test]
2388 fn test_area_arithmetic() {
2389 let a = Area::square_meters(4.0);
2390 let b = Area::square_meters(2.0);
2391 assert_eq!((a + b).to_square_meters(), 6.0);
2392 assert_eq!((a - b).to_square_meters(), 2.0);
2393 let scaled = 3.0 * b;
2394 assert_eq!(scaled.to_square_meters(), 6.0);
2395 let f: f64 = a.into();
2396 assert_eq!(f, 4.0);
2397 }
2398
2399 #[test]
2404 fn test_area_to_mass_new() {
2405 let r = AreaToMass::new(0.1);
2406 assert_eq!(r.as_f64(), 0.1);
2407 }
2408
2409 #[test]
2410 fn test_area_to_mass_i64_units() {
2411 let r = 1_i64.m2_per_kg();
2412 assert_eq!(r.to_square_meters_per_kilogram(), 1.0);
2413 }
2414
2415 #[test]
2416 fn test_area_to_mass_arithmetic() {
2417 let a = AreaToMass::square_meters_per_kilogram(0.1);
2418 let b = AreaToMass::square_meters_per_kilogram(0.05);
2419 assert_approx_eq!((a + b).as_f64(), 0.15, rtol <= 1e-10);
2420 assert_approx_eq!((a - b).as_f64(), 0.05, rtol <= 1e-10);
2421 let scaled = 2.0 * b;
2422 assert_approx_eq!(scaled.as_f64(), 0.1, rtol <= 1e-10);
2423 let f: f64 = a.into();
2424 assert_approx_eq!(f, 0.1, rtol <= 1e-10);
2425 }
2426
2427 #[test]
2432 fn test_frequency_new() {
2433 let f = Frequency::new(1e9);
2434 assert_eq!(f.to_hertz(), 1e9);
2435 }
2436
2437 #[test]
2438 fn test_frequency_to_kilohertz() {
2439 let f = Frequency::megahertz(1.0);
2440 assert_approx_eq!(f.to_kilohertz(), 1000.0, rtol <= 1e-10);
2441 }
2442
2443 #[test]
2444 fn test_frequency_to_terahertz() {
2445 let f = Frequency::terahertz(1.0);
2446 assert_approx_eq!(f.to_terahertz(), 1.0, rtol <= 1e-10);
2447 }
2448
2449 #[test]
2450 fn test_frequency_arithmetic() {
2451 let a = Frequency::gigahertz(1.0);
2452 let b = Frequency::gigahertz(0.5);
2453 assert_approx_eq!((a + b).to_gigahertz(), 1.5, rtol <= 1e-10);
2454 assert_approx_eq!((a - b).to_gigahertz(), 0.5, rtol <= 1e-10);
2455 let scaled = 2.0 * b;
2456 assert_approx_eq!(scaled.to_gigahertz(), 1.0, rtol <= 1e-10);
2457 let f: f64 = a.into();
2458 assert_approx_eq!(f, 1e9, rtol <= 1e-10);
2459 }
2460
2461 #[test]
2466 fn test_temperature_neg() {
2467 let t = Temperature::kelvin(100.0);
2468 assert_eq!((-t).as_f64(), -100.0);
2469 }
2470
2471 #[test]
2472 fn test_temperature_add_assign_sub_assign() {
2473 let mut a = Temperature::kelvin(200.0);
2474 a += Temperature::kelvin(50.0);
2475 assert_eq!(a.as_f64(), 250.0);
2476 a -= Temperature::kelvin(100.0);
2477 assert_eq!(a.as_f64(), 150.0);
2478 }
2479
2480 #[test]
2485 fn test_pressure_new() {
2486 let p = Pressure::new(101325.0);
2487 assert_eq!(p.as_f64(), 101325.0);
2488 }
2489
2490 #[test]
2491 fn test_pressure_arithmetic() {
2492 let a = Pressure::pa(1000.0);
2493 let b = Pressure::pa(500.0);
2494 assert_eq!((a + b).to_pa(), 1500.0);
2495 assert_eq!((a - b).to_pa(), 500.0);
2496 assert_eq!((-b).to_pa(), -500.0);
2497 let scaled = 2.0 * b;
2498 assert_eq!(scaled.to_pa(), 1000.0);
2499 let f: f64 = a.into();
2500 assert_eq!(f, 1000.0);
2501 }
2502
2503 #[test]
2508 fn test_power_new() {
2509 let p = Power::new(500.0);
2510 assert_eq!(p.as_f64(), 500.0);
2511 }
2512
2513 #[test]
2514 fn test_power_add_assign_sub_assign() {
2515 let mut p = Power::watts(200.0);
2516 p += Power::watts(100.0);
2517 assert_eq!(p.to_watts(), 300.0);
2518 p -= Power::watts(50.0);
2519 assert_eq!(p.to_watts(), 250.0);
2520 }
2521
2522 #[test]
2527 fn test_angular_rate_new() {
2528 let ar = AngularRate::new(2.0);
2529 assert_eq!(ar.as_f64(), 2.0);
2530 }
2531
2532 #[test]
2533 fn test_angular_rate_add_assign_sub_assign() {
2534 let mut ar = AngularRate::radians_per_second(1.0);
2535 ar += AngularRate::radians_per_second(0.5);
2536 assert_approx_eq!(ar.as_f64(), 1.5, rtol <= 1e-10);
2537 ar -= AngularRate::radians_per_second(0.5);
2538 assert_approx_eq!(ar.as_f64(), 1.0, rtol <= 1e-10);
2539 let f: f64 = ar.into();
2540 assert_approx_eq!(f, 1.0, rtol <= 1e-10);
2541 }
2542
2543 #[test]
2548 fn test_decibel_add_assign_sub_assign() {
2549 let mut d = Decibel::new(10.0);
2550 d += Decibel::new(3.0);
2551 assert_approx_eq!(d.as_f64(), 13.0, rtol <= 1e-10);
2552 d -= Decibel::new(3.0);
2553 assert_approx_eq!(d.as_f64(), 10.0, rtol <= 1e-10);
2554 }
2555
2556 #[test]
2557 fn test_decibel_i64_units() {
2558 let d = 10_i64.db();
2559 assert_eq!(d.as_f64(), 10.0);
2560 }
2561
2562 #[test]
2563 fn test_decibel_mul_and_into() {
2564 let d = Decibel::new(5.0);
2565 let scaled = 2.0 * d;
2566 assert_approx_eq!(scaled.as_f64(), 10.0, rtol <= 1e-10);
2567 let f: f64 = d.into();
2568 assert_eq!(f, 5.0);
2569 }
2570
2571 #[rstest]
2576 #[case(1.0_f64, Sign::Positive)]
2577 #[case(-1.0_f64, Sign::Negative)]
2578 #[case(0.0_f64, Sign::Positive)]
2579 #[case(-0.0_f64, Sign::Negative)] #[case(f64::INFINITY, Sign::Positive)]
2581 #[case(f64::NEG_INFINITY, Sign::Negative)]
2582 fn test_sign_from_f64(#[case] input: f64, #[case] expected: Sign) {
2583 assert_eq!(Sign::from(input), expected);
2584 }
2585
2586 #[rstest]
2587 #[case(1_i32, Sign::Positive)]
2588 #[case(-1_i32, Sign::Negative)]
2589 #[case(0_i32, Sign::Positive)]
2590 fn test_sign_from_i32(#[case] input: i32, #[case] expected: Sign) {
2591 assert_eq!(Sign::from(input), expected);
2592 }
2593
2594 #[test]
2595 fn test_sign_from_signed_integer_widths() {
2596 assert_eq!(Sign::from(-1_i8), Sign::Negative);
2597 assert_eq!(Sign::from(-1_i16), Sign::Negative);
2598 assert_eq!(Sign::from(-1_i64), Sign::Negative);
2599 assert_eq!(Sign::from(-1_isize), Sign::Negative);
2600 }
2601
2602 #[test]
2603 fn test_sign_to_f64() {
2604 assert_eq!(f64::from(Sign::Positive), 1.0);
2605 assert_eq!(f64::from(Sign::Negative), -1.0);
2606 assert_eq!(Sign::Positive.as_f64(), 1.0);
2607 assert_eq!(Sign::Negative.as_f64(), -1.0);
2608 }
2609
2610 #[test]
2611 fn test_sign_display() {
2612 assert_eq!(format!("{}", Sign::Positive), "+");
2613 assert_eq!(format!("{}", Sign::Negative), "-");
2614 }
2615
2616 #[test]
2621 fn test_angle_from_dms_erfa_af2a() {
2622 let a = Angle::from_dms(Sign::Negative, 45, 13, 27.2);
2624 assert_approx_eq!(a.to_radians(), -0.789_311_579_431_364_4, atol <= 1e-12);
2625 }
2626
2627 #[test]
2628 fn test_angle_from_dms_negative_within_one_degree() {
2629 let a = Angle::from_dms(Sign::Negative, 0, 30, 0.0);
2631 assert!(a.to_radians() < 0.0);
2632 assert_approx_eq!(a.to_degrees(), -0.5, atol <= 1e-12);
2633 }
2634
2635 #[test]
2640 fn test_angle_to_hms_erfa_a2tf() {
2641 let (sign, hours, min, sec) = Angle::radians(-3.01234).to_hms();
2643 assert_eq!(sign, Sign::Negative);
2644 assert_eq!(hours, 11);
2645 assert_eq!(min, 30);
2646 assert_approx_eq!(sec, 22.6484, atol <= 1e-4);
2647 }
2648
2649 #[test]
2650 fn test_angle_to_hms_negative_within_one_hour() {
2651 let (sign, hours, min, sec) = Angle::degrees(-7.5).to_hms();
2653 assert_eq!(sign, Sign::Negative);
2654 assert_eq!(hours, 0);
2655 assert_eq!(min, 30);
2656 assert_approx_eq!(sec, 0.0, atol <= 1e-10);
2657 }
2658
2659 #[test]
2664 fn test_angle_to_dms_erfa_a2af() {
2665 let (sign, deg, min, sec) = Angle::radians(2.345).to_dms();
2667 assert_eq!(sign, Sign::Positive);
2668 assert_eq!(deg, 134);
2669 assert_eq!(min, 21);
2670 assert_approx_eq!(sec, 30.9706, atol <= 1e-4);
2671 }
2672
2673 #[test]
2674 fn test_angle_to_dms_negative() {
2675 let (sign, deg, min, sec) = Angle::degrees(-0.5).to_dms();
2677 assert_eq!(sign, Sign::Negative);
2678 assert_eq!(deg, 0);
2679 assert_eq!(min, 30);
2680 assert_approx_eq!(sec, 0.0, atol <= 1e-10);
2681 }
2682
2683 #[test]
2684 fn test_angle_to_dms_zero() {
2685 let (sign, deg, min, sec) = Angle::ZERO.to_dms();
2686 assert_eq!(sign, Sign::Positive);
2687 assert_eq!(deg, 0);
2688 assert_eq!(min, 0);
2689 assert_eq!(sec, 0.0);
2690 }
2691
2692 #[rstest]
2693 #[case(0.0)]
2694 #[case(0.5)]
2695 #[case(-0.5)]
2696 #[case(2.345)]
2697 #[case(-3.01234)]
2698 #[case(core::f64::consts::PI)]
2699 #[case(-core::f64::consts::PI)]
2700 fn test_angle_dms_roundtrip(#[case] radians: f64) {
2701 let a = Angle::radians(radians);
2702 let (sign, deg, min, sec) = a.to_dms();
2703 let b = Angle::from_dms(sign, deg, min, sec);
2704 assert_approx_eq!(a.to_radians(), b.to_radians(), atol <= 1e-12);
2705 }
2706
2707 #[rstest]
2708 #[case(0.0)]
2709 #[case(0.5)]
2710 #[case(-0.5)]
2711 #[case(2.345)]
2712 #[case(-3.01234)]
2713 #[case(core::f64::consts::PI)]
2714 #[case(-core::f64::consts::PI)]
2715 fn test_angle_hms_roundtrip(#[case] radians: f64) {
2716 let a = Angle::radians(radians);
2717 let (sign, hours, min, sec) = a.to_hms();
2718 let b = Angle::from_hms(sign, hours, min, sec);
2719 assert_approx_eq!(a.to_radians(), b.to_radians(), atol <= 1e-12);
2720 }
2721}