use crate::prefix::Prefix;
use std::convert::From;
use std::fmt;
use crate::base::Base;
use crate::prefix::Constraint;
#[diagnostic::on_unimplemented(
message = "`{Self}` cannot be converted to `f64`",
label = "this type doesn't implement `IntoF64`",
note = "for `u64`, `i64`, `usize`, or `isize`, enable the `lossy-conversions` feature"
)]
pub trait IntoF64 {
fn into_f64(self) -> f64;
}
macro_rules! impl_into_f64_lossless {
($($t:ty),*) => {
$(
impl IntoF64 for $t {
#[inline]
fn into_f64(self) -> f64 {
self.into()
}
}
)*
};
}
impl_into_f64_lossless!(u8, i8, u16, i16, u32, i32, f32, f64);
#[cfg(feature = "lossy-conversions")]
macro_rules! impl_into_f64_lossy {
($($t:ty),*) => {
$(
impl IntoF64 for $t {
#[inline]
fn into_f64(self) -> f64 {
self as f64
}
}
)*
};
}
#[cfg(feature = "lossy-conversions")]
impl_into_f64_lossy!(u64, i64, usize, isize);
#[derive(Debug, PartialEq)]
pub struct Value {
pub mantissa: f64,
pub prefix: Prefix,
pub base: Base,
}
impl Value {
pub fn new<F>(x: F) -> Self
where
F: IntoF64,
{
Value::new_with(x, Base::B1000, Constraint::None)
}
pub fn new_with<F, C>(x: F, base: Base, prefix_constraint: C) -> Self
where
F: IntoF64,
C: AsRef<Constraint>,
{
let x: f64 = x.into_f64();
let exponent: i32 = base.integral_exponent_for(x);
let prefix = Self::closest_prefix_for(exponent, prefix_constraint);
let mantissa = x / base.pow(prefix.exponent());
Value {
mantissa,
base,
prefix,
}
}
pub fn to_f64(&self) -> f64 {
let scale = self.base.pow(self.prefix.exponent());
self.mantissa * scale
}
pub fn signum(&self) -> f64 {
self.mantissa.signum()
}
fn closest_prefix_for<C: AsRef<Constraint>>(exponent: i32, constraint: C) -> Prefix {
use std::convert::TryFrom;
match constraint.as_ref() {
Constraint::None => {
Prefix::try_from(exponent.clamp(Prefix::Yocto as i32, Prefix::Yotta as i32))
.unwrap_or(Prefix::Unit)
}
Constraint::UnitOnly => Prefix::Unit,
Constraint::UnitAndAbove => {
Prefix::try_from(exponent.clamp(Prefix::Unit as i32, Prefix::Yotta as i32))
.unwrap_or(Prefix::Unit)
}
Constraint::UnitAndBelow => {
Prefix::try_from(exponent.clamp(Prefix::Yocto as i32, Prefix::Unit as i32))
.unwrap_or(Prefix::Unit)
}
Constraint::Custom(allowed_prefixes) => {
if allowed_prefixes.is_empty() {
panic!("At least one prefix should be allowed");
}
let smallest_prefix = *allowed_prefixes.first().unwrap();
if exponent < smallest_prefix as i32 {
return smallest_prefix;
}
allowed_prefixes
.iter()
.take_while(|&&prefix| prefix as i32 <= exponent)
.cloned()
.last()
.unwrap_or(Prefix::Unit)
}
}
}
}
impl From<Value> for f64 {
fn from(value: Value) -> Self {
value.to_f64()
}
}
macro_rules! impl_from_num_for_value {
($t:ty) => {
impl From<$t> for Value {
fn from(x: $t) -> Self {
Value::new(x)
}
}
impl From<&$t> for Value {
fn from(x: &$t) -> Self {
Value::new(*x)
}
}
};
}
impl fmt::Display for Value {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.prefix {
Prefix::Unit => write!(f, "{}", self.mantissa),
_ => write!(f, "{} {}", self.mantissa, self.prefix),
}
}
}
impl_from_num_for_value!(u8);
impl_from_num_for_value!(i8);
impl_from_num_for_value!(u16);
impl_from_num_for_value!(i16);
impl_from_num_for_value!(u32);
impl_from_num_for_value!(i32);
impl_from_num_for_value!(f32);
impl_from_num_for_value!(f64);
#[cfg(feature = "lossy-conversions")]
impl_from_num_for_value!(u64);
#[cfg(feature = "lossy-conversions")]
impl_from_num_for_value!(i64);
#[cfg(feature = "lossy-conversions")]
impl_from_num_for_value!(usize);
#[cfg(feature = "lossy-conversions")]
impl_from_num_for_value!(isize);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn out_of_scale_values() {
let actual = Value::new(1e-28);
let expected = Value {
mantissa: 1e-4f64,
prefix: Prefix::Yocto,
base: Base::B1000,
};
assert_eq!(actual, expected);
let actual = Value::new(-1.5e28);
let expected = Value {
mantissa: -1.5e4f64,
prefix: Prefix::Yotta,
base: Base::B1000,
};
assert_eq!(actual, expected);
}
#[test]
fn unit_values() {
let actual = Value::new(1);
let expected = Value {
mantissa: 1f64,
prefix: Prefix::Unit,
base: Base::B1000,
};
assert_eq!(actual, expected);
let actual = Value::new(-1.3);
let expected = Value {
mantissa: -1.3f64,
prefix: Prefix::Unit,
base: Base::B1000,
};
assert_eq!(actual, expected);
}
#[test]
fn small_values() {
let actual = Value::new(0.1);
let expected = Value {
mantissa: 100f64,
prefix: Prefix::Milli,
base: Base::B1000,
};
assert_eq!(actual, expected);
let actual = Value::new(-0.1);
let expected = Value {
mantissa: -100f64,
prefix: Prefix::Milli,
base: Base::B1000,
};
assert_eq!(actual, expected);
let actual = Value::new(0.001);
let expected = Value {
mantissa: 1f64,
prefix: Prefix::Milli,
base: Base::B1000,
};
assert_eq!(actual, expected);
let actual = Value::new(-0.001);
let expected = Value {
mantissa: -1f64,
prefix: Prefix::Milli,
base: Base::B1000,
};
assert_eq!(actual, expected);
let actual = Value::new(0.000_1);
let expected = Value {
mantissa: 100.00000000000001f64,
prefix: Prefix::Micro,
base: Base::B1000,
};
assert_eq!(actual, expected);
let actual = Value::new(-0.000_1);
let expected = Value {
mantissa: -100.00000000000001f64,
prefix: Prefix::Micro,
base: Base::B1000,
};
assert_eq!(actual, expected);
let actual = Value::new(-1e-4);
let expected = Value {
mantissa: -100.00000000000001f64,
prefix: Prefix::Micro,
base: Base::B1000,
};
assert_eq!(actual, expected);
let actual = Value::new(-1e-8);
let expected = Value {
mantissa: -10f64,
prefix: Prefix::Nano,
base: Base::B1000,
};
assert_eq!(actual, expected);
let actual = Value::new(-1e-23);
let expected = Value {
mantissa: -10f64,
prefix: Prefix::Yocto,
base: Base::B1000,
};
assert_eq!(actual, expected);
let actual = Value::new(0.12345);
let expected = Value {
mantissa: 123.45f64,
prefix: Prefix::Milli,
base: Base::B1000,
};
assert_eq!(actual, expected);
let actual = Value::new(-0.12345);
let expected = Value {
mantissa: -123.45f64,
prefix: Prefix::Milli,
base: Base::B1000,
};
assert_eq!(actual, expected);
let actual = Value::new(0.01234);
let expected = Value {
mantissa: 12.34f64,
prefix: Prefix::Milli,
base: Base::B1000,
};
assert_eq!(actual, expected);
let actual = Value::new(-0.01234);
let expected = Value {
mantissa: -12.34f64,
prefix: Prefix::Milli,
base: Base::B1000,
};
assert_eq!(actual, expected);
let actual = Value::new(0.001234);
let expected = Value {
mantissa: 1.234f64,
prefix: Prefix::Milli,
base: Base::B1000,
};
assert_eq!(actual, expected);
let actual = Value::new(-0.001234);
let expected = Value {
mantissa: -1.234f64,
prefix: Prefix::Milli,
base: Base::B1000,
};
assert_eq!(actual, expected);
let actual = Value::new(0.000_123_400);
let expected = Value {
mantissa: 123.39999999999999f64,
prefix: Prefix::Micro,
base: Base::B1000,
};
assert_eq!(actual, expected);
let actual = Value::new(-0.000_123_400);
let expected = Value {
mantissa: -123.39999999999999f64,
prefix: Prefix::Micro,
base: Base::B1000,
};
assert_eq!(actual, expected);
}
#[test]
fn large_values() {
let actual = Value::new(1234);
let expected = Value {
mantissa: 1.234f64,
prefix: Prefix::Kilo,
base: Base::B1000,
};
assert_eq!(actual, expected);
let actual = Value::new(123_456);
let expected = Value {
mantissa: 123.456f64,
prefix: Prefix::Kilo,
base: Base::B1000,
};
assert_eq!(actual, expected);
let actual = Value::new(123_456_000);
let expected = Value {
mantissa: 123.456f64,
prefix: Prefix::Mega,
base: Base::B1000,
};
assert_eq!(actual, expected);
let actual = Value::new(-123_456_000);
let expected = Value {
mantissa: -123.456f64,
prefix: Prefix::Mega,
base: Base::B1000,
};
assert_eq!(actual, expected);
}
#[test]
fn from_numbers() {
let actual = Value::from(0.1f32);
let expected = Value {
mantissa: 100.00000149011612f64,
prefix: Prefix::Milli,
base: Base::B1000,
};
assert_eq!(actual, expected);
let actual = Value::from(-0.1);
let expected = Value {
mantissa: -100f64,
prefix: Prefix::Milli,
base: Base::B1000,
};
assert_eq!(actual, expected);
let actual = Value::from(1.5);
let expected = Value {
mantissa: 1.5f64,
prefix: Prefix::Unit,
base: Base::B1000,
};
assert_eq!(actual, expected);
let actual = Value::from(-1.5);
let expected = Value {
mantissa: -1.5f64,
prefix: Prefix::Unit,
base: Base::B1000,
};
assert_eq!(actual, expected);
let actual = Value::from(15u32);
let expected = Value {
mantissa: 15f64,
prefix: Prefix::Unit,
base: Base::B1000,
};
assert_eq!(actual, expected);
let actual = Value::from(-1.5e28);
let expected = Value {
mantissa: -1.5e4f64,
prefix: Prefix::Yotta,
base: Base::B1000,
};
assert_eq!(actual, expected);
}
#[test]
fn from_ref_number() {
let number = 0.1;
let actual = Value::from(&number);
let expected = Value {
mantissa: 100f64,
prefix: Prefix::Milli,
base: Base::B1000,
};
assert_eq!(actual, expected);
let number = 10_000_000u32;
let actual = Value::from(&number);
let expected = Value {
mantissa: 10f64,
prefix: Prefix::Mega,
base: Base::B1000,
};
assert_eq!(actual, expected);
}
#[test]
fn into_f64() {
let x = 0.1;
let actual: f64 = Value::from(x).into();
let expected = x;
assert_eq!(actual, expected);
let x = -0.1;
let actual: f64 = Value::from(x).into();
let expected = x;
assert_eq!(actual, expected);
let x = 1.500;
let actual: f64 = Value::from(x).into();
let expected = x;
assert_eq!(actual, expected);
let x = -1.500;
let actual: f64 = Value::from(x).into();
let expected = x;
assert_eq!(actual, expected);
}
#[test]
fn large_value_with_base_1024() {
let actual = Value::new_with(1, Base::B1024, Constraint::None);
let expected = Value {
mantissa: 1f64,
prefix: Prefix::Unit,
base: Base::B1024,
};
assert_eq!(actual, expected);
let actual = Value::new_with(16, Base::B1024, Constraint::None);
let expected = Value {
mantissa: 16f64,
prefix: Prefix::Unit,
base: Base::B1024,
};
assert_eq!(actual, expected);
let actual = Value::new_with(1024, Base::B1024, Constraint::None);
let expected = Value {
mantissa: 1f64,
prefix: Prefix::Kilo,
base: Base::B1024,
};
assert_eq!(actual, expected);
let actual = Value::new_with(1.6 * 1024f32, Base::B1024, Constraint::None);
let expected = Value {
mantissa: 1.600000023841858f64,
prefix: Prefix::Kilo,
base: Base::B1024,
};
assert_eq!(actual, expected);
let actual = Value::new_with(16 * 1024 * 1024, Base::B1024, Constraint::None);
let expected = Value {
mantissa: 16f64,
prefix: Prefix::Mega,
base: Base::B1024,
};
assert_eq!(actual, expected);
}
#[test]
fn values_with_prefix_constraints() {
let actual = Value::new_with(1325, Base::B1000, Constraint::UnitAndBelow);
let expected = Value {
mantissa: 1325f64,
prefix: Prefix::Unit,
base: Base::B1000,
};
assert_eq!(actual, expected);
let actual = Value::new_with(0.015, Base::B1024, Constraint::UnitAndAbove);
let expected = Value {
mantissa: 0.015,
prefix: Prefix::Unit,
base: Base::B1024,
};
assert_eq!(actual, expected);
let actual = Value::new_with(0.015, Base::B1000, Constraint::UnitOnly);
let expected = Value {
mantissa: 0.015,
prefix: Prefix::Unit,
base: Base::B1000,
};
assert_eq!(actual, expected);
}
#[test]
fn closest_prefix_without_constraint() {
let exponent = -24;
let actual = Value::closest_prefix_for(exponent, Constraint::None);
let expected = Prefix::Yocto;
assert_eq!(actual, expected);
let exponent = 0;
let actual = Value::closest_prefix_for(exponent, Constraint::None);
let expected = Prefix::Unit;
assert_eq!(actual, expected);
let exponent = 24;
let actual = Value::closest_prefix_for(exponent, Constraint::None);
let expected = Prefix::Yotta;
assert_eq!(actual, expected);
let exponent = 30;
let actual = Value::closest_prefix_for(exponent, Constraint::None);
let expected = Prefix::Yotta;
assert_eq!(actual, expected);
let exponent = 1; let actual = Value::closest_prefix_for(exponent, Constraint::None);
let expected = Prefix::Unit;
assert_eq!(actual, expected);
}
#[test]
fn closest_prefix_with_unitandabove() {
let constraint = Constraint::UnitAndAbove;
let exponent = -24;
let actual = Value::closest_prefix_for(exponent, &constraint);
let expected = Prefix::Unit;
assert_eq!(actual, expected);
let exponent = 0;
let actual = Value::closest_prefix_for(exponent, &constraint);
let expected = Prefix::Unit;
assert_eq!(actual, expected);
let exponent = 24;
let actual = Value::closest_prefix_for(exponent, &constraint);
let expected = Prefix::Yotta;
assert_eq!(actual, expected);
let exponent = 30;
let actual = Value::closest_prefix_for(exponent, &constraint);
let expected = Prefix::Yotta;
assert_eq!(actual, expected);
let exponent = 1; let actual = Value::closest_prefix_for(exponent, &constraint);
let expected = Prefix::Unit;
assert_eq!(actual, expected);
}
#[test]
fn closest_prefix_with_unitandbelow() {
let constraint = Constraint::UnitAndBelow;
let exponent = -24;
let actual = Value::closest_prefix_for(exponent, &constraint);
let expected = Prefix::Yocto;
assert_eq!(actual, expected);
let exponent = 0;
let actual = Value::closest_prefix_for(exponent, &constraint);
let expected = Prefix::Unit;
assert_eq!(actual, expected);
let exponent = 24;
let actual = Value::closest_prefix_for(exponent, &constraint);
let expected = Prefix::Unit;
assert_eq!(actual, expected);
let exponent = -30;
let actual = Value::closest_prefix_for(exponent, &constraint);
let expected = Prefix::Yocto;
assert_eq!(actual, expected);
let exponent = -1; let actual = Value::closest_prefix_for(exponent, &constraint);
let expected = Prefix::Unit;
assert_eq!(actual, expected);
}
#[test]
fn closest_prefix_with_custom() {
let constraint = Constraint::Custom(vec![Prefix::Milli, Prefix::Unit, Prefix::Kilo]);
let exponent = -24;
let actual = Value::closest_prefix_for(exponent, &constraint);
let expected = Prefix::Milli;
assert_eq!(actual, expected);
let exponent = -3;
let actual = Value::closest_prefix_for(exponent, &constraint);
let expected = Prefix::Milli;
assert_eq!(actual, expected);
let exponent = 0;
let actual = Value::closest_prefix_for(exponent, &constraint);
let expected = Prefix::Unit;
assert_eq!(actual, expected);
let exponent = 3;
let actual = Value::closest_prefix_for(exponent, &constraint);
let expected = Prefix::Kilo;
assert_eq!(actual, expected);
let exponent = 24;
let actual = Value::closest_prefix_for(exponent, &constraint);
let expected = Prefix::Kilo;
assert_eq!(actual, expected);
let exponent = -30;
let actual = Value::closest_prefix_for(exponent, &constraint);
let expected = Prefix::Milli;
assert_eq!(actual, expected);
let exponent = -1; let actual = Value::closest_prefix_for(exponent, &constraint);
let expected = Prefix::Milli;
assert_eq!(actual, expected);
}
#[test]
#[should_panic]
fn closest_prefix_with_custom_empty() {
let constraint = Constraint::Custom(vec![]);
let exponent = 3;
Value::closest_prefix_for(exponent, constraint);
}
}