use std::fmt::{Display, Formatter};
use std::ops::{Add, AddAssign, Div, Mul};
use approx::{AbsDiffEq, RelativeEq, relative_eq};
use dimensioned::si::{Meter, Second};
use num_traits::{Float, Num, NumCast, Pow, ToPrimitive};
use tracing::debug;
use crate::types::{Result, TypeError};
macro_rules! unit_of_measure {
($a:ident as $u:ident) => {
unit_of_measure!($a as $u, "");
};
($a:ident as $u:ident, $doc:literal) => {
#[allow(clippy::empty_docs)]
#[doc = $doc]
#[derive(Clone, Copy, Default, PartialEq, PartialOrd, Debug)]
pub struct $u<N: Num> {
pub value_unsafe: N,
}
impl<N> $u<N>
where
N: Num,
{
pub fn new(x: N) -> Self {
$u { value_unsafe: x }
}
}
impl<N> $u<N>
where
N: Num + NumCast,
{
pub fn num_cast_from<M: Num + ToPrimitive>(val: $u<M>) -> Option<$u<N>> {
<N as NumCast>::from(val.value_unsafe).map($u::new)
}
}
#[allow(dead_code)]
#[allow(clippy::empty_docs)]
#[doc = $doc]
pub const $a: $u<u8> = $u { value_unsafe: 1u8 };
impl<N> Add for $u<N>
where
N: Num + Add,
{
type Output = Self;
fn add(self, rhs: Self) -> Self {
Self::new(self.value_unsafe + rhs.value_unsafe)
}
}
impl<N> AddAssign for $u<N>
where
N: Num + AddAssign,
{
fn add_assign(&mut self, rhs: Self) {
self.value_unsafe += rhs.value_unsafe;
}
}
__constant_mul_impl!($u, f64);
__constant_mul_impl!($u, f32);
__constant_mul_impl!($u, i8);
__constant_mul_impl!($u, i16);
__constant_mul_impl!($u, i32);
__constant_mul_impl!($u, i64);
__constant_mul_impl!($u, u8);
__constant_mul_impl!($u, u16);
__constant_mul_impl!($u, u32);
__constant_mul_impl!($u, u64);
impl<N> Div<N> for $u<N>
where
N: Num + Div,
{
type Output = Self;
fn div(self, rhs: N) -> Self {
Self::new(self.value_unsafe / rhs)
}
}
impl<N> AbsDiffEq for $u<N>
where
N: Num + Float + AbsDiffEq<N, Epsilon = N>,
{
type Epsilon = N;
fn default_epsilon() -> Self::Epsilon {
N::epsilon()
}
fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool {
self.value_unsafe.abs_diff_eq(&other.value_unsafe, epsilon)
}
}
impl<N> RelativeEq for $u<N>
where
N: Num + Float + AbsDiffEq<N, Epsilon = N> + RelativeEq<N>,
{
fn default_max_relative() -> Self::Epsilon {
N::epsilon()
}
fn relative_eq(
&self,
other: &Self,
epsilon: Self::Epsilon,
max_relative: Self::Epsilon,
) -> bool {
relative_eq!(
self.value_unsafe,
other.value_unsafe,
epsilon = epsilon,
max_relative = max_relative
)
}
}
};
}
macro_rules! __constant_mul_impl {
($u:ident, $n:tt) => {
impl<N> Mul<$u<N>> for $n
where
N: Num,
$n: From<N>,
{
type Output = $u<$n>;
fn mul(self, rhs: $u<N>) -> Self::Output {
Self::Output::new(self * <$n as ::std::convert::From<N>>::from(rhs.value_unsafe))
}
}
};
}
unit_of_measure![DEG as Degree, "Angular degree"];
unit_of_measure![SEMI as Semicircle];
impl TryFrom<Degree<f64>> for Semicircle<i32> {
type Error = TypeError;
fn try_from(value: Degree<f64>) -> Result<Self> {
let mut sc64 = <i64 as NumCast>::from((2f64.pow(31) / 180.0) * value.value_unsafe)
.ok_or(TypeError::NumericCast)?;
if sc64 == (i32::MAX as i64) + 1 {
debug!("Wrapping ~180.0 deg to negative semicircle value");
sc64 = i32::MIN as i64;
}
Ok(<i32 as NumCast>::from(sc64).ok_or(TypeError::NumericCast)? * SEMI)
}
}
impl From<Semicircle<i32>> for Degree<f64> {
fn from(value: Semicircle<i32>) -> Self {
(<f64 as From<i32>>::from(value.value_unsafe) * 180.0 / 2f64.pow(31)) * DEG
}
}
unit_of_measure![CM as Centimeter];
impl<N> From<Meter<N>> for Centimeter<N>
where
N: Num + From<u8>,
{
fn from(value: Meter<N>) -> Self {
Self::new(N::from(100) * value.value_unsafe)
}
}
unit_of_measure![KM as Kilometer];
impl From<Meter<f64>> for Kilometer<f64> {
fn from(value: Meter<f64>) -> Self {
Self::new(value.value_unsafe / 1_000.0)
}
}
impl<N> Display for Kilometer<N>
where
N: Num + Display,
{
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{:.02} km", self.value_unsafe)
}
}
unit_of_measure![MI as Mile];
impl From<Meter<f64>> for Mile<f64> {
fn from(value: Meter<f64>) -> Self {
Self::new(value.value_unsafe / 1609.344)
}
}
impl<N> Display for Mile<N>
where
N: Num + Display,
{
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{:.02} mi", self.value_unsafe)
}
}
unit_of_measure![MS as Millisecond];
impl<N> From<Second<N>> for Millisecond<N>
where
N: Num + From<u16>,
{
fn from(value: Second<N>) -> Self {
Self::new(N::from(1_000) * value.value_unsafe)
}
}
unit_of_measure![NS as Nanosecond];
impl<N> From<Second<N>> for Nanosecond<N>
where
N: Num + From<u32>,
{
fn from(value: Second<N>) -> Self {
Self::new(N::from(1_000_000_000) * value.value_unsafe)
}
}
#[cfg(test)]
mod tests {
use anyhow::Result;
use approx::assert_relative_eq;
use dimensioned::si::M;
use super::{DEG, Degree, KM, Kilometer, MI, Mile, SEMI, Semicircle};
#[test]
fn from_deg_min() -> Result<()> {
let sc = Semicircle::<i32>::try_from(-180.0 * DEG)?;
assert_eq!(sc, i32::MIN * SEMI);
Ok(())
}
#[test]
fn from_deg_max() -> Result<()> {
let sc = Semicircle::<i32>::try_from(180.0 * DEG)?;
assert_eq!(sc, i32::MIN * SEMI);
Ok(())
}
#[test]
fn sc_round_trip_negative() -> Result<()> {
let original = (i32::MIN + 10) * SEMI;
let deg: Degree<f64> = original.try_into()?;
let result: Semicircle<i32> = deg.try_into()?;
assert_eq!(result, original);
Ok(())
}
#[test]
fn sc_round_trip_positive() -> Result<()> {
let original = (i32::MAX - 10) * SEMI;
let deg: Degree<f64> = original.try_into()?;
let result: Semicircle<i32> = deg.try_into()?;
assert_eq!(result, original);
Ok(())
}
#[test]
fn kilometer_conversion() {
let meters = 1.0 * M;
let kilometers: Kilometer<f64> = meters.into();
assert_eq!(kilometers, 0.001 * KM);
}
#[test]
fn mile_conversion() {
let meters = 1_000.0 * M;
let miles: Mile<f64> = meters.into();
assert_relative_eq!(miles, 0.6213712 * MI, epsilon = 0.000001);
}
}