use std::{
convert::From,
fmt::{Display, Formatter, LowerExp, UpperExp},
ops::{Add, Div, Mul},
};
use crate::{Const, Inv, Zero};
macro_rules! impl_display {
($name:ident) => {
impl<T: Display> Display for $name<T> {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
Display::fmt(&self.0, f)
}
}
impl<T: LowerExp> LowerExp for $name<T> {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
LowerExp::fmt(&self.0, f)
}
}
impl<T: UpperExp> 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>(pub T);
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
pub struct Seconds<T>(pub T);
impl<T> Add<Seconds<T>> for &Seconds<T>
where
T: Clone + Add<Output = T>,
{
type Output = Seconds<T>;
fn add(self, rhs: Seconds<T>) -> Self::Output {
Seconds(self.0.clone() + rhs.0)
}
}
impl<T> Add for &Seconds<T>
where
T: Clone + Add<Output = T>,
{
type Output = Seconds<T>;
fn add(self, rhs: Self) -> Self::Output {
Seconds(self.0.clone() + rhs.0.clone())
}
}
impl<T> Mul<&T> for &Seconds<T>
where
T: Clone + Mul<Output = T>,
{
type Output = Seconds<T>;
fn mul(self, rhs: &T) -> Self::Output {
Seconds(self.0.clone() * rhs.clone())
}
}
impl<T> Mul<T> for &Seconds<T>
where
T: Clone + Mul<Output = T>,
{
type Output = Seconds<T>;
fn mul(self, rhs: T) -> Self::Output {
Seconds(self.0.clone() * rhs)
}
}
impl<T> Zero for Seconds<T>
where
T: Zero,
{
fn zero() -> Self {
Seconds(T::zero())
}
fn is_zero(&self) -> bool {
self.0.is_zero()
}
}
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
pub struct Hertz<T>(pub T);
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
pub struct RadiansPerSecond<T>(pub T);
impl_display!(Decibel);
impl_display!(Seconds);
impl_display!(Hertz);
impl_display!(RadiansPerSecond);
impl<T> From<Hertz<T>> for RadiansPerSecond<T>
where
T: Const + Mul<Output = T>,
{
fn from(hz: Hertz<T>) -> Self {
Self(T::tau() * hz.0)
}
}
impl<T> From<RadiansPerSecond<T>> for Hertz<T>
where
T: Div<Output = T> + Const,
{
fn from(rps: RadiansPerSecond<T>) -> Self {
Self(rps.0 / T::tau())
}
}
impl<T> Inv for Seconds<T>
where
T: Inv<Output = T>,
{
type Output = Hertz<T>;
fn inv(self) -> Self::Output {
Hertz(self.0.inv())
}
}
impl<T> Inv for Hertz<T>
where
T: Inv<Output = T>,
{
type Output = Seconds<T>;
fn inv(self) -> Self::Output {
Seconds(self.0.inv())
}
}
#[cfg(test)]
mod tests {
use proptest::prelude::*;
use super::*;
#[test]
fn decibel() {
assert_abs_diff_eq!(40., 100_f64.to_db(), epsilon = 0.);
assert_relative_eq!(-3.0103, 2_f64.recip().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.)));
}
#[test]
fn zero_seconds() {
assert!(Seconds::<f32>::zero().is_zero());
}
}