use std::cmp::Ordering;
use std::fmt;
static POW10_I64: [i64; 20] = [
1,
10,
100,
1_000,
10_000,
100_000,
1_000_000,
10_000_000,
100_000_000,
1_000_000_000,
10_000_000_000,
100_000_000_000,
1_000_000_000_000,
10_000_000_000_000,
100_000_000_000_000,
1_000_000_000_000_000,
10_000_000_000_000_000,
100_000_000_000_000_000,
1_000_000_000_000_000_000,
i64::MAX,
];
static POW10_F64: [f64; 20] = [
1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16,
1e17, 1e18, 1e19,
];
#[derive(Clone, Copy, Default)]
pub struct Price {
pub value: i32,
pub price_type: i32,
}
impl Price {
pub const ZERO: Self = Self {
value: 0,
price_type: 0,
};
#[inline]
#[must_use]
pub fn new(value: i32, price_type: i32) -> Self {
Self {
value,
price_type: price_type.clamp(0, 19),
}
}
#[must_use]
pub fn is_zero(&self) -> bool {
self.value == 0 || self.price_type == 0
}
#[allow(clippy::cast_sign_loss)]
#[inline]
#[must_use]
pub fn to_f64(&self) -> f64 {
if self.price_type == 0 {
return 0.0;
}
let exp = self.price_type - 10;
if exp >= 0 {
f64::from(self.value) * POW10_F64[exp as usize]
} else {
f64::from(self.value) / POW10_F64[(-exp) as usize]
}
}
#[allow(clippy::cast_sign_loss, clippy::trivially_copy_pass_by_ref)]
#[inline]
fn compare(&self, other: &Self) -> Ordering {
if self.price_type == other.price_type {
return self.value.cmp(&other.value);
}
if self.price_type > other.price_type {
let exp = (self.price_type - other.price_type) as usize;
if exp > 18 {
return self.to_f64().total_cmp(&other.to_f64());
}
let scaled = i64::from(self.value).checked_mul(POW10_I64[exp]);
match scaled {
Some(s) => s.cmp(&i64::from(other.value)),
None => self.to_f64().total_cmp(&other.to_f64()),
}
} else {
let exp = (other.price_type - self.price_type) as usize;
if exp > 18 {
return self.to_f64().total_cmp(&other.to_f64());
}
let scaled = i64::from(other.value).checked_mul(POW10_I64[exp]);
match scaled {
Some(s) => i64::from(self.value).cmp(&s),
None => self.to_f64().total_cmp(&other.to_f64()),
}
}
}
}
impl PartialEq for Price {
fn eq(&self, other: &Self) -> bool {
self.compare(other) == Ordering::Equal
}
}
impl Eq for Price {}
impl PartialOrd for Price {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Price {
fn cmp(&self, other: &Self) -> Ordering {
self.compare(other)
}
}
impl fmt::Debug for Price {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Price({self})")
}
}
impl fmt::Display for Price {
#[allow(clippy::cast_sign_loss)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.price_type == 0 {
return write!(f, "0.0");
}
if self.price_type == 10 {
return write!(f, "{}.0", self.value);
}
if self.price_type > 10 {
let zeros = "0".repeat((self.price_type - 10) as usize);
return write!(f, "{}{}.0", self.value, zeros);
}
let is_neg = self.value < 0;
let abs_str = if is_neg {
format!("{}", i64::from(-self.value))
} else {
format!("{}", self.value)
};
let frac_digits = (10 - self.price_type) as usize;
let padded = if abs_str.len() <= frac_digits {
let pad = "0".repeat(frac_digits - abs_str.len() + 1);
format!("{pad}{abs_str}")
} else {
abs_str
};
let split = padded.len() - frac_digits;
let result = format!("{}.{}", &padded[..split], &padded[split..]);
if is_neg {
write!(f, "-{result}")
} else {
write!(f, "{result}")
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_price_display() {
assert_eq!(Price::new(0, 0).to_string(), "0.0");
assert_eq!(Price::new(15025, 8).to_string(), "150.25");
assert_eq!(Price::new(100, 10).to_string(), "100.0");
assert_eq!(Price::new(5, 12).to_string(), "500.0");
assert_eq!(Price::new(-15025, 8).to_string(), "-150.25");
assert_eq!(Price::new(5, 7).to_string(), "0.005");
}
#[test]
fn test_price_to_f64() {
let p = Price::new(15025, 8);
assert!((p.to_f64() - 150.25).abs() < 1e-10);
}
#[test]
fn test_price_comparison() {
let a = Price::new(15025, 8); let b = Price::new(15000, 8); let c = Price::new(1502500, 6); assert!(a > b);
assert_eq!(a, c);
}
}