use crate::models::FiniteF64;
use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
use std::fmt;
use std::iter::Sum;
use std::ops::Add;
#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(transparent)]
pub struct RequestCharge(FiniteF64);
impl RequestCharge {
pub fn new(value: f64) -> Self {
Self(FiniteF64::new_lossy(value))
}
pub const fn value(self) -> f64 {
self.0.value()
}
}
impl fmt::Display for RequestCharge {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.value())
}
}
impl PartialOrd for RequestCharge {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for RequestCharge {
fn cmp(&self, other: &Self) -> Ordering {
self.value().total_cmp(&other.value())
}
}
impl Add for RequestCharge {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
Self::new(self.value() + rhs.value())
}
}
impl Sum for RequestCharge {
fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
iter.fold(Self::default(), |acc, x| acc + x)
}
}
impl From<f64> for RequestCharge {
fn from(value: f64) -> Self {
Self::new(value)
}
}
impl From<RequestCharge> for f64 {
fn from(charge: RequestCharge) -> Self {
charge.value()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_is_zero() {
let charge = RequestCharge::default();
assert_eq!(charge.value(), 0.0);
}
#[test]
fn new_and_value() {
let charge = RequestCharge::new(3.5);
assert_eq!(charge.value(), 3.5);
}
#[test]
fn add() {
let a = RequestCharge::new(1.5);
let b = RequestCharge::new(2.5);
assert_eq!((a + b).value(), 4.0);
}
#[test]
fn sum_iterator() {
let charges = vec![
RequestCharge::new(1.0),
RequestCharge::new(2.0),
RequestCharge::new(3.0),
];
let total: RequestCharge = charges.into_iter().sum();
assert_eq!(total.value(), 6.0);
}
#[test]
fn sum_empty_iterator() {
let charges: Vec<RequestCharge> = vec![];
let total: RequestCharge = charges.into_iter().sum();
assert_eq!(total.value(), 0.0);
}
#[test]
fn display() {
let charge = RequestCharge::new(5.5);
assert_eq!(format!("{}", charge), "5.5");
}
#[test]
fn from_f64() {
let charge: RequestCharge = 3.5.into();
assert_eq!(charge.value(), 3.5);
}
#[test]
fn into_f64() {
let charge = RequestCharge::new(3.5);
let val: f64 = charge.into();
assert_eq!(val, 3.5);
}
#[test]
fn partial_ord() {
let a = RequestCharge::new(1.0);
let b = RequestCharge::new(2.0);
assert!(a < b);
assert!(b > a);
}
#[test]
fn serialization() {
let charge = RequestCharge::new(3.5);
let json = serde_json::to_string(&charge).unwrap();
assert_eq!(json, "3.5");
}
#[test]
fn deserialization() {
let charge: RequestCharge = serde_json::from_str("3.5").unwrap();
assert_eq!(charge.value(), 3.5);
}
#[test]
fn copy_semantics() {
let a = RequestCharge::new(1.0);
let b = a;
assert_eq!(a.value(), b.value()); }
#[test]
fn nan_normalized_to_zero() {
let charge = RequestCharge::new(f64::NAN);
assert_eq!(charge.value(), 0.0);
}
#[test]
fn negative_zero_normalized() {
let charge = RequestCharge::new(-0.0);
assert_eq!(charge, RequestCharge::new(0.0));
assert_eq!(charge.value().to_bits(), 0.0_f64.to_bits());
}
#[test]
fn eq_and_hash_consistent() {
use std::collections::HashSet;
let a = RequestCharge::new(3.5);
let b = RequestCharge::new(3.5);
assert_eq!(a, b);
let mut set = HashSet::new();
set.insert(a);
assert!(set.contains(&b));
}
#[test]
fn nan_from_f64() {
let charge: RequestCharge = f64::NAN.into();
assert_eq!(charge.value(), 0.0);
}
#[test]
fn nan_add_normalized() {
let a = RequestCharge::new(f64::NAN);
let b = RequestCharge::new(1.0);
let result = a + b;
assert_eq!(result.value(), 1.0);
}
#[test]
fn ord() {
let a = RequestCharge::new(1.0);
let b = RequestCharge::new(2.0);
let c = RequestCharge::new(2.0);
assert!(a < b);
assert_eq!(b, c);
assert_eq!(b.cmp(&c), std::cmp::Ordering::Equal);
}
}