1use std::{
8 f64::consts::{FRAC_PI_2, FRAC_PI_4, PI, TAU},
9 fmt::{Display, Formatter, Result},
10 ops::{Add, AddAssign, Mul, Neg, Sub, SubAssign},
11};
12
13use glam::DMat3;
14use lox_test_utils::ApproxEq;
15
16use crate::f64::consts::SECONDS_PER_DAY;
17
18pub const DEGREES_IN_CIRCLE: f64 = 360.0;
20
21pub const ARCSECONDS_IN_CIRCLE: f64 = DEGREES_IN_CIRCLE * 60.0 * 60.0;
23
24pub const RADIANS_IN_ARCSECOND: f64 = TAU / ARCSECONDS_IN_CIRCLE;
26
27type Radians = f64;
28
29#[derive(Copy, Clone, Debug, Default, PartialEq, PartialOrd, ApproxEq)]
31#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
32#[repr(transparent)]
33pub struct Angle(Radians);
34
35impl Angle {
36 pub const ZERO: Self = Self(0.0);
38 pub const PI: Self = Self(PI);
40 pub const TAU: Self = Self(TAU);
42 pub const FRAC_PI_2: Self = Self(FRAC_PI_2);
44 pub const FRAC_PI_4: Self = Self(FRAC_PI_4);
46
47 pub const fn new(rad: f64) -> Self {
49 Self(rad)
50 }
51
52 pub const fn radians(rad: f64) -> Self {
54 Self(rad)
55 }
56
57 pub const fn radians_normalized(rad: f64) -> Self {
60 Self(rad).mod_two_pi()
61 }
62
63 pub const fn radians_normalized_signed(rad: f64) -> Self {
66 Self(rad).mod_two_pi_signed()
67 }
68
69 pub const fn degrees(deg: f64) -> Self {
71 Self(deg.to_radians())
72 }
73
74 pub const fn from_hms(hours: i64, minutes: u8, seconds: f64) -> Self {
76 Self::degrees(15.0 * (hours as f64 + minutes as f64 / 60.0 + seconds / 3600.0))
77 }
78
79 pub const fn degrees_normalized(deg: f64) -> Self {
82 Self((deg % DEGREES_IN_CIRCLE).to_radians()).mod_two_pi()
83 }
84
85 pub const fn degrees_normalized_signed(deg: f64) -> Self {
88 Self((deg % DEGREES_IN_CIRCLE).to_radians())
89 }
90
91 pub const fn arcseconds(asec: f64) -> Self {
93 Self(asec * RADIANS_IN_ARCSECOND)
94 }
95
96 pub const fn arcseconds_normalized(asec: f64) -> Self {
99 Self((asec % ARCSECONDS_IN_CIRCLE) * RADIANS_IN_ARCSECOND).mod_two_pi()
100 }
101
102 pub const fn arcseconds_normalized_signed(asec: f64) -> Self {
105 Self((asec % ARCSECONDS_IN_CIRCLE) * RADIANS_IN_ARCSECOND)
106 }
107
108 pub fn is_zero(&self) -> bool {
110 self.0 == 0.0
111 }
112
113 pub const fn abs(&self) -> Self {
115 Self(self.0.abs())
116 }
117
118 pub fn from_asin(value: f64) -> Self {
120 Self(value.asin())
121 }
122
123 pub fn from_asinh(value: f64) -> Self {
125 Self(value.asinh())
126 }
127
128 pub fn from_acos(value: f64) -> Self {
130 Self(value.acos())
131 }
132
133 pub fn from_acosh(value: f64) -> Self {
135 Self(value.acosh())
136 }
137
138 pub fn from_atan(value: f64) -> Self {
140 Self(value.atan())
141 }
142
143 pub fn from_atanh(value: f64) -> Self {
145 Self(value.atanh())
146 }
147
148 pub fn from_atan2(y: f64, x: f64) -> Self {
150 Self(y.atan2(x))
151 }
152
153 pub fn cos(&self) -> f64 {
155 self.0.cos()
156 }
157
158 pub fn cosh(&self) -> f64 {
160 self.0.cosh()
161 }
162
163 pub fn sin(&self) -> f64 {
165 self.0.sin()
166 }
167
168 pub fn sinh(&self) -> f64 {
170 self.0.sinh()
171 }
172
173 pub fn sin_cos(&self) -> (f64, f64) {
175 self.0.sin_cos()
176 }
177
178 pub fn tan(&self) -> f64 {
180 self.0.tan()
181 }
182
183 pub fn tanh(&self) -> f64 {
185 self.0.tanh()
186 }
187
188 pub const fn mod_two_pi(&self) -> Self {
190 let mut a = self.0 % TAU;
191 if a < 0.0 {
192 a += TAU
193 }
194 Self(a)
195 }
196
197 pub const fn mod_two_pi_signed(&self) -> Self {
199 Self(self.0 % TAU)
200 }
201
202 pub const fn normalize_two_pi(&self, center: Self) -> Self {
205 Self(self.0 - TAU * ((self.0 + PI - center.0) / TAU).floor())
206 }
207
208 pub const fn as_f64(&self) -> f64 {
210 self.0
211 }
212
213 pub const fn to_radians(&self) -> f64 {
215 self.0
216 }
217
218 pub const fn to_degrees(&self) -> f64 {
220 self.0.to_degrees()
221 }
222
223 pub const fn to_arcseconds(&self) -> f64 {
225 self.0 / RADIANS_IN_ARCSECOND
226 }
227
228 pub fn rotation_x(&self) -> DMat3 {
230 DMat3::from_rotation_x(-self.to_radians())
231 }
232
233 pub fn rotation_y(&self) -> DMat3 {
235 DMat3::from_rotation_y(-self.to_radians())
236 }
237
238 pub fn rotation_z(&self) -> DMat3 {
240 DMat3::from_rotation_z(-self.to_radians())
241 }
242}
243
244impl Display for Angle {
245 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
246 self.0.to_degrees().fmt(f)?;
247 write!(f, " deg")
248 }
249}
250
251pub trait AngleUnits {
264 fn rad(&self) -> Angle;
266 fn deg(&self) -> Angle;
268 fn arcsec(&self) -> Angle;
270 fn mas(&self) -> Angle;
272 fn uas(&self) -> Angle;
274}
275
276impl AngleUnits for f64 {
277 fn rad(&self) -> Angle {
278 Angle::radians(*self)
279 }
280
281 fn deg(&self) -> Angle {
282 Angle::degrees(*self)
283 }
284
285 fn arcsec(&self) -> Angle {
286 Angle::arcseconds(*self)
287 }
288
289 fn mas(&self) -> Angle {
290 Angle::arcseconds(self * 1e-3)
291 }
292
293 fn uas(&self) -> Angle {
294 Angle::arcseconds(self * 1e-6)
295 }
296}
297
298impl AngleUnits for i64 {
299 fn rad(&self) -> Angle {
300 Angle::radians(*self as f64)
301 }
302
303 fn deg(&self) -> Angle {
304 Angle::degrees(*self as f64)
305 }
306
307 fn arcsec(&self) -> Angle {
308 Angle::arcseconds(*self as f64)
309 }
310
311 fn mas(&self) -> Angle {
312 Angle::arcseconds(*self as f64 * 1e-3)
313 }
314
315 fn uas(&self) -> Angle {
316 Angle::arcseconds(*self as f64 * 1e-6)
317 }
318}
319
320pub const ASTRONOMICAL_UNIT: f64 = 1.495978707e11;
322
323type Meters = f64;
324
325#[derive(Copy, Clone, Debug, Default, PartialEq, PartialOrd, ApproxEq)]
327#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
328#[repr(transparent)]
329pub struct Distance(Meters);
330
331impl Distance {
332 pub const fn new(m: f64) -> Self {
334 Self(m)
335 }
336
337 pub const fn meters(m: f64) -> Self {
339 Self(m)
340 }
341
342 pub const fn kilometers(m: f64) -> Self {
344 Self(m * 1e3)
345 }
346
347 pub const fn astronomical_units(au: f64) -> Self {
349 Self(au * ASTRONOMICAL_UNIT)
350 }
351
352 pub const fn as_f64(&self) -> f64 {
354 self.0
355 }
356
357 pub const fn to_meters(&self) -> f64 {
359 self.0
360 }
361
362 pub const fn to_kilometers(&self) -> f64 {
364 self.0 * 1e-3
365 }
366
367 pub const fn to_astronomical_units(&self) -> f64 {
369 self.0 / ASTRONOMICAL_UNIT
370 }
371}
372
373impl Display for Distance {
374 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
375 (1e-3 * self.0).fmt(f)?;
376 write!(f, " km")
377 }
378}
379
380pub trait DistanceUnits {
393 fn m(&self) -> Distance;
395 fn km(&self) -> Distance;
397 fn au(&self) -> Distance;
399}
400
401impl DistanceUnits for f64 {
402 fn m(&self) -> Distance {
403 Distance::meters(*self)
404 }
405
406 fn km(&self) -> Distance {
407 Distance::kilometers(*self)
408 }
409
410 fn au(&self) -> Distance {
411 Distance::astronomical_units(*self)
412 }
413}
414
415impl DistanceUnits for i64 {
416 fn m(&self) -> Distance {
417 Distance::meters(*self as f64)
418 }
419
420 fn km(&self) -> Distance {
421 Distance::kilometers(*self as f64)
422 }
423
424 fn au(&self) -> Distance {
425 Distance::astronomical_units(*self as f64)
426 }
427}
428
429type MetersPerSecond = f64;
430
431#[derive(Copy, Clone, Debug, Default, PartialEq, PartialOrd, ApproxEq)]
433#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
434#[repr(transparent)]
435pub struct Velocity(MetersPerSecond);
436
437impl Velocity {
438 pub const fn new(mps: f64) -> Self {
440 Self(mps)
441 }
442
443 pub const fn meters_per_second(mps: f64) -> Self {
445 Self(mps)
446 }
447
448 pub const fn kilometers_per_second(mps: f64) -> Self {
450 Self(mps * 1e3)
451 }
452
453 pub const fn astronomical_units_per_day(aud: f64) -> Self {
455 Self(aud * ASTRONOMICAL_UNIT / SECONDS_PER_DAY)
456 }
457
458 pub const fn fraction_of_speed_of_light(c: f64) -> Self {
460 Self(c * SPEED_OF_LIGHT)
461 }
462
463 pub const fn as_f64(&self) -> f64 {
465 self.0
466 }
467
468 pub const fn to_meters_per_second(&self) -> f64 {
470 self.0
471 }
472
473 pub const fn to_kilometers_per_second(&self) -> f64 {
475 self.0 * 1e-3
476 }
477
478 pub const fn to_astronomical_units_per_day(&self) -> f64 {
480 self.0 * SECONDS_PER_DAY / ASTRONOMICAL_UNIT
481 }
482
483 pub const fn to_fraction_of_speed_of_light(&self) -> f64 {
485 self.0 / SPEED_OF_LIGHT
486 }
487}
488
489impl Display for Velocity {
490 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
491 (1e-3 * self.0).fmt(f)?;
492 write!(f, " km/s")
493 }
494}
495
496pub trait VelocityUnits {
509 fn mps(&self) -> Velocity;
511 fn kps(&self) -> Velocity;
513 fn aud(&self) -> Velocity;
515 fn c(&self) -> Velocity;
517}
518
519impl VelocityUnits for f64 {
520 fn mps(&self) -> Velocity {
521 Velocity::meters_per_second(*self)
522 }
523
524 fn kps(&self) -> Velocity {
525 Velocity::kilometers_per_second(*self)
526 }
527
528 fn aud(&self) -> Velocity {
529 Velocity::astronomical_units_per_day(*self)
530 }
531
532 fn c(&self) -> Velocity {
533 Velocity::fraction_of_speed_of_light(*self)
534 }
535}
536
537impl VelocityUnits for i64 {
538 fn mps(&self) -> Velocity {
539 Velocity::meters_per_second(*self as f64)
540 }
541
542 fn kps(&self) -> Velocity {
543 Velocity::kilometers_per_second(*self as f64)
544 }
545
546 fn aud(&self) -> Velocity {
547 Velocity::astronomical_units_per_day(*self as f64)
548 }
549
550 fn c(&self) -> Velocity {
551 Velocity::fraction_of_speed_of_light(*self as f64)
552 }
553}
554
555pub const SPEED_OF_LIGHT: f64 = 299792458.0;
557
558#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
560#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
561pub enum FrequencyBand {
562 HF,
564 VHF,
566 UHF,
568 L,
570 S,
572 C,
574 X,
576 Ku,
578 K,
580 Ka,
582 V,
584 W,
586 G,
588}
589
590type Hertz = f64;
591
592#[derive(Copy, Clone, Debug, Default, PartialEq, PartialOrd, ApproxEq)]
594#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
595#[repr(transparent)]
596pub struct Frequency(Hertz);
597
598impl Frequency {
599 pub const fn new(hz: Hertz) -> Self {
601 Self(hz)
602 }
603
604 pub const fn hertz(hz: Hertz) -> Self {
606 Self(hz)
607 }
608
609 pub const fn kilohertz(hz: Hertz) -> Self {
611 Self(hz * 1e3)
612 }
613
614 pub const fn megahertz(hz: Hertz) -> Self {
616 Self(hz * 1e6)
617 }
618
619 pub const fn gigahertz(hz: Hertz) -> Self {
621 Self(hz * 1e9)
622 }
623
624 pub const fn terahertz(hz: Hertz) -> Self {
626 Self(hz * 1e12)
627 }
628
629 pub const fn to_hertz(&self) -> f64 {
631 self.0
632 }
633
634 pub const fn to_kilohertz(&self) -> f64 {
636 self.0 * 1e-3
637 }
638
639 pub const fn to_megahertz(&self) -> f64 {
641 self.0 * 1e-6
642 }
643
644 pub const fn to_gigahertz(&self) -> f64 {
646 self.0 * 1e-9
647 }
648
649 pub const fn to_terahertz(&self) -> f64 {
651 self.0 * 1e-12
652 }
653
654 pub fn wavelength(&self) -> Distance {
656 Distance(SPEED_OF_LIGHT / self.0)
657 }
658
659 pub fn band(&self) -> Option<FrequencyBand> {
661 match self.0 {
662 f if f < 3e6 => None,
663 f if f < 30e6 => Some(FrequencyBand::HF),
664 f if f < 300e6 => Some(FrequencyBand::VHF),
665 f if f < 1e9 => Some(FrequencyBand::UHF),
666 f if f < 2e9 => Some(FrequencyBand::L),
667 f if f < 4e9 => Some(FrequencyBand::S),
668 f if f < 8e9 => Some(FrequencyBand::C),
669 f if f < 12e9 => Some(FrequencyBand::X),
670 f if f < 18e9 => Some(FrequencyBand::Ku),
671 f if f < 27e9 => Some(FrequencyBand::K),
672 f if f < 40e9 => Some(FrequencyBand::Ka),
673 f if f < 75e9 => Some(FrequencyBand::V),
674 f if f < 110e9 => Some(FrequencyBand::W),
675 f if f < 300e9 => Some(FrequencyBand::G),
676 _ => None,
677 }
678 }
679}
680
681impl Display for Frequency {
682 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
683 (1e-9 * self.0).fmt(f)?;
684 write!(f, " GHz")
685 }
686}
687
688pub trait FrequencyUnits {
701 fn hz(&self) -> Frequency;
703 fn khz(&self) -> Frequency;
705 fn mhz(&self) -> Frequency;
707 fn ghz(&self) -> Frequency;
709 fn thz(&self) -> Frequency;
711}
712
713impl FrequencyUnits for f64 {
714 fn hz(&self) -> Frequency {
715 Frequency::hertz(*self)
716 }
717
718 fn khz(&self) -> Frequency {
719 Frequency::kilohertz(*self)
720 }
721
722 fn mhz(&self) -> Frequency {
723 Frequency::megahertz(*self)
724 }
725
726 fn ghz(&self) -> Frequency {
727 Frequency::gigahertz(*self)
728 }
729
730 fn thz(&self) -> Frequency {
731 Frequency::terahertz(*self)
732 }
733}
734
735impl FrequencyUnits for i64 {
736 fn hz(&self) -> Frequency {
737 Frequency::hertz(*self as f64)
738 }
739
740 fn khz(&self) -> Frequency {
741 Frequency::kilohertz(*self as f64)
742 }
743
744 fn mhz(&self) -> Frequency {
745 Frequency::megahertz(*self as f64)
746 }
747
748 fn ghz(&self) -> Frequency {
749 Frequency::gigahertz(*self as f64)
750 }
751
752 fn thz(&self) -> Frequency {
753 Frequency::terahertz(*self as f64)
754 }
755}
756
757pub type Kelvin = f64;
759
760type KelvinValue = f64;
761
762#[derive(Copy, Clone, Debug, Default, PartialEq, PartialOrd, ApproxEq)]
764#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
765#[repr(transparent)]
766pub struct Temperature(KelvinValue);
767
768impl Temperature {
769 pub const fn new(k: f64) -> Self {
771 Self(k)
772 }
773
774 pub const fn kelvin(k: f64) -> Self {
776 Self(k)
777 }
778
779 pub const fn as_f64(&self) -> f64 {
781 self.0
782 }
783
784 pub const fn to_kelvin(&self) -> f64 {
786 self.0
787 }
788}
789
790impl Display for Temperature {
791 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
792 self.0.fmt(f)?;
793 write!(f, " K")
794 }
795}
796
797type Pascals = f64;
798
799#[derive(Copy, Clone, Debug, Default, PartialEq, PartialOrd, ApproxEq)]
801#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
802#[repr(transparent)]
803pub struct Pressure(Pascals);
804
805impl Pressure {
806 pub const fn new(pa: f64) -> Self {
808 Self(pa)
809 }
810
811 pub const fn pa(pa: f64) -> Self {
813 Self(pa)
814 }
815
816 pub const fn hpa(hpa: f64) -> Self {
818 Self(hpa * 100.0)
819 }
820
821 pub const fn as_f64(&self) -> f64 {
823 self.0
824 }
825
826 pub const fn to_pa(&self) -> f64 {
828 self.0
829 }
830
831 pub const fn to_hpa(&self) -> f64 {
833 self.0 * 0.01
834 }
835}
836
837impl Display for Pressure {
838 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
839 self.0.fmt(f)?;
840 write!(f, " Pa")
841 }
842}
843
844type Watts = f64;
845
846#[derive(Copy, Clone, Debug, Default, PartialEq, PartialOrd, ApproxEq)]
848#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
849#[repr(transparent)]
850pub struct Power(Watts);
851
852impl Power {
853 pub const fn new(w: f64) -> Self {
855 Self(w)
856 }
857
858 pub const fn watts(w: f64) -> Self {
860 Self(w)
861 }
862
863 pub const fn kilowatts(kw: f64) -> Self {
865 Self(kw * 1e3)
866 }
867
868 pub const fn as_f64(&self) -> f64 {
870 self.0
871 }
872
873 pub const fn to_watts(&self) -> f64 {
875 self.0
876 }
877
878 pub const fn to_kilowatts(&self) -> f64 {
880 self.0 * 1e-3
881 }
882
883 pub fn to_dbw(&self) -> f64 {
885 10.0 * self.0.log10()
886 }
887}
888
889impl Display for Power {
890 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
891 self.0.fmt(f)?;
892 write!(f, " W")
893 }
894}
895
896type RadiansPerSecond = f64;
897
898#[derive(Copy, Clone, Debug, Default, PartialEq, PartialOrd, ApproxEq)]
900#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
901#[repr(transparent)]
902pub struct AngularRate(RadiansPerSecond);
903
904impl AngularRate {
905 pub const fn new(rps: f64) -> Self {
907 Self(rps)
908 }
909
910 pub const fn radians_per_second(rps: f64) -> Self {
912 Self(rps)
913 }
914
915 pub const fn degrees_per_second(dps: f64) -> Self {
917 Self(dps.to_radians())
918 }
919
920 pub const fn as_f64(&self) -> f64 {
922 self.0
923 }
924
925 pub const fn to_radians_per_second(&self) -> f64 {
927 self.0
928 }
929
930 pub const fn to_degrees_per_second(&self) -> f64 {
932 self.0.to_degrees()
933 }
934}
935
936impl Display for AngularRate {
937 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
938 self.0.to_degrees().fmt(f)?;
939 write!(f, " deg/s")
940 }
941}
942
943type DecibelValue = f64;
944
945#[derive(Copy, Clone, Debug, Default, PartialEq, PartialOrd, ApproxEq)]
947#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
948#[repr(transparent)]
949pub struct Decibel(DecibelValue);
950
951impl Decibel {
952 pub const fn new(db: f64) -> Self {
954 Self(db)
955 }
956
957 pub fn from_linear(val: f64) -> Self {
959 Self(10.0 * val.log10())
960 }
961
962 pub fn to_linear(self) -> f64 {
964 10.0_f64.powf(self.0 / 10.0)
965 }
966
967 pub const fn as_f64(self) -> f64 {
969 self.0
970 }
971}
972
973impl Display for Decibel {
974 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
975 self.0.fmt(f)?;
976 write!(f, " dB")
977 }
978}
979
980pub trait DecibelUnits {
993 fn db(&self) -> Decibel;
995}
996
997impl DecibelUnits for f64 {
998 fn db(&self) -> Decibel {
999 Decibel::new(*self)
1000 }
1001}
1002
1003impl DecibelUnits for i64 {
1004 fn db(&self) -> Decibel {
1005 Decibel::new(*self as f64)
1006 }
1007}
1008
1009macro_rules! trait_impls {
1010 ($($unit:ident),*) => {
1011 $(
1012 impl Neg for $unit {
1013 type Output = Self;
1014
1015 fn neg(self) -> Self::Output {
1016 Self(-self.0)
1017 }
1018 }
1019
1020 impl Add for $unit {
1021 type Output = Self;
1022
1023 fn add(self, rhs: Self) -> Self::Output {
1024 Self(self.0 + rhs.0)
1025 }
1026 }
1027
1028 impl AddAssign for $unit {
1029 fn add_assign(&mut self, rhs: Self) {
1030 self.0 = self.0 + rhs.0;
1031 }
1032 }
1033
1034 impl Sub for $unit {
1035 type Output = Self;
1036
1037 fn sub(self, rhs: Self) -> Self::Output {
1038 Self(self.0 - rhs.0)
1039 }
1040 }
1041
1042 impl SubAssign for $unit {
1043 fn sub_assign(&mut self, rhs: Self) {
1044 self.0 = self.0 - rhs.0
1045 }
1046 }
1047
1048 impl Mul<$unit> for f64 {
1049 type Output = $unit;
1050
1051 fn mul(self, rhs: $unit) -> Self::Output {
1052 $unit(self * rhs.0)
1053 }
1054 }
1055
1056 impl From<$unit> for f64 {
1057 fn from(val: $unit) -> Self {
1058 val.0
1059 }
1060 }
1061 )*
1062 };
1063}
1064
1065trait_impls!(
1066 Angle,
1067 AngularRate,
1068 Decibel,
1069 Distance,
1070 Frequency,
1071 Power,
1072 Pressure,
1073 Temperature,
1074 Velocity
1075);
1076
1077#[cfg(test)]
1078mod tests {
1079 use alloc::format;
1080 use std::f64::consts::{FRAC_PI_2, PI};
1081
1082 use lox_test_utils::assert_approx_eq;
1083 use rstest::rstest;
1084
1085 extern crate alloc;
1086
1087 use super::*;
1088
1089 #[test]
1090 fn test_angle_deg() {
1091 let angle = 90.0.deg();
1092 assert_approx_eq!(angle.0, FRAC_PI_2, rtol <= 1e-10);
1093 }
1094
1095 #[test]
1096 fn test_angle_rad() {
1097 let angle = PI.rad();
1098 assert_approx_eq!(angle.0, PI, rtol <= 1e-10);
1099 }
1100
1101 #[test]
1102 fn test_angle_conversions() {
1103 let angle_deg = 180.0.deg();
1104 let angle_rad = PI.rad();
1105 assert_approx_eq!(angle_deg.0, angle_rad.0, rtol <= 1e-10);
1106 }
1107
1108 #[test]
1109 fn test_angle_display() {
1110 let angle = 90.123456.deg();
1111 assert_eq!(format!("{:.2}", angle), "90.12 deg")
1112 }
1113
1114 #[test]
1115 fn test_angle_neg() {
1116 assert_eq!(Angle(-1.0), -1.0.rad())
1117 }
1118
1119 const TOLERANCE: f64 = f64::EPSILON;
1120
1121 #[rstest]
1122 #[case(Angle::ZERO, Angle::ZERO, 0.0)]
1124 #[case(Angle::PI, Angle::ZERO, -PI)]
1125 #[case(-Angle::PI, Angle::ZERO, -PI)]
1126 #[case(Angle::TAU, Angle::ZERO, 0.0)]
1127 #[case(Angle::FRAC_PI_2, Angle::ZERO, FRAC_PI_2)]
1128 #[case(-Angle::FRAC_PI_2, Angle::ZERO, -FRAC_PI_2)]
1129 #[case(Angle::ZERO, Angle::PI, 0.0)]
1131 #[case(Angle::PI, Angle::PI, PI)]
1132 #[case(-Angle::PI, Angle::PI, PI)]
1133 #[case(Angle::TAU, Angle::PI, 0.0)]
1134 #[case(Angle::FRAC_PI_2, Angle::PI, FRAC_PI_2)]
1135 #[case(-Angle::FRAC_PI_2, Angle::PI, 3.0 * PI / 2.0)]
1136 #[case(Angle::ZERO, -Angle::PI, -TAU)]
1138 #[case(Angle::PI, -Angle::PI, -PI)]
1139 #[case(-Angle::PI, -Angle::PI, -PI)]
1140 #[case(Angle::TAU, -Angle::PI, -TAU)]
1141 #[case(Angle::FRAC_PI_2, -Angle::PI, -3.0 * PI / 2.0)]
1142 #[case(-Angle::FRAC_PI_2, -Angle::PI, -FRAC_PI_2)]
1143 fn test_angle_normalize_two_pi(#[case] angle: Angle, #[case] center: Angle, #[case] exp: f64) {
1144 if exp == 0.0 {
1147 assert_approx_eq!(angle.normalize_two_pi(center).0, exp, atol <= TOLERANCE);
1148 } else {
1149 assert_approx_eq!(angle.normalize_two_pi(center).0, exp, rtol <= TOLERANCE);
1150 }
1151 }
1152
1153 #[test]
1154 fn test_distance_m() {
1155 let distance = 1000.0.m();
1156 assert_eq!(distance.0, 1000.0);
1157 }
1158
1159 #[test]
1160 fn test_distance_km() {
1161 let distance = 1.0.km();
1162 assert_eq!(distance.0, 1000.0);
1163 }
1164
1165 #[test]
1166 fn test_distance_au() {
1167 let distance = 1.0.au();
1168 assert_eq!(distance.0, ASTRONOMICAL_UNIT);
1169 }
1170
1171 #[test]
1172 fn test_distance_conversions() {
1173 let d1 = 1.5e11.m();
1174 let d2 = (1.5e11 / ASTRONOMICAL_UNIT).au();
1175 assert_approx_eq!(d1.0, d2.0, rtol <= 1e-9);
1176 }
1177
1178 #[test]
1179 fn test_distance_display() {
1180 let distance = 9.123456.km();
1181 assert_eq!(format!("{:.2}", distance), "9.12 km")
1182 }
1183
1184 #[test]
1185 fn test_distance_neg() {
1186 assert_eq!(Distance(-1.0), -1.0.m())
1187 }
1188
1189 #[test]
1190 fn test_velocity_mps() {
1191 let velocity = 1000.0.mps();
1192 assert_eq!(velocity.0, 1000.0);
1193 }
1194
1195 #[test]
1196 fn test_velocity_kps() {
1197 let velocity = 1.0.kps();
1198 assert_eq!(velocity.0, 1000.0);
1199 }
1200
1201 #[test]
1202 fn test_velocity_conversions() {
1203 let v1 = 7500.0.mps();
1204 let v2 = 7.5.kps();
1205 assert_eq!(v1.0, v2.0);
1206 }
1207
1208 #[test]
1209 fn test_velocity_display() {
1210 let velocity = 9.123456.kps();
1211 assert_eq!(format!("{:.2}", velocity), "9.12 km/s")
1212 }
1213
1214 #[test]
1215 fn test_velocity_neg() {
1216 assert_eq!(Velocity(-1.0), -1.0.mps())
1217 }
1218
1219 #[test]
1220 fn test_frequency_hz() {
1221 let frequency = 1000.0.hz();
1222 assert_eq!(frequency.0, 1000.0);
1223 }
1224
1225 #[test]
1226 fn test_frequency_khz() {
1227 let frequency = 1.0.khz();
1228 assert_eq!(frequency.0, 1000.0);
1229 }
1230
1231 #[test]
1232 fn test_frequency_mhz() {
1233 let frequency = 1.0.mhz();
1234 assert_eq!(frequency.0, 1_000_000.0);
1235 }
1236
1237 #[test]
1238 fn test_frequency_ghz() {
1239 let frequency = 1.0.ghz();
1240 assert_eq!(frequency.0, 1_000_000_000.0);
1241 }
1242
1243 #[test]
1244 fn test_frequency_thz() {
1245 let frequency = 1.0.thz();
1246 assert_eq!(frequency.0, 1_000_000_000_000.0);
1247 }
1248
1249 #[test]
1250 fn test_frequency_conversions() {
1251 let f1 = 2.4.ghz();
1252 let f2 = 2400.0.mhz();
1253 assert_eq!(f1.0, f2.0);
1254 }
1255
1256 #[test]
1257 fn test_frequency_wavelength() {
1258 let f = 1.0.ghz();
1259 let wavelength = f.wavelength();
1260 assert_approx_eq!(wavelength.0, 0.299792458, rtol <= 1e-9);
1261 }
1262
1263 #[test]
1264 fn test_frequency_wavelength_speed_of_light() {
1265 let f = 299792458.0.hz(); let wavelength = f.wavelength();
1267 assert_approx_eq!(wavelength.0, 1.0, rtol <= 1e-10);
1268 }
1269
1270 #[test]
1271 fn test_frequency_display() {
1272 let frequency = 2.4123456.ghz();
1273 assert_eq!(format!("{:.2}", frequency), "2.41 GHz");
1274 }
1275
1276 #[rstest]
1277 #[case(0.0.hz(), None)]
1278 #[case(3.0.mhz(), Some(FrequencyBand::HF))]
1279 #[case(30.0.mhz(), Some(FrequencyBand::VHF))]
1280 #[case(300.0.mhz(), Some(FrequencyBand::UHF))]
1281 #[case(1.0.ghz(), Some(FrequencyBand::L))]
1282 #[case(2.0.ghz(), Some(FrequencyBand::S))]
1283 #[case(4.0.ghz(), Some(FrequencyBand::C))]
1284 #[case(8.0.ghz(), Some(FrequencyBand::X))]
1285 #[case(12.0.ghz(), Some(FrequencyBand::Ku))]
1286 #[case(18.0.ghz(), Some(FrequencyBand::K))]
1287 #[case(27.0.ghz(), Some(FrequencyBand::Ka))]
1288 #[case(40.0.ghz(), Some(FrequencyBand::V))]
1289 #[case(75.0.ghz(), Some(FrequencyBand::W))]
1290 #[case(110.0.ghz(), Some(FrequencyBand::G))]
1291 #[case(1.0.thz(), None)]
1292 fn test_frequency_band(#[case] f: Frequency, #[case] exp: Option<FrequencyBand>) {
1293 assert_eq!(f.band(), exp)
1294 }
1295
1296 #[test]
1297 fn test_decibel_db() {
1298 let d = 3.0.db();
1299 assert_eq!(d.as_f64(), 3.0);
1300 }
1301
1302 #[test]
1303 fn test_decibel_from_linear() {
1304 let d = Decibel::from_linear(100.0);
1305 assert_approx_eq!(d.0, 20.0, rtol <= 1e-10);
1306 }
1307
1308 #[test]
1309 fn test_decibel_to_linear() {
1310 let d = Decibel::new(20.0);
1311 assert_approx_eq!(d.to_linear(), 100.0, rtol <= 1e-10);
1312 }
1313
1314 #[test]
1315 fn test_decibel_roundtrip() {
1316 let val = 42.5;
1317 let d = Decibel::new(val);
1318 let roundtripped = Decibel::from_linear(d.to_linear());
1319 assert_approx_eq!(roundtripped.0, val, rtol <= 1e-10);
1320 }
1321
1322 #[test]
1323 fn test_decibel_add() {
1324 let sum = 3.0.db() + 3.0.db();
1325 assert_approx_eq!(sum.0, 6.0, rtol <= 1e-10);
1326 }
1327
1328 #[test]
1329 fn test_decibel_sub() {
1330 let diff = 6.0.db() - 3.0.db();
1331 assert_approx_eq!(diff.0, 3.0, rtol <= 1e-10);
1332 }
1333
1334 #[test]
1335 fn test_decibel_neg() {
1336 assert_eq!(-3.0.db(), Decibel::new(-3.0));
1337 }
1338
1339 #[test]
1340 fn test_decibel_display() {
1341 let d = 3.0.db();
1342 assert_eq!(format!("{:.1}", d), "3.0 dB");
1343 }
1344
1345 #[test]
1348 fn test_temperature_new() {
1349 let t = Temperature::new(290.0);
1350 assert_eq!(t.as_f64(), 290.0);
1351 }
1352
1353 #[test]
1354 fn test_temperature_kelvin() {
1355 let t = Temperature::kelvin(300.0);
1356 assert_eq!(t.to_kelvin(), 300.0);
1357 }
1358
1359 #[test]
1360 fn test_temperature_display() {
1361 let t = Temperature::new(290.0);
1362 assert_eq!(format!("{}", t), "290 K");
1363 }
1364
1365 #[test]
1366 fn test_temperature_arithmetic() {
1367 let a = Temperature::new(100.0);
1368 let b = Temperature::new(200.0);
1369 assert_eq!((a + b).as_f64(), 300.0);
1370 assert_eq!((b - a).as_f64(), 100.0);
1371 assert_eq!((-a).as_f64(), -100.0);
1372 assert_eq!((2.0 * a).as_f64(), 200.0);
1373 }
1374
1375 #[test]
1378 fn test_power_watts() {
1379 let p = Power::watts(100.0);
1380 assert_eq!(p.to_watts(), 100.0);
1381 }
1382
1383 #[test]
1384 fn test_power_kilowatts() {
1385 let p = Power::kilowatts(1.0);
1386 assert_eq!(p.to_watts(), 1000.0);
1387 assert_eq!(p.to_kilowatts(), 1.0);
1388 }
1389
1390 #[test]
1391 fn test_power_dbw() {
1392 let p = Power::watts(100.0);
1393 assert_approx_eq!(p.to_dbw(), 20.0, rtol <= 1e-10);
1394 }
1395
1396 #[test]
1397 fn test_power_display() {
1398 let p = Power::watts(100.0);
1399 assert_eq!(format!("{}", p), "100 W");
1400 }
1401
1402 #[test]
1403 fn test_power_arithmetic() {
1404 let a = Power::watts(50.0);
1405 let b = Power::watts(150.0);
1406 assert_eq!((a + b).as_f64(), 200.0);
1407 assert_eq!((b - a).as_f64(), 100.0);
1408 assert_eq!((-a).as_f64(), -50.0);
1409 }
1410
1411 #[test]
1414 fn test_angular_rate_rps() {
1415 let ar = AngularRate::radians_per_second(1.0);
1416 assert_eq!(ar.to_radians_per_second(), 1.0);
1417 assert_approx_eq!(ar.to_degrees_per_second(), 57.29577951308232, rtol <= 1e-10);
1418 }
1419
1420 #[test]
1421 fn test_angular_rate_dps() {
1422 let ar = AngularRate::degrees_per_second(180.0);
1423 assert_approx_eq!(
1424 ar.to_radians_per_second(),
1425 core::f64::consts::PI,
1426 rtol <= 1e-10
1427 );
1428 }
1429
1430 #[test]
1431 fn test_angular_rate_display() {
1432 let ar = AngularRate::radians_per_second(1.0);
1433 let s = format!("{}", ar);
1434 assert!(s.contains("deg/s"));
1435 }
1436
1437 #[test]
1438 fn test_angular_rate_arithmetic() {
1439 let a = AngularRate::new(1.0);
1440 let b = AngularRate::new(2.0);
1441 assert_eq!((a + b).as_f64(), 3.0);
1442 assert_eq!((b - a).as_f64(), 1.0);
1443 assert_eq!((-a).as_f64(), -1.0);
1444 assert_eq!((3.0 * a).as_f64(), 3.0);
1445 }
1446
1447 #[test]
1450 fn test_pressure_hpa() {
1451 let p = Pressure::hpa(1013.25);
1452 assert_eq!(p.to_hpa(), 1013.25);
1453 assert_approx_eq!(p.to_pa(), 101325.0, rtol <= 1e-10);
1454 }
1455
1456 #[test]
1457 fn test_pressure_pa() {
1458 let p = Pressure::pa(101325.0);
1459 assert_approx_eq!(p.to_hpa(), 1013.25, rtol <= 1e-10);
1460 }
1461
1462 #[test]
1463 fn test_pressure_display() {
1464 let p = Pressure::pa(101325.0);
1465 let s = format!("{}", p);
1466 assert!(s.contains("Pa"));
1467 }
1468}