use std::{
f64::consts::{FRAC_PI_2, FRAC_PI_4, PI, TAU},
fmt::{Display, Formatter, Result},
ops::{Add, AddAssign, Mul, Neg, Sub, SubAssign},
};
use glam::DMat3;
use lox_test_utils::ApproxEq;
use crate::f64::consts::SECONDS_PER_DAY;
pub const DEGREES_IN_CIRCLE: f64 = 360.0;
pub const ARCSECONDS_IN_CIRCLE: f64 = DEGREES_IN_CIRCLE * 60.0 * 60.0;
pub const RADIANS_IN_ARCSECOND: f64 = TAU / ARCSECONDS_IN_CIRCLE;
type Radians = f64;
#[derive(Copy, Clone, Debug, Default, PartialEq, PartialOrd, ApproxEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(transparent)]
pub struct Angle(Radians);
impl Angle {
pub const ZERO: Self = Self(0.0);
pub const PI: Self = Self(PI);
pub const TAU: Self = Self(TAU);
pub const FRAC_PI_2: Self = Self(FRAC_PI_2);
pub const FRAC_PI_4: Self = Self(FRAC_PI_4);
pub const fn new(rad: f64) -> Self {
Self(rad)
}
pub const fn radians(rad: f64) -> Self {
Self(rad)
}
pub const fn radians_normalized(rad: f64) -> Self {
Self(rad).mod_two_pi()
}
pub const fn radians_normalized_signed(rad: f64) -> Self {
Self(rad).mod_two_pi_signed()
}
pub const fn degrees(deg: f64) -> Self {
Self(deg.to_radians())
}
pub const fn from_hms(hours: i64, minutes: u8, seconds: f64) -> Self {
Self::degrees(15.0 * (hours as f64 + minutes as f64 / 60.0 + seconds / 3600.0))
}
pub const fn degrees_normalized(deg: f64) -> Self {
Self((deg % DEGREES_IN_CIRCLE).to_radians()).mod_two_pi()
}
pub const fn degrees_normalized_signed(deg: f64) -> Self {
Self((deg % DEGREES_IN_CIRCLE).to_radians())
}
pub const fn arcseconds(asec: f64) -> Self {
Self(asec * RADIANS_IN_ARCSECOND)
}
pub const fn arcseconds_normalized(asec: f64) -> Self {
Self((asec % ARCSECONDS_IN_CIRCLE) * RADIANS_IN_ARCSECOND).mod_two_pi()
}
pub const fn arcseconds_normalized_signed(asec: f64) -> Self {
Self((asec % ARCSECONDS_IN_CIRCLE) * RADIANS_IN_ARCSECOND)
}
pub fn is_zero(&self) -> bool {
self.0 == 0.0
}
pub const fn abs(&self) -> Self {
Self(self.0.abs())
}
pub fn from_asin(value: f64) -> Self {
Self(value.asin())
}
pub fn from_asinh(value: f64) -> Self {
Self(value.asinh())
}
pub fn from_acos(value: f64) -> Self {
Self(value.acos())
}
pub fn from_acosh(value: f64) -> Self {
Self(value.acosh())
}
pub fn from_atan(value: f64) -> Self {
Self(value.atan())
}
pub fn from_atanh(value: f64) -> Self {
Self(value.atanh())
}
pub fn from_atan2(y: f64, x: f64) -> Self {
Self(y.atan2(x))
}
pub fn cos(&self) -> f64 {
self.0.cos()
}
pub fn cosh(&self) -> f64 {
self.0.cosh()
}
pub fn sin(&self) -> f64 {
self.0.sin()
}
pub fn sinh(&self) -> f64 {
self.0.sinh()
}
pub fn sin_cos(&self) -> (f64, f64) {
self.0.sin_cos()
}
pub fn tan(&self) -> f64 {
self.0.tan()
}
pub fn tanh(&self) -> f64 {
self.0.tanh()
}
pub const fn mod_two_pi(&self) -> Self {
let mut a = self.0 % TAU;
if a < 0.0 {
a += TAU
}
Self(a)
}
pub const fn mod_two_pi_signed(&self) -> Self {
Self(self.0 % TAU)
}
pub const fn normalize_two_pi(&self, center: Self) -> Self {
Self(self.0 - TAU * ((self.0 + PI - center.0) / TAU).floor())
}
pub const fn as_f64(&self) -> f64 {
self.0
}
pub const fn to_radians(&self) -> f64 {
self.0
}
pub const fn to_degrees(&self) -> f64 {
self.0.to_degrees()
}
pub const fn to_arcseconds(&self) -> f64 {
self.0 / RADIANS_IN_ARCSECOND
}
pub fn rotation_x(&self) -> DMat3 {
DMat3::from_rotation_x(-self.to_radians())
}
pub fn rotation_y(&self) -> DMat3 {
DMat3::from_rotation_y(-self.to_radians())
}
pub fn rotation_z(&self) -> DMat3 {
DMat3::from_rotation_z(-self.to_radians())
}
}
impl Display for Angle {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
self.0.to_degrees().fmt(f)?;
write!(f, " deg")
}
}
pub trait AngleUnits {
fn rad(&self) -> Angle;
fn deg(&self) -> Angle;
fn arcsec(&self) -> Angle;
fn mas(&self) -> Angle;
fn uas(&self) -> Angle;
}
impl AngleUnits for f64 {
fn rad(&self) -> Angle {
Angle::radians(*self)
}
fn deg(&self) -> Angle {
Angle::degrees(*self)
}
fn arcsec(&self) -> Angle {
Angle::arcseconds(*self)
}
fn mas(&self) -> Angle {
Angle::arcseconds(self * 1e-3)
}
fn uas(&self) -> Angle {
Angle::arcseconds(self * 1e-6)
}
}
impl AngleUnits for i64 {
fn rad(&self) -> Angle {
Angle::radians(*self as f64)
}
fn deg(&self) -> Angle {
Angle::degrees(*self as f64)
}
fn arcsec(&self) -> Angle {
Angle::arcseconds(*self as f64)
}
fn mas(&self) -> Angle {
Angle::arcseconds(*self as f64 * 1e-3)
}
fn uas(&self) -> Angle {
Angle::arcseconds(*self as f64 * 1e-6)
}
}
pub const ASTRONOMICAL_UNIT: f64 = 1.495978707e11;
type Meters = f64;
#[derive(Copy, Clone, Debug, Default, PartialEq, PartialOrd, ApproxEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(transparent)]
pub struct Distance(Meters);
impl Distance {
pub const fn new(m: f64) -> Self {
Self(m)
}
pub const fn meters(m: f64) -> Self {
Self(m)
}
pub const fn kilometers(m: f64) -> Self {
Self(m * 1e3)
}
pub const fn astronomical_units(au: f64) -> Self {
Self(au * ASTRONOMICAL_UNIT)
}
pub const fn as_f64(&self) -> f64 {
self.0
}
pub const fn to_meters(&self) -> f64 {
self.0
}
pub const fn to_kilometers(&self) -> f64 {
self.0 * 1e-3
}
pub const fn to_astronomical_units(&self) -> f64 {
self.0 / ASTRONOMICAL_UNIT
}
}
impl Display for Distance {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
(1e-3 * self.0).fmt(f)?;
write!(f, " km")
}
}
pub trait DistanceUnits {
fn m(&self) -> Distance;
fn km(&self) -> Distance;
fn au(&self) -> Distance;
}
impl DistanceUnits for f64 {
fn m(&self) -> Distance {
Distance::meters(*self)
}
fn km(&self) -> Distance {
Distance::kilometers(*self)
}
fn au(&self) -> Distance {
Distance::astronomical_units(*self)
}
}
impl DistanceUnits for i64 {
fn m(&self) -> Distance {
Distance::meters(*self as f64)
}
fn km(&self) -> Distance {
Distance::kilometers(*self as f64)
}
fn au(&self) -> Distance {
Distance::astronomical_units(*self as f64)
}
}
type MetersPerSecond = f64;
#[derive(Copy, Clone, Debug, Default, PartialEq, PartialOrd, ApproxEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(transparent)]
pub struct Velocity(MetersPerSecond);
impl Velocity {
pub const fn new(mps: f64) -> Self {
Self(mps)
}
pub const fn meters_per_second(mps: f64) -> Self {
Self(mps)
}
pub const fn kilometers_per_second(mps: f64) -> Self {
Self(mps * 1e3)
}
pub const fn astronomical_units_per_day(aud: f64) -> Self {
Self(aud * ASTRONOMICAL_UNIT / SECONDS_PER_DAY)
}
pub const fn fraction_of_speed_of_light(c: f64) -> Self {
Self(c * SPEED_OF_LIGHT)
}
pub const fn as_f64(&self) -> f64 {
self.0
}
pub const fn to_meters_per_second(&self) -> f64 {
self.0
}
pub const fn to_kilometers_per_second(&self) -> f64 {
self.0 * 1e-3
}
pub const fn to_astronomical_units_per_day(&self) -> f64 {
self.0 * SECONDS_PER_DAY / ASTRONOMICAL_UNIT
}
pub const fn to_fraction_of_speed_of_light(&self) -> f64 {
self.0 / SPEED_OF_LIGHT
}
}
impl Display for Velocity {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
(1e-3 * self.0).fmt(f)?;
write!(f, " km/s")
}
}
pub trait VelocityUnits {
fn mps(&self) -> Velocity;
fn kps(&self) -> Velocity;
fn aud(&self) -> Velocity;
fn c(&self) -> Velocity;
}
impl VelocityUnits for f64 {
fn mps(&self) -> Velocity {
Velocity::meters_per_second(*self)
}
fn kps(&self) -> Velocity {
Velocity::kilometers_per_second(*self)
}
fn aud(&self) -> Velocity {
Velocity::astronomical_units_per_day(*self)
}
fn c(&self) -> Velocity {
Velocity::fraction_of_speed_of_light(*self)
}
}
impl VelocityUnits for i64 {
fn mps(&self) -> Velocity {
Velocity::meters_per_second(*self as f64)
}
fn kps(&self) -> Velocity {
Velocity::kilometers_per_second(*self as f64)
}
fn aud(&self) -> Velocity {
Velocity::astronomical_units_per_day(*self as f64)
}
fn c(&self) -> Velocity {
Velocity::fraction_of_speed_of_light(*self as f64)
}
}
pub const SPEED_OF_LIGHT: f64 = 299792458.0;
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum FrequencyBand {
HF,
VHF,
UHF,
L,
S,
C,
X,
Ku,
K,
Ka,
V,
W,
G,
}
type Hertz = f64;
#[derive(Copy, Clone, Debug, Default, PartialEq, PartialOrd, ApproxEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(transparent)]
pub struct Frequency(Hertz);
impl Frequency {
pub const fn new(hz: Hertz) -> Self {
Self(hz)
}
pub const fn hertz(hz: Hertz) -> Self {
Self(hz)
}
pub const fn kilohertz(hz: Hertz) -> Self {
Self(hz * 1e3)
}
pub const fn megahertz(hz: Hertz) -> Self {
Self(hz * 1e6)
}
pub const fn gigahertz(hz: Hertz) -> Self {
Self(hz * 1e9)
}
pub const fn terahertz(hz: Hertz) -> Self {
Self(hz * 1e12)
}
pub const fn to_hertz(&self) -> f64 {
self.0
}
pub const fn to_kilohertz(&self) -> f64 {
self.0 * 1e-3
}
pub const fn to_megahertz(&self) -> f64 {
self.0 * 1e-6
}
pub const fn to_gigahertz(&self) -> f64 {
self.0 * 1e-9
}
pub const fn to_terahertz(&self) -> f64 {
self.0 * 1e-12
}
pub fn wavelength(&self) -> Distance {
Distance(SPEED_OF_LIGHT / self.0)
}
pub fn band(&self) -> Option<FrequencyBand> {
match self.0 {
f if f < 3e6 => None,
f if f < 30e6 => Some(FrequencyBand::HF),
f if f < 300e6 => Some(FrequencyBand::VHF),
f if f < 1e9 => Some(FrequencyBand::UHF),
f if f < 2e9 => Some(FrequencyBand::L),
f if f < 4e9 => Some(FrequencyBand::S),
f if f < 8e9 => Some(FrequencyBand::C),
f if f < 12e9 => Some(FrequencyBand::X),
f if f < 18e9 => Some(FrequencyBand::Ku),
f if f < 27e9 => Some(FrequencyBand::K),
f if f < 40e9 => Some(FrequencyBand::Ka),
f if f < 75e9 => Some(FrequencyBand::V),
f if f < 110e9 => Some(FrequencyBand::W),
f if f < 300e9 => Some(FrequencyBand::G),
_ => None,
}
}
}
impl Display for Frequency {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
(1e-9 * self.0).fmt(f)?;
write!(f, " GHz")
}
}
pub trait FrequencyUnits {
fn hz(&self) -> Frequency;
fn khz(&self) -> Frequency;
fn mhz(&self) -> Frequency;
fn ghz(&self) -> Frequency;
fn thz(&self) -> Frequency;
}
impl FrequencyUnits for f64 {
fn hz(&self) -> Frequency {
Frequency::hertz(*self)
}
fn khz(&self) -> Frequency {
Frequency::kilohertz(*self)
}
fn mhz(&self) -> Frequency {
Frequency::megahertz(*self)
}
fn ghz(&self) -> Frequency {
Frequency::gigahertz(*self)
}
fn thz(&self) -> Frequency {
Frequency::terahertz(*self)
}
}
impl FrequencyUnits for i64 {
fn hz(&self) -> Frequency {
Frequency::hertz(*self as f64)
}
fn khz(&self) -> Frequency {
Frequency::kilohertz(*self as f64)
}
fn mhz(&self) -> Frequency {
Frequency::megahertz(*self as f64)
}
fn ghz(&self) -> Frequency {
Frequency::gigahertz(*self as f64)
}
fn thz(&self) -> Frequency {
Frequency::terahertz(*self as f64)
}
}
pub type Kelvin = f64;
type KelvinValue = f64;
#[derive(Copy, Clone, Debug, Default, PartialEq, PartialOrd, ApproxEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(transparent)]
pub struct Temperature(KelvinValue);
impl Temperature {
pub const fn new(k: f64) -> Self {
Self(k)
}
pub const fn kelvin(k: f64) -> Self {
Self(k)
}
pub const fn as_f64(&self) -> f64 {
self.0
}
pub const fn to_kelvin(&self) -> f64 {
self.0
}
}
impl Display for Temperature {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
self.0.fmt(f)?;
write!(f, " K")
}
}
type Pascals = f64;
#[derive(Copy, Clone, Debug, Default, PartialEq, PartialOrd, ApproxEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(transparent)]
pub struct Pressure(Pascals);
impl Pressure {
pub const fn new(pa: f64) -> Self {
Self(pa)
}
pub const fn pa(pa: f64) -> Self {
Self(pa)
}
pub const fn hpa(hpa: f64) -> Self {
Self(hpa * 100.0)
}
pub const fn as_f64(&self) -> f64 {
self.0
}
pub const fn to_pa(&self) -> f64 {
self.0
}
pub const fn to_hpa(&self) -> f64 {
self.0 * 0.01
}
}
impl Display for Pressure {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
self.0.fmt(f)?;
write!(f, " Pa")
}
}
type Watts = f64;
#[derive(Copy, Clone, Debug, Default, PartialEq, PartialOrd, ApproxEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(transparent)]
pub struct Power(Watts);
impl Power {
pub const fn new(w: f64) -> Self {
Self(w)
}
pub const fn watts(w: f64) -> Self {
Self(w)
}
pub const fn kilowatts(kw: f64) -> Self {
Self(kw * 1e3)
}
pub const fn as_f64(&self) -> f64 {
self.0
}
pub const fn to_watts(&self) -> f64 {
self.0
}
pub const fn to_kilowatts(&self) -> f64 {
self.0 * 1e-3
}
pub fn to_dbw(&self) -> f64 {
10.0 * self.0.log10()
}
}
impl Display for Power {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
self.0.fmt(f)?;
write!(f, " W")
}
}
type RadiansPerSecond = f64;
#[derive(Copy, Clone, Debug, Default, PartialEq, PartialOrd, ApproxEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(transparent)]
pub struct AngularRate(RadiansPerSecond);
impl AngularRate {
pub const fn new(rps: f64) -> Self {
Self(rps)
}
pub const fn radians_per_second(rps: f64) -> Self {
Self(rps)
}
pub const fn degrees_per_second(dps: f64) -> Self {
Self(dps.to_radians())
}
pub const fn as_f64(&self) -> f64 {
self.0
}
pub const fn to_radians_per_second(&self) -> f64 {
self.0
}
pub const fn to_degrees_per_second(&self) -> f64 {
self.0.to_degrees()
}
}
impl Display for AngularRate {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
self.0.to_degrees().fmt(f)?;
write!(f, " deg/s")
}
}
type DecibelValue = f64;
#[derive(Copy, Clone, Debug, Default, PartialEq, PartialOrd, ApproxEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(transparent)]
pub struct Decibel(DecibelValue);
impl Decibel {
pub const fn new(db: f64) -> Self {
Self(db)
}
pub fn from_linear(val: f64) -> Self {
Self(10.0 * val.log10())
}
pub fn to_linear(self) -> f64 {
10.0_f64.powf(self.0 / 10.0)
}
pub const fn as_f64(self) -> f64 {
self.0
}
}
impl Display for Decibel {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
self.0.fmt(f)?;
write!(f, " dB")
}
}
pub trait DecibelUnits {
fn db(&self) -> Decibel;
}
impl DecibelUnits for f64 {
fn db(&self) -> Decibel {
Decibel::new(*self)
}
}
impl DecibelUnits for i64 {
fn db(&self) -> Decibel {
Decibel::new(*self as f64)
}
}
macro_rules! trait_impls {
($($unit:ident),*) => {
$(
impl Neg for $unit {
type Output = Self;
fn neg(self) -> Self::Output {
Self(-self.0)
}
}
impl Add for $unit {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
Self(self.0 + rhs.0)
}
}
impl AddAssign for $unit {
fn add_assign(&mut self, rhs: Self) {
self.0 = self.0 + rhs.0;
}
}
impl Sub for $unit {
type Output = Self;
fn sub(self, rhs: Self) -> Self::Output {
Self(self.0 - rhs.0)
}
}
impl SubAssign for $unit {
fn sub_assign(&mut self, rhs: Self) {
self.0 = self.0 - rhs.0
}
}
impl Mul<$unit> for f64 {
type Output = $unit;
fn mul(self, rhs: $unit) -> Self::Output {
$unit(self * rhs.0)
}
}
impl From<$unit> for f64 {
fn from(val: $unit) -> Self {
val.0
}
}
)*
};
}
trait_impls!(
Angle,
AngularRate,
Decibel,
Distance,
Frequency,
Power,
Pressure,
Temperature,
Velocity
);
#[cfg(test)]
mod tests {
use alloc::format;
use std::f64::consts::{FRAC_PI_2, PI};
use lox_test_utils::assert_approx_eq;
use rstest::rstest;
extern crate alloc;
use super::*;
#[test]
fn test_angle_deg() {
let angle = 90.0.deg();
assert_approx_eq!(angle.0, FRAC_PI_2, rtol <= 1e-10);
}
#[test]
fn test_angle_rad() {
let angle = PI.rad();
assert_approx_eq!(angle.0, PI, rtol <= 1e-10);
}
#[test]
fn test_angle_conversions() {
let angle_deg = 180.0.deg();
let angle_rad = PI.rad();
assert_approx_eq!(angle_deg.0, angle_rad.0, rtol <= 1e-10);
}
#[test]
fn test_angle_display() {
let angle = 90.123456.deg();
assert_eq!(format!("{:.2}", angle), "90.12 deg")
}
#[test]
fn test_angle_neg() {
assert_eq!(Angle(-1.0), -1.0.rad())
}
const TOLERANCE: f64 = f64::EPSILON;
#[rstest]
#[case(Angle::ZERO, Angle::ZERO, 0.0)]
#[case(Angle::PI, Angle::ZERO, -PI)]
#[case(-Angle::PI, Angle::ZERO, -PI)]
#[case(Angle::TAU, Angle::ZERO, 0.0)]
#[case(Angle::FRAC_PI_2, Angle::ZERO, FRAC_PI_2)]
#[case(-Angle::FRAC_PI_2, Angle::ZERO, -FRAC_PI_2)]
#[case(Angle::ZERO, Angle::PI, 0.0)]
#[case(Angle::PI, Angle::PI, PI)]
#[case(-Angle::PI, Angle::PI, PI)]
#[case(Angle::TAU, Angle::PI, 0.0)]
#[case(Angle::FRAC_PI_2, Angle::PI, FRAC_PI_2)]
#[case(-Angle::FRAC_PI_2, Angle::PI, 3.0 * PI / 2.0)]
#[case(Angle::ZERO, -Angle::PI, -TAU)]
#[case(Angle::PI, -Angle::PI, -PI)]
#[case(-Angle::PI, -Angle::PI, -PI)]
#[case(Angle::TAU, -Angle::PI, -TAU)]
#[case(Angle::FRAC_PI_2, -Angle::PI, -3.0 * PI / 2.0)]
#[case(-Angle::FRAC_PI_2, -Angle::PI, -FRAC_PI_2)]
fn test_angle_normalize_two_pi(#[case] angle: Angle, #[case] center: Angle, #[case] exp: f64) {
if exp == 0.0 {
assert_approx_eq!(angle.normalize_two_pi(center).0, exp, atol <= TOLERANCE);
} else {
assert_approx_eq!(angle.normalize_two_pi(center).0, exp, rtol <= TOLERANCE);
}
}
#[test]
fn test_distance_m() {
let distance = 1000.0.m();
assert_eq!(distance.0, 1000.0);
}
#[test]
fn test_distance_km() {
let distance = 1.0.km();
assert_eq!(distance.0, 1000.0);
}
#[test]
fn test_distance_au() {
let distance = 1.0.au();
assert_eq!(distance.0, ASTRONOMICAL_UNIT);
}
#[test]
fn test_distance_conversions() {
let d1 = 1.5e11.m();
let d2 = (1.5e11 / ASTRONOMICAL_UNIT).au();
assert_approx_eq!(d1.0, d2.0, rtol <= 1e-9);
}
#[test]
fn test_distance_display() {
let distance = 9.123456.km();
assert_eq!(format!("{:.2}", distance), "9.12 km")
}
#[test]
fn test_distance_neg() {
assert_eq!(Distance(-1.0), -1.0.m())
}
#[test]
fn test_velocity_mps() {
let velocity = 1000.0.mps();
assert_eq!(velocity.0, 1000.0);
}
#[test]
fn test_velocity_kps() {
let velocity = 1.0.kps();
assert_eq!(velocity.0, 1000.0);
}
#[test]
fn test_velocity_conversions() {
let v1 = 7500.0.mps();
let v2 = 7.5.kps();
assert_eq!(v1.0, v2.0);
}
#[test]
fn test_velocity_display() {
let velocity = 9.123456.kps();
assert_eq!(format!("{:.2}", velocity), "9.12 km/s")
}
#[test]
fn test_velocity_neg() {
assert_eq!(Velocity(-1.0), -1.0.mps())
}
#[test]
fn test_frequency_hz() {
let frequency = 1000.0.hz();
assert_eq!(frequency.0, 1000.0);
}
#[test]
fn test_frequency_khz() {
let frequency = 1.0.khz();
assert_eq!(frequency.0, 1000.0);
}
#[test]
fn test_frequency_mhz() {
let frequency = 1.0.mhz();
assert_eq!(frequency.0, 1_000_000.0);
}
#[test]
fn test_frequency_ghz() {
let frequency = 1.0.ghz();
assert_eq!(frequency.0, 1_000_000_000.0);
}
#[test]
fn test_frequency_thz() {
let frequency = 1.0.thz();
assert_eq!(frequency.0, 1_000_000_000_000.0);
}
#[test]
fn test_frequency_conversions() {
let f1 = 2.4.ghz();
let f2 = 2400.0.mhz();
assert_eq!(f1.0, f2.0);
}
#[test]
fn test_frequency_wavelength() {
let f = 1.0.ghz();
let wavelength = f.wavelength();
assert_approx_eq!(wavelength.0, 0.299792458, rtol <= 1e-9);
}
#[test]
fn test_frequency_wavelength_speed_of_light() {
let f = 299792458.0.hz(); let wavelength = f.wavelength();
assert_approx_eq!(wavelength.0, 1.0, rtol <= 1e-10);
}
#[test]
fn test_frequency_display() {
let frequency = 2.4123456.ghz();
assert_eq!(format!("{:.2}", frequency), "2.41 GHz");
}
#[rstest]
#[case(0.0.hz(), None)]
#[case(3.0.mhz(), Some(FrequencyBand::HF))]
#[case(30.0.mhz(), Some(FrequencyBand::VHF))]
#[case(300.0.mhz(), Some(FrequencyBand::UHF))]
#[case(1.0.ghz(), Some(FrequencyBand::L))]
#[case(2.0.ghz(), Some(FrequencyBand::S))]
#[case(4.0.ghz(), Some(FrequencyBand::C))]
#[case(8.0.ghz(), Some(FrequencyBand::X))]
#[case(12.0.ghz(), Some(FrequencyBand::Ku))]
#[case(18.0.ghz(), Some(FrequencyBand::K))]
#[case(27.0.ghz(), Some(FrequencyBand::Ka))]
#[case(40.0.ghz(), Some(FrequencyBand::V))]
#[case(75.0.ghz(), Some(FrequencyBand::W))]
#[case(110.0.ghz(), Some(FrequencyBand::G))]
#[case(1.0.thz(), None)]
fn test_frequency_band(#[case] f: Frequency, #[case] exp: Option<FrequencyBand>) {
assert_eq!(f.band(), exp)
}
#[test]
fn test_decibel_db() {
let d = 3.0.db();
assert_eq!(d.as_f64(), 3.0);
}
#[test]
fn test_decibel_from_linear() {
let d = Decibel::from_linear(100.0);
assert_approx_eq!(d.0, 20.0, rtol <= 1e-10);
}
#[test]
fn test_decibel_to_linear() {
let d = Decibel::new(20.0);
assert_approx_eq!(d.to_linear(), 100.0, rtol <= 1e-10);
}
#[test]
fn test_decibel_roundtrip() {
let val = 42.5;
let d = Decibel::new(val);
let roundtripped = Decibel::from_linear(d.to_linear());
assert_approx_eq!(roundtripped.0, val, rtol <= 1e-10);
}
#[test]
fn test_decibel_add() {
let sum = 3.0.db() + 3.0.db();
assert_approx_eq!(sum.0, 6.0, rtol <= 1e-10);
}
#[test]
fn test_decibel_sub() {
let diff = 6.0.db() - 3.0.db();
assert_approx_eq!(diff.0, 3.0, rtol <= 1e-10);
}
#[test]
fn test_decibel_neg() {
assert_eq!(-3.0.db(), Decibel::new(-3.0));
}
#[test]
fn test_decibel_display() {
let d = 3.0.db();
assert_eq!(format!("{:.1}", d), "3.0 dB");
}
#[test]
fn test_temperature_new() {
let t = Temperature::new(290.0);
assert_eq!(t.as_f64(), 290.0);
}
#[test]
fn test_temperature_kelvin() {
let t = Temperature::kelvin(300.0);
assert_eq!(t.to_kelvin(), 300.0);
}
#[test]
fn test_temperature_display() {
let t = Temperature::new(290.0);
assert_eq!(format!("{}", t), "290 K");
}
#[test]
fn test_temperature_arithmetic() {
let a = Temperature::new(100.0);
let b = Temperature::new(200.0);
assert_eq!((a + b).as_f64(), 300.0);
assert_eq!((b - a).as_f64(), 100.0);
assert_eq!((-a).as_f64(), -100.0);
assert_eq!((2.0 * a).as_f64(), 200.0);
}
#[test]
fn test_power_watts() {
let p = Power::watts(100.0);
assert_eq!(p.to_watts(), 100.0);
}
#[test]
fn test_power_kilowatts() {
let p = Power::kilowatts(1.0);
assert_eq!(p.to_watts(), 1000.0);
assert_eq!(p.to_kilowatts(), 1.0);
}
#[test]
fn test_power_dbw() {
let p = Power::watts(100.0);
assert_approx_eq!(p.to_dbw(), 20.0, rtol <= 1e-10);
}
#[test]
fn test_power_display() {
let p = Power::watts(100.0);
assert_eq!(format!("{}", p), "100 W");
}
#[test]
fn test_power_arithmetic() {
let a = Power::watts(50.0);
let b = Power::watts(150.0);
assert_eq!((a + b).as_f64(), 200.0);
assert_eq!((b - a).as_f64(), 100.0);
assert_eq!((-a).as_f64(), -50.0);
}
#[test]
fn test_angular_rate_rps() {
let ar = AngularRate::radians_per_second(1.0);
assert_eq!(ar.to_radians_per_second(), 1.0);
assert_approx_eq!(ar.to_degrees_per_second(), 57.29577951308232, rtol <= 1e-10);
}
#[test]
fn test_angular_rate_dps() {
let ar = AngularRate::degrees_per_second(180.0);
assert_approx_eq!(
ar.to_radians_per_second(),
core::f64::consts::PI,
rtol <= 1e-10
);
}
#[test]
fn test_angular_rate_display() {
let ar = AngularRate::radians_per_second(1.0);
let s = format!("{}", ar);
assert!(s.contains("deg/s"));
}
#[test]
fn test_angular_rate_arithmetic() {
let a = AngularRate::new(1.0);
let b = AngularRate::new(2.0);
assert_eq!((a + b).as_f64(), 3.0);
assert_eq!((b - a).as_f64(), 1.0);
assert_eq!((-a).as_f64(), -1.0);
assert_eq!((3.0 * a).as_f64(), 3.0);
}
#[test]
fn test_pressure_hpa() {
let p = Pressure::hpa(1013.25);
assert_eq!(p.to_hpa(), 1013.25);
assert_approx_eq!(p.to_pa(), 101325.0, rtol <= 1e-10);
}
#[test]
fn test_pressure_pa() {
let p = Pressure::pa(101325.0);
assert_approx_eq!(p.to_hpa(), 1013.25, rtol <= 1e-10);
}
#[test]
fn test_pressure_display() {
let p = Pressure::pa(101325.0);
let s = format!("{}", p);
assert!(s.contains("Pa"));
}
}