use std::{
cmp::{Eq, Ord, PartialEq, PartialOrd},
default::Default,
fmt::Display,
ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign},
};
use rust_decimal::prelude::*;
use crate::{format::Format, scale::Scale, utils::scale_format_to_string};
#[derive(Debug, Clone, Default)]
pub struct ParsedQuantity {
pub(crate) value: Decimal,
pub(super) scale: Scale,
pub(super) format: Format,
}
impl Display for ParsedQuantity {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let string_representation = format!(
"{}{}",
self.value,
scale_format_to_string(&self.scale, &self.format)
);
write!(f, "{}", string_representation)
}
}
impl From<Decimal> for ParsedQuantity {
fn from(value: Decimal) -> Self {
Self {
value,
format: Format::DecimalSI,
scale: Scale::One,
}
}
}
impl Add for ParsedQuantity {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
let mut lhs = self;
let mut rhs = rhs;
normalize_formats(&mut lhs, &mut rhs);
normalize_scales(&mut lhs, &mut rhs);
let value = lhs.value.add(rhs.value).normalize();
Self {
value,
scale: lhs.scale,
format: lhs.format,
}
}
}
impl Sub for ParsedQuantity {
type Output = Self;
fn sub(self, rhs: Self) -> Self::Output {
let mut lhs = self;
let mut rhs = rhs;
normalize_formats(&mut lhs, &mut rhs);
normalize_scales(&mut lhs, &mut rhs);
let value = lhs.value.sub(rhs.value).normalize();
Self {
value,
scale: lhs.scale,
format: lhs.format,
}
}
}
impl<T> Div<T> for ParsedQuantity
where
T: Into<Decimal>,
{
type Output = Self;
fn div(self, rhs: T) -> Self::Output {
let rhs: Decimal = rhs.into();
Self {
value: self.value / rhs,
scale: self.scale,
format: self.format,
}
}
}
impl<T> Mul<T> for ParsedQuantity
where
T: Into<Decimal>,
{
type Output = Self;
fn mul(self, rhs: T) -> Self::Output {
let rhs: Decimal = rhs.into();
Self {
value: self.value * rhs,
scale: self.scale,
format: self.format,
}
}
}
impl Neg for ParsedQuantity {
type Output = Self;
fn neg(self) -> Self::Output {
Self {
value: self.value.neg(),
scale: self.scale,
format: self.format,
}
}
}
impl AddAssign for ParsedQuantity {
fn add_assign(&mut self, rhs: Self) {
let mut rhs = rhs;
normalize_formats(self, &mut rhs);
normalize_scales(self, &mut rhs);
self.value.add_assign(rhs.value);
}
}
impl SubAssign for ParsedQuantity {
fn sub_assign(&mut self, rhs: Self) {
let mut rhs = rhs;
normalize_formats(self, &mut rhs);
normalize_scales(self, &mut rhs);
self.value.sub_assign(rhs.value);
}
}
impl<T> MulAssign<T> for ParsedQuantity
where
T: Into<Decimal>,
{
fn mul_assign(&mut self, rhs: T) {
let rhs: Decimal = rhs.into();
self.value.mul_assign(rhs);
}
}
impl<T> DivAssign<T> for ParsedQuantity
where
T: Into<Decimal>,
{
fn div_assign(&mut self, rhs: T) {
let rhs: Decimal = rhs.into();
self.value.div_assign(rhs);
}
}
impl PartialEq for ParsedQuantity {
fn eq(&self, other: &Self) -> bool {
let mut lhs = self.clone();
let mut rhs = other.clone();
normalize_formats(&mut lhs, &mut rhs);
normalize_scales(&mut lhs, &mut rhs);
lhs.value.eq(&rhs.value)
}
}
impl Eq for ParsedQuantity {}
impl PartialOrd for ParsedQuantity {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for ParsedQuantity {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
let mut lhs = self.clone();
let mut rhs = other.clone();
normalize_formats(&mut lhs, &mut rhs);
normalize_scales(&mut lhs, &mut rhs);
lhs.value.cmp(&rhs.value)
}
}
impl ParsedQuantity {
pub fn to_string_with_precision(&self, precision: u32) -> String {
format!(
"{}{}",
self.value
.round_dp_with_strategy(precision, RoundingStrategy::MidpointAwayFromZero)
.normalize(),
scale_format_to_string(&self.scale, &self.format)
)
}
pub fn to_bytes_f64(&self) -> Option<f64> {
let scale: i32 = (&self.scale).into();
self.value.to_f64().map(|value| {
value
* match &self.format {
Format::BinarySI => 1024_f64.powi(scale),
Format::DecimalSI => 1000_f64.powi(scale),
}
})
}
pub fn to_bytes_f32(&self) -> Option<f32> {
let scale: i32 = (&self.scale).into();
self.value.to_f32().map(|value| {
value
* match &self.format {
Format::BinarySI => 1024_f32.powi(scale),
Format::DecimalSI => 1000_f32.powi(scale),
}
})
}
pub fn to_bytes_i128(&self) -> Option<i128> {
let scale: i32 = (&self.scale).into();
let scale: u32 = scale.try_into().ok()?;
self.value.to_i128().map(|value| {
value
* match &self.format {
Format::BinarySI => 1024_i128.pow(scale),
Format::DecimalSI => 1000_i128.pow(scale),
}
})
}
pub fn to_bytes_i64(&self) -> Option<i64> {
let scale: i32 = (&self.scale).into();
let scale: u32 = scale.try_into().ok()?;
self.value.to_i64().map(|value| {
value
* match &self.format {
Format::BinarySI => 1024_i64.pow(scale),
Format::DecimalSI => 1000_i64.pow(scale),
}
})
}
pub fn to_bytes_i32(&self) -> Option<i32> {
let scale: i32 = (&self.scale).into();
let scale: u32 = scale.try_into().ok()?;
self.value.to_i32().map(|value| {
value
* match &self.format {
Format::BinarySI => 1024_i32.pow(scale),
Format::DecimalSI => 1000_i32.pow(scale),
}
})
}
pub fn to_bytes_i16(&self) -> Option<i16> {
let scale: i32 = (&self.scale).into();
let scale: u32 = scale.try_into().ok()?;
self.value.to_i16().map(|value| {
value
* match &self.format {
Format::BinarySI => 1024_i16.pow(scale),
Format::DecimalSI => 1000_i16.pow(scale),
}
})
}
pub fn to_bytes_i8(&self) -> Option<i8> {
let scale: i32 = (&self.scale).into();
if scale != 0 {
return None;
}
self.value.to_i8()
}
pub fn to_bytes_isize(&self) -> Option<isize> {
let scale: i32 = (&self.scale).into();
let scale: u32 = scale.try_into().ok()?;
self.value.to_isize().map(|value| {
value
* match &self.format {
Format::BinarySI => 1024_isize.pow(scale),
Format::DecimalSI => 1000_isize.pow(scale),
}
})
}
pub fn to_bytes_u128(&self) -> Option<u128> {
let scale: i32 = (&self.scale).into();
let scale: u32 = scale.try_into().ok()?;
self.value.to_u128().map(|value| {
value
* match &self.format {
Format::BinarySI => 1024_u128.pow(scale),
Format::DecimalSI => 1000_u128.pow(scale),
}
})
}
pub fn to_bytes_u64(&self) -> Option<u64> {
let scale: i32 = (&self.scale).into();
let scale: u32 = scale.try_into().ok()?;
self.value.to_u64().map(|value| {
value
* match &self.format {
Format::BinarySI => 1024_u64.pow(scale),
Format::DecimalSI => 1000_u64.pow(scale),
}
})
}
pub fn to_bytes_u32(&self) -> Option<u32> {
let scale: i32 = (&self.scale).into();
let scale: u32 = scale.try_into().ok()?;
self.value.to_u32().map(|value| {
value
* match &self.format {
Format::BinarySI => 1024_u32.pow(scale),
Format::DecimalSI => 1000_u32.pow(scale),
}
})
}
pub fn to_bytes_u16(&self) -> Option<u16> {
let scale: i32 = (&self.scale).into();
let scale: u32 = scale.try_into().ok()?;
self.value.to_u16().map(|value| {
value
* match &self.format {
Format::BinarySI => 1024_u16.pow(scale),
Format::DecimalSI => 1000_u16.pow(scale),
}
})
}
pub fn to_bytes_u8(&self) -> Option<u8> {
let scale: i32 = (&self.scale).into();
if scale != 0 {
return None;
}
self.value.to_u8()
}
pub fn to_bytes_usize(&self) -> Option<usize> {
let scale: i32 = (&self.scale).into();
let scale: u32 = scale.try_into().ok()?;
self.value.to_usize().map(|value| {
value
* match &self.format {
Format::BinarySI => 1024_usize.pow(scale),
Format::DecimalSI => 1000_usize.pow(scale),
}
})
}
}
fn normalize_scales(lhs: &mut ParsedQuantity, rhs: &mut ParsedQuantity) {
let rhs_scale: i32 = (&rhs.scale).into();
let lhs_scale: i32 = (&lhs.scale).into();
let multiplier = rhs_scale.abs_diff(lhs_scale).to_i32().unwrap_or_default();
match lhs_scale.cmp(&rhs_scale) {
std::cmp::Ordering::Less => {
rhs.value *= Decimal::from_f32(match &rhs.format {
Format::BinarySI => 1024_f32.powi(multiplier),
Format::DecimalSI => 1000_f32.powi(multiplier),
})
.unwrap_or_default();
rhs.scale = lhs.scale.clone();
}
std::cmp::Ordering::Equal => {
}
std::cmp::Ordering::Greater => {
lhs.value *= Decimal::from_f32(match &lhs.format {
Format::BinarySI => 1024_f32.powi(multiplier),
Format::DecimalSI => 1000_f32.powi(multiplier),
})
.unwrap_or_default();
lhs.scale = rhs.scale.clone();
}
}
}
fn normalize_formats(lhs: &mut ParsedQuantity, rhs: &mut ParsedQuantity) {
match (&lhs.format, &rhs.format) {
(Format::BinarySI, Format::BinarySI) => {}
(Format::BinarySI, Format::DecimalSI) => {
let value = rhs
.value
.mul(
Decimal::from_f32((1000_f32 / 1024_f32).powi(rhs.scale.clone().into()))
.unwrap_or_default()
.normalize(),
)
.normalize();
rhs.value = value;
rhs.format = Format::BinarySI;
}
(Format::DecimalSI, Format::BinarySI) => {
let value = rhs
.value
.mul(
Decimal::from_f32((1024_f32 / 1000_f32).powi(rhs.scale.clone().into()))
.unwrap_or_default()
.normalize(),
)
.normalize();
rhs.value = value;
rhs.format = Format::DecimalSI;
}
(Format::DecimalSI, Format::DecimalSI) => {}
};
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_partial_eq() {
let q1 = ParsedQuantity {
value: Decimal::from_f32(1.0).unwrap(),
scale: Scale::Kilo,
format: Format::BinarySI,
};
let q2 = ParsedQuantity {
value: Decimal::from_f32(1.0).unwrap(),
scale: Scale::Kilo,
format: Format::BinarySI,
};
assert_eq!(q1, q2);
}
#[test]
fn test_partial_ne() {
let q1 = ParsedQuantity {
value: Decimal::from_f32(1.0).unwrap(),
scale: Scale::Kilo,
format: Format::BinarySI,
};
let q2 = ParsedQuantity {
value: Decimal::from_f32(1.0).unwrap(),
scale: Scale::Mega,
format: Format::DecimalSI,
};
assert_ne!(q1, q2);
}
#[test]
fn test_ord_le() {
let q1 = ParsedQuantity {
value: Decimal::from_f32(1.0).unwrap(),
scale: Scale::Kilo,
format: Format::BinarySI,
};
let q2 = ParsedQuantity {
value: Decimal::from_f32(1.0).unwrap(),
scale: Scale::Mega,
format: Format::DecimalSI,
};
assert!(q1 < q2);
}
#[test]
fn test_ord_leq() {
let q1 = ParsedQuantity {
value: Decimal::from_f32(1.0).unwrap(),
scale: Scale::Kilo,
format: Format::DecimalSI,
};
let q2 = ParsedQuantity {
value: Decimal::from_f32(1.0).unwrap(),
scale: Scale::Kilo,
format: Format::DecimalSI,
};
assert!(q1 <= q2);
}
#[test]
fn test_ord_le_different_formats() {
let q1 = ParsedQuantity {
value: Decimal::from_f32(1.0).unwrap(),
scale: Scale::One,
format: Format::BinarySI,
};
let q2 = ParsedQuantity {
value: Decimal::from_f32(1.0).unwrap(),
scale: Scale::One,
format: Format::DecimalSI,
};
assert!(q1 <= q2);
assert_eq!(q1, q2);
}
#[test]
fn test_eq_different_formats_and_scales() {
let q1 = ParsedQuantity {
value: Decimal::from_f32(1.0).unwrap(),
scale: Scale::Kilo,
format: Format::BinarySI,
};
let q2 = ParsedQuantity {
value: Decimal::from_f32(1024.0).unwrap(),
scale: Scale::One,
format: Format::DecimalSI,
};
assert_eq!(q1, q2);
}
#[test]
fn test_ord_gt() {
let q1 = ParsedQuantity {
value: Decimal::from_f32(1.0).unwrap(),
scale: Scale::Kilo,
format: Format::BinarySI,
};
let q2 = ParsedQuantity {
value: Decimal::from_f32(1020.0).unwrap(),
scale: Scale::One,
format: Format::DecimalSI,
};
assert!(q1 > q2);
}
#[test]
fn test_default_parsed_quantity() {
let quantity = ParsedQuantity::default();
assert_eq!(quantity.value, Decimal::from_f32(0.0).unwrap());
assert_eq!(quantity.scale, Scale::One);
assert_eq!(quantity.format, Format::BinarySI);
}
}