#![doc = include_str!("../README.md")]
#[derive(thiserror::Error, Debug, PartialEq)]
pub enum UnitScaleError {
#[error("Outside of type bounds: {0}")]
Conversion(String),
#[error("Unknown error")]
Unknown(String),
}
pub trait UnitScale {
const SCALE: f64;
}
pub trait Scaled<U> {
fn scaled_value(&self) -> U;
}
pub trait ScaledPrimitiveByteSize {
fn primitive_byte_size() -> usize;
}
#[cfg(test)]
mod tests {
use super::*;
use core::marker::PhantomData;
use core::mem::discriminant;
use float_cmp::approx_eq;
use num_traits::{FromPrimitive, ToPrimitive};
struct Scale0_01;
impl UnitScale for Scale0_01 {
const SCALE: f64 = 0.01;
}
struct Volts<S, U> {
value: U,
_scale: std::marker::PhantomData<S>,
}
impl<S, U> TryFrom<f64> for Volts<S, U>
where
S: UnitScale,
U: FromPrimitive,
{
type Error = UnitScaleError;
fn try_from(value: f64) -> Result<Self, Self::Error> {
let scaled_value = value / S::SCALE;
if let Some(value) = U::from_f64(scaled_value) {
Ok(Self {
value,
_scale: PhantomData,
})
} else {
Err(UnitScaleError::Conversion(format!(
"Scaled {} is outside of {} bounds",
scaled_value,
std::any::type_name::<U>()
)))
}
}
}
impl<S, U> Scaled<U> for Volts<S, U>
where
S: UnitScale,
U: Copy,
{
fn scaled_value(&self) -> U {
self.value
}
}
impl<S, U> ScaledPrimitiveByteSize for Volts<S, U>
where
S: UnitScale,
U: Copy + ToPrimitive,
{
fn primitive_byte_size() -> usize {
core::mem::size_of::<U>()
}
}
impl<S, U> Volts<S, U>
where
S: UnitScale,
U: Copy + ToPrimitive,
{
fn to_f64(&self) -> Option<f64> {
self.value.to_f64().map(|v| v * S::SCALE)
}
}
#[test]
fn data_scaled_to_0_01() {
assert_eq!(Scale0_01::SCALE, 0.01);
}
#[test]
fn test_scale_0_01_for_u8() {
let value: f64 = 1.28;
let volts = Volts::<Scale0_01, u8>::try_from(value).unwrap();
assert_eq!(volts.scaled_value(), 128);
assert!(approx_eq!(
f64,
volts.to_f64().expect("Unable to convert to f64"),
value,
epsilon = 0.01
));
}
#[test]
fn test_scale_0_01_for_u8_out_of_bounds() {
let value: f64 = 3.01;
let volts = Volts::<Scale0_01, u8>::try_from(value);
assert!(volts.is_err());
if let Err(e) = volts {
assert_eq!(
discriminant(&e),
discriminant(&UnitScaleError::Conversion("".into()))
);
}
}
#[test]
fn primitive_byte_size() {
let byte_size = Volts::<Scale0_01, u8>::primitive_byte_size();
assert_eq!(byte_size, 1, "For `u8` the primitive byte size should be 1");
}
}