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 Watts = 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 Power(Watts);
804
805impl Power {
806 pub const fn new(w: f64) -> Self {
808 Self(w)
809 }
810
811 pub const fn watts(w: f64) -> Self {
813 Self(w)
814 }
815
816 pub const fn kilowatts(kw: f64) -> Self {
818 Self(kw * 1e3)
819 }
820
821 pub const fn as_f64(&self) -> f64 {
823 self.0
824 }
825
826 pub const fn to_watts(&self) -> f64 {
828 self.0
829 }
830
831 pub const fn to_kilowatts(&self) -> f64 {
833 self.0 * 1e-3
834 }
835
836 pub fn to_dbw(&self) -> f64 {
838 10.0 * self.0.log10()
839 }
840}
841
842impl Display for Power {
843 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
844 self.0.fmt(f)?;
845 write!(f, " W")
846 }
847}
848
849type BitsPerSecond = f64;
850
851#[derive(Copy, Clone, Debug, Default, PartialEq, PartialOrd, ApproxEq)]
853#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
854#[repr(transparent)]
855pub struct DataRate(BitsPerSecond);
856
857impl DataRate {
858 pub const fn new(bps: f64) -> Self {
860 Self(bps)
861 }
862
863 pub const fn bits_per_second(bps: f64) -> Self {
865 Self(bps)
866 }
867
868 pub const fn kilobits_per_second(kbps: f64) -> Self {
870 Self(kbps * 1e3)
871 }
872
873 pub const fn megabits_per_second(mbps: f64) -> Self {
875 Self(mbps * 1e6)
876 }
877
878 pub const fn as_f64(&self) -> f64 {
880 self.0
881 }
882
883 pub const fn to_bits_per_second(&self) -> f64 {
885 self.0
886 }
887
888 pub const fn to_kilobits_per_second(&self) -> f64 {
890 self.0 * 1e-3
891 }
892
893 pub const fn to_megabits_per_second(&self) -> f64 {
895 self.0 * 1e-6
896 }
897}
898
899impl Display for DataRate {
900 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
901 (1e-3 * self.0).fmt(f)?;
902 write!(f, " kbps")
903 }
904}
905
906type RadiansPerSecond = f64;
907
908#[derive(Copy, Clone, Debug, Default, PartialEq, PartialOrd, ApproxEq)]
910#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
911#[repr(transparent)]
912pub struct AngularRate(RadiansPerSecond);
913
914impl AngularRate {
915 pub const fn new(rps: f64) -> Self {
917 Self(rps)
918 }
919
920 pub const fn radians_per_second(rps: f64) -> Self {
922 Self(rps)
923 }
924
925 pub const fn degrees_per_second(dps: f64) -> Self {
927 Self(dps.to_radians())
928 }
929
930 pub const fn as_f64(&self) -> f64 {
932 self.0
933 }
934
935 pub const fn to_radians_per_second(&self) -> f64 {
937 self.0
938 }
939
940 pub const fn to_degrees_per_second(&self) -> f64 {
942 self.0.to_degrees()
943 }
944}
945
946impl Display for AngularRate {
947 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
948 self.0.to_degrees().fmt(f)?;
949 write!(f, " deg/s")
950 }
951}
952
953type DecibelValue = f64;
954
955#[derive(Copy, Clone, Debug, Default, PartialEq, PartialOrd, ApproxEq)]
957#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
958#[repr(transparent)]
959pub struct Decibel(DecibelValue);
960
961impl Decibel {
962 pub const fn new(db: f64) -> Self {
964 Self(db)
965 }
966
967 pub fn from_linear(val: f64) -> Self {
969 Self(10.0 * val.log10())
970 }
971
972 pub fn to_linear(self) -> f64 {
974 10.0_f64.powf(self.0 / 10.0)
975 }
976
977 pub const fn as_f64(self) -> f64 {
979 self.0
980 }
981}
982
983impl Display for Decibel {
984 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
985 self.0.fmt(f)?;
986 write!(f, " dB")
987 }
988}
989
990pub trait DecibelUnits {
1003 fn db(&self) -> Decibel;
1005}
1006
1007impl DecibelUnits for f64 {
1008 fn db(&self) -> Decibel {
1009 Decibel::new(*self)
1010 }
1011}
1012
1013impl DecibelUnits for i64 {
1014 fn db(&self) -> Decibel {
1015 Decibel::new(*self as f64)
1016 }
1017}
1018
1019macro_rules! trait_impls {
1020 ($($unit:ident),*) => {
1021 $(
1022 impl Neg for $unit {
1023 type Output = Self;
1024
1025 fn neg(self) -> Self::Output {
1026 Self(-self.0)
1027 }
1028 }
1029
1030 impl Add for $unit {
1031 type Output = Self;
1032
1033 fn add(self, rhs: Self) -> Self::Output {
1034 Self(self.0 + rhs.0)
1035 }
1036 }
1037
1038 impl AddAssign for $unit {
1039 fn add_assign(&mut self, rhs: Self) {
1040 self.0 = self.0 + rhs.0;
1041 }
1042 }
1043
1044 impl Sub for $unit {
1045 type Output = Self;
1046
1047 fn sub(self, rhs: Self) -> Self::Output {
1048 Self(self.0 - rhs.0)
1049 }
1050 }
1051
1052 impl SubAssign for $unit {
1053 fn sub_assign(&mut self, rhs: Self) {
1054 self.0 = self.0 - rhs.0
1055 }
1056 }
1057
1058 impl Mul<$unit> for f64 {
1059 type Output = $unit;
1060
1061 fn mul(self, rhs: $unit) -> Self::Output {
1062 $unit(self * rhs.0)
1063 }
1064 }
1065
1066 impl From<$unit> for f64 {
1067 fn from(val: $unit) -> Self {
1068 val.0
1069 }
1070 }
1071 )*
1072 };
1073}
1074
1075trait_impls!(
1076 Angle,
1077 AngularRate,
1078 DataRate,
1079 Decibel,
1080 Distance,
1081 Frequency,
1082 Power,
1083 Temperature,
1084 Velocity
1085);
1086
1087#[cfg(test)]
1088mod tests {
1089 use alloc::format;
1090 use std::f64::consts::{FRAC_PI_2, PI};
1091
1092 use lox_test_utils::assert_approx_eq;
1093 use rstest::rstest;
1094
1095 extern crate alloc;
1096
1097 use super::*;
1098
1099 #[test]
1100 fn test_angle_deg() {
1101 let angle = 90.0.deg();
1102 assert_approx_eq!(angle.0, FRAC_PI_2, rtol <= 1e-10);
1103 }
1104
1105 #[test]
1106 fn test_angle_rad() {
1107 let angle = PI.rad();
1108 assert_approx_eq!(angle.0, PI, rtol <= 1e-10);
1109 }
1110
1111 #[test]
1112 fn test_angle_conversions() {
1113 let angle_deg = 180.0.deg();
1114 let angle_rad = PI.rad();
1115 assert_approx_eq!(angle_deg.0, angle_rad.0, rtol <= 1e-10);
1116 }
1117
1118 #[test]
1119 fn test_angle_display() {
1120 let angle = 90.123456.deg();
1121 assert_eq!(format!("{:.2}", angle), "90.12 deg")
1122 }
1123
1124 #[test]
1125 fn test_angle_neg() {
1126 assert_eq!(Angle(-1.0), -1.0.rad())
1127 }
1128
1129 const TOLERANCE: f64 = f64::EPSILON;
1130
1131 #[rstest]
1132 #[case(Angle::ZERO, Angle::ZERO, 0.0)]
1134 #[case(Angle::PI, Angle::ZERO, -PI)]
1135 #[case(-Angle::PI, Angle::ZERO, -PI)]
1136 #[case(Angle::TAU, Angle::ZERO, 0.0)]
1137 #[case(Angle::FRAC_PI_2, Angle::ZERO, FRAC_PI_2)]
1138 #[case(-Angle::FRAC_PI_2, Angle::ZERO, -FRAC_PI_2)]
1139 #[case(Angle::ZERO, Angle::PI, 0.0)]
1141 #[case(Angle::PI, Angle::PI, PI)]
1142 #[case(-Angle::PI, Angle::PI, PI)]
1143 #[case(Angle::TAU, Angle::PI, 0.0)]
1144 #[case(Angle::FRAC_PI_2, Angle::PI, FRAC_PI_2)]
1145 #[case(-Angle::FRAC_PI_2, Angle::PI, 3.0 * PI / 2.0)]
1146 #[case(Angle::ZERO, -Angle::PI, -TAU)]
1148 #[case(Angle::PI, -Angle::PI, -PI)]
1149 #[case(-Angle::PI, -Angle::PI, -PI)]
1150 #[case(Angle::TAU, -Angle::PI, -TAU)]
1151 #[case(Angle::FRAC_PI_2, -Angle::PI, -3.0 * PI / 2.0)]
1152 #[case(-Angle::FRAC_PI_2, -Angle::PI, -FRAC_PI_2)]
1153 fn test_angle_normalize_two_pi(#[case] angle: Angle, #[case] center: Angle, #[case] exp: f64) {
1154 if exp == 0.0 {
1157 assert_approx_eq!(angle.normalize_two_pi(center).0, exp, atol <= TOLERANCE);
1158 } else {
1159 assert_approx_eq!(angle.normalize_two_pi(center).0, exp, rtol <= TOLERANCE);
1160 }
1161 }
1162
1163 #[test]
1164 fn test_distance_m() {
1165 let distance = 1000.0.m();
1166 assert_eq!(distance.0, 1000.0);
1167 }
1168
1169 #[test]
1170 fn test_distance_km() {
1171 let distance = 1.0.km();
1172 assert_eq!(distance.0, 1000.0);
1173 }
1174
1175 #[test]
1176 fn test_distance_au() {
1177 let distance = 1.0.au();
1178 assert_eq!(distance.0, ASTRONOMICAL_UNIT);
1179 }
1180
1181 #[test]
1182 fn test_distance_conversions() {
1183 let d1 = 1.5e11.m();
1184 let d2 = (1.5e11 / ASTRONOMICAL_UNIT).au();
1185 assert_approx_eq!(d1.0, d2.0, rtol <= 1e-9);
1186 }
1187
1188 #[test]
1189 fn test_distance_display() {
1190 let distance = 9.123456.km();
1191 assert_eq!(format!("{:.2}", distance), "9.12 km")
1192 }
1193
1194 #[test]
1195 fn test_distance_neg() {
1196 assert_eq!(Distance(-1.0), -1.0.m())
1197 }
1198
1199 #[test]
1200 fn test_velocity_mps() {
1201 let velocity = 1000.0.mps();
1202 assert_eq!(velocity.0, 1000.0);
1203 }
1204
1205 #[test]
1206 fn test_velocity_kps() {
1207 let velocity = 1.0.kps();
1208 assert_eq!(velocity.0, 1000.0);
1209 }
1210
1211 #[test]
1212 fn test_velocity_conversions() {
1213 let v1 = 7500.0.mps();
1214 let v2 = 7.5.kps();
1215 assert_eq!(v1.0, v2.0);
1216 }
1217
1218 #[test]
1219 fn test_velocity_display() {
1220 let velocity = 9.123456.kps();
1221 assert_eq!(format!("{:.2}", velocity), "9.12 km/s")
1222 }
1223
1224 #[test]
1225 fn test_velocity_neg() {
1226 assert_eq!(Velocity(-1.0), -1.0.mps())
1227 }
1228
1229 #[test]
1230 fn test_frequency_hz() {
1231 let frequency = 1000.0.hz();
1232 assert_eq!(frequency.0, 1000.0);
1233 }
1234
1235 #[test]
1236 fn test_frequency_khz() {
1237 let frequency = 1.0.khz();
1238 assert_eq!(frequency.0, 1000.0);
1239 }
1240
1241 #[test]
1242 fn test_frequency_mhz() {
1243 let frequency = 1.0.mhz();
1244 assert_eq!(frequency.0, 1_000_000.0);
1245 }
1246
1247 #[test]
1248 fn test_frequency_ghz() {
1249 let frequency = 1.0.ghz();
1250 assert_eq!(frequency.0, 1_000_000_000.0);
1251 }
1252
1253 #[test]
1254 fn test_frequency_thz() {
1255 let frequency = 1.0.thz();
1256 assert_eq!(frequency.0, 1_000_000_000_000.0);
1257 }
1258
1259 #[test]
1260 fn test_frequency_conversions() {
1261 let f1 = 2.4.ghz();
1262 let f2 = 2400.0.mhz();
1263 assert_eq!(f1.0, f2.0);
1264 }
1265
1266 #[test]
1267 fn test_frequency_wavelength() {
1268 let f = 1.0.ghz();
1269 let wavelength = f.wavelength();
1270 assert_approx_eq!(wavelength.0, 0.299792458, rtol <= 1e-9);
1271 }
1272
1273 #[test]
1274 fn test_frequency_wavelength_speed_of_light() {
1275 let f = 299792458.0.hz(); let wavelength = f.wavelength();
1277 assert_approx_eq!(wavelength.0, 1.0, rtol <= 1e-10);
1278 }
1279
1280 #[test]
1281 fn test_frequency_display() {
1282 let frequency = 2.4123456.ghz();
1283 assert_eq!(format!("{:.2}", frequency), "2.41 GHz");
1284 }
1285
1286 #[rstest]
1287 #[case(0.0.hz(), None)]
1288 #[case(3.0.mhz(), Some(FrequencyBand::HF))]
1289 #[case(30.0.mhz(), Some(FrequencyBand::VHF))]
1290 #[case(300.0.mhz(), Some(FrequencyBand::UHF))]
1291 #[case(1.0.ghz(), Some(FrequencyBand::L))]
1292 #[case(2.0.ghz(), Some(FrequencyBand::S))]
1293 #[case(4.0.ghz(), Some(FrequencyBand::C))]
1294 #[case(8.0.ghz(), Some(FrequencyBand::X))]
1295 #[case(12.0.ghz(), Some(FrequencyBand::Ku))]
1296 #[case(18.0.ghz(), Some(FrequencyBand::K))]
1297 #[case(27.0.ghz(), Some(FrequencyBand::Ka))]
1298 #[case(40.0.ghz(), Some(FrequencyBand::V))]
1299 #[case(75.0.ghz(), Some(FrequencyBand::W))]
1300 #[case(110.0.ghz(), Some(FrequencyBand::G))]
1301 #[case(1.0.thz(), None)]
1302 fn test_frequency_band(#[case] f: Frequency, #[case] exp: Option<FrequencyBand>) {
1303 assert_eq!(f.band(), exp)
1304 }
1305
1306 #[test]
1307 fn test_decibel_db() {
1308 let d = 3.0.db();
1309 assert_eq!(d.as_f64(), 3.0);
1310 }
1311
1312 #[test]
1313 fn test_decibel_from_linear() {
1314 let d = Decibel::from_linear(100.0);
1315 assert_approx_eq!(d.0, 20.0, rtol <= 1e-10);
1316 }
1317
1318 #[test]
1319 fn test_decibel_to_linear() {
1320 let d = Decibel::new(20.0);
1321 assert_approx_eq!(d.to_linear(), 100.0, rtol <= 1e-10);
1322 }
1323
1324 #[test]
1325 fn test_decibel_roundtrip() {
1326 let val = 42.5;
1327 let d = Decibel::new(val);
1328 let roundtripped = Decibel::from_linear(d.to_linear());
1329 assert_approx_eq!(roundtripped.0, val, rtol <= 1e-10);
1330 }
1331
1332 #[test]
1333 fn test_decibel_add() {
1334 let sum = 3.0.db() + 3.0.db();
1335 assert_approx_eq!(sum.0, 6.0, rtol <= 1e-10);
1336 }
1337
1338 #[test]
1339 fn test_decibel_sub() {
1340 let diff = 6.0.db() - 3.0.db();
1341 assert_approx_eq!(diff.0, 3.0, rtol <= 1e-10);
1342 }
1343
1344 #[test]
1345 fn test_decibel_neg() {
1346 assert_eq!(-3.0.db(), Decibel::new(-3.0));
1347 }
1348
1349 #[test]
1350 fn test_decibel_display() {
1351 let d = 3.0.db();
1352 assert_eq!(format!("{:.1}", d), "3.0 dB");
1353 }
1354
1355 #[test]
1358 fn test_temperature_new() {
1359 let t = Temperature::new(290.0);
1360 assert_eq!(t.as_f64(), 290.0);
1361 }
1362
1363 #[test]
1364 fn test_temperature_kelvin() {
1365 let t = Temperature::kelvin(300.0);
1366 assert_eq!(t.to_kelvin(), 300.0);
1367 }
1368
1369 #[test]
1370 fn test_temperature_display() {
1371 let t = Temperature::new(290.0);
1372 assert_eq!(format!("{}", t), "290 K");
1373 }
1374
1375 #[test]
1376 fn test_temperature_arithmetic() {
1377 let a = Temperature::new(100.0);
1378 let b = Temperature::new(200.0);
1379 assert_eq!((a + b).as_f64(), 300.0);
1380 assert_eq!((b - a).as_f64(), 100.0);
1381 assert_eq!((-a).as_f64(), -100.0);
1382 assert_eq!((2.0 * a).as_f64(), 200.0);
1383 }
1384
1385 #[test]
1388 fn test_power_watts() {
1389 let p = Power::watts(100.0);
1390 assert_eq!(p.to_watts(), 100.0);
1391 }
1392
1393 #[test]
1394 fn test_power_kilowatts() {
1395 let p = Power::kilowatts(1.0);
1396 assert_eq!(p.to_watts(), 1000.0);
1397 assert_eq!(p.to_kilowatts(), 1.0);
1398 }
1399
1400 #[test]
1401 fn test_power_dbw() {
1402 let p = Power::watts(100.0);
1403 assert_approx_eq!(p.to_dbw(), 20.0, rtol <= 1e-10);
1404 }
1405
1406 #[test]
1407 fn test_power_display() {
1408 let p = Power::watts(100.0);
1409 assert_eq!(format!("{}", p), "100 W");
1410 }
1411
1412 #[test]
1413 fn test_power_arithmetic() {
1414 let a = Power::watts(50.0);
1415 let b = Power::watts(150.0);
1416 assert_eq!((a + b).as_f64(), 200.0);
1417 assert_eq!((b - a).as_f64(), 100.0);
1418 assert_eq!((-a).as_f64(), -50.0);
1419 }
1420
1421 #[test]
1424 fn test_data_rate_bps() {
1425 let dr = DataRate::bits_per_second(1000.0);
1426 assert_eq!(dr.to_bits_per_second(), 1000.0);
1427 assert_eq!(dr.to_kilobits_per_second(), 1.0);
1428 assert_eq!(dr.to_megabits_per_second(), 0.001);
1429 }
1430
1431 #[test]
1432 fn test_data_rate_kbps() {
1433 let dr = DataRate::kilobits_per_second(1.0);
1434 assert_eq!(dr.to_bits_per_second(), 1000.0);
1435 }
1436
1437 #[test]
1438 fn test_data_rate_mbps() {
1439 let dr = DataRate::megabits_per_second(1.0);
1440 assert_eq!(dr.to_bits_per_second(), 1_000_000.0);
1441 }
1442
1443 #[test]
1444 fn test_data_rate_display() {
1445 let dr = DataRate::kilobits_per_second(1.0);
1446 assert_eq!(format!("{}", dr), "1 kbps");
1447 }
1448
1449 #[test]
1450 fn test_data_rate_arithmetic() {
1451 let a = DataRate::new(100.0);
1452 let b = DataRate::new(200.0);
1453 assert_eq!((a + b).as_f64(), 300.0);
1454 assert_eq!((b - a).as_f64(), 100.0);
1455 assert_eq!((-a).as_f64(), -100.0);
1456 }
1457
1458 #[test]
1461 fn test_angular_rate_rps() {
1462 let ar = AngularRate::radians_per_second(1.0);
1463 assert_eq!(ar.to_radians_per_second(), 1.0);
1464 assert_approx_eq!(ar.to_degrees_per_second(), 57.29577951308232, rtol <= 1e-10);
1465 }
1466
1467 #[test]
1468 fn test_angular_rate_dps() {
1469 let ar = AngularRate::degrees_per_second(180.0);
1470 assert_approx_eq!(
1471 ar.to_radians_per_second(),
1472 core::f64::consts::PI,
1473 rtol <= 1e-10
1474 );
1475 }
1476
1477 #[test]
1478 fn test_angular_rate_display() {
1479 let ar = AngularRate::radians_per_second(1.0);
1480 let s = format!("{}", ar);
1481 assert!(s.contains("deg/s"));
1482 }
1483
1484 #[test]
1485 fn test_angular_rate_arithmetic() {
1486 let a = AngularRate::new(1.0);
1487 let b = AngularRate::new(2.0);
1488 assert_eq!((a + b).as_f64(), 3.0);
1489 assert_eq!((b - a).as_f64(), 1.0);
1490 assert_eq!((-a).as_f64(), -1.0);
1491 assert_eq!((3.0 * a).as_f64(), 3.0);
1492 }
1493}