use std::{
convert::From,
fmt::{Display, Formatter, LowerExp, UpperExp},
};
use num_traits::{Float, FloatConst, Inv, Num};
macro_rules! impl_display {
($name:ident) => {
impl<T: Display + Num> Display for $name<T> {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
Display::fmt(&self.0, f)
}
}
impl<T: LowerExp + Float> LowerExp for $name<T> {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
LowerExp::fmt(&self.0, f)
}
}
impl<T: UpperExp + Float> UpperExp for $name<T> {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
UpperExp::fmt(&self.0, f)
}
}
};
}
pub trait ToDecibel {
fn to_db(&self) -> Self;
}
impl ToDecibel for f64 {
fn to_db(&self) -> Self {
20. * self.log10()
}
}
impl ToDecibel for f32 {
fn to_db(&self) -> Self {
20. * self.log10()
}
}
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
pub struct Decibel<T: Num>(pub T);
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
pub struct Seconds<T: Num>(pub T);
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
pub struct Hertz<T: Num>(pub T);
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
pub struct RadiansPerSecond<T: Num>(pub T);
impl_display!(Decibel);
impl_display!(Seconds);
impl_display!(Hertz);
impl_display!(RadiansPerSecond);
impl<T: Num + FloatConst> From<Hertz<T>> for RadiansPerSecond<T> {
fn from(hz: Hertz<T>) -> Self {
Self(T::TAU() * hz.0)
}
}
impl<T: Num + FloatConst> From<RadiansPerSecond<T>> for Hertz<T> {
fn from(rps: RadiansPerSecond<T>) -> Self {
Self(rps.0 / T::TAU())
}
}
impl<T: Inv<Output = T> + Num> Inv for Seconds<T> {
type Output = Hertz<T>;
fn inv(self) -> Self::Output {
Hertz(self.0.inv())
}
}
impl<T: Inv<Output = T> + Num> Inv for Hertz<T> {
type Output = Seconds<T>;
fn inv(self) -> Self::Output {
Seconds(self.0.inv())
}
}
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;
#[test]
fn decibel() {
assert_abs_diff_eq!(40., 100_f64.to_db(), epsilon = 0.);
assert_relative_eq!(-3.0103, 2_f64.inv().sqrt().to_db(), max_relative = 1e5);
assert_abs_diff_eq!(0., 1_f32.to_db(), epsilon = 0.);
}
#[test]
fn conversion() {
let tau = 2. * std::f64::consts::PI;
assert_eq!(RadiansPerSecond(tau), RadiansPerSecond::from(Hertz(1.0)));
let hz = Hertz(2.0);
assert_eq!(hz, Hertz::from(RadiansPerSecond::from(hz)));
let rps = RadiansPerSecond(2.0);
assert_eq!(rps, RadiansPerSecond::from(Hertz::from(rps)));
}
proptest! {
#[test]
fn qc_conversion_hertz(hz in (0.0..1e12)) {
assert_relative_eq!(
hz,
Hertz::from(RadiansPerSecond::from(Hertz(hz))).0,
max_relative = 1e-15
);
}
}
proptest! {
#[test]
fn qc_conversion_rps(rps in (0.0..1e12)) {
assert_relative_eq!(
rps,
RadiansPerSecond::from(Hertz::from(RadiansPerSecond(rps))).0,
max_relative = 1e-15
);
}
}
proptest! {
#[test]
fn qc_conversion_s_hz(s in (0.0..1e12_f32)) {
assert_relative_eq!(s, Seconds(s).inv().inv().0, max_relative = 1e-5);
}
}
proptest! {
#[test]
fn qc_conversion_hz_s(hz in (0.0..1e12_f64)) {
assert_relative_eq!(hz, Hertz(hz).inv().inv().0, max_relative = 1e-14);
}
}
#[test]
fn format() {
assert_eq!("0.33".to_owned(), format!("{:.2}", Seconds(1. / 3.)));
assert_eq!("0.3333".to_owned(), format!("{:.*}", 4, Seconds(1. / 3.)));
assert_eq!("4.20e1".to_owned(), format!("{:.2e}", Seconds(42.)));
assert_eq!("4.20E2".to_owned(), format!("{:.2E}", Seconds(420.)));
}
}