use crate::chains::chain::OptionChain;
use crate::constants::EPSILON;
use crate::model::utils::ToRound;
use crate::series::OptionSeries;
use approx::{AbsDiffEq, RelativeEq};
use num_traits::{FromPrimitive, Pow, ToPrimitive};
use rust_decimal::{Decimal, MathematicalOps};
use rust_decimal_macros::dec;
use serde::de::Visitor;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::cmp::{Ordering, PartialEq};
use std::fmt;
use std::fmt::Display;
use std::iter::Sum;
use std::ops::{Add, AddAssign, Div, Mul, MulAssign, Neg, Sub};
use std::str::FromStr;
#[derive(PartialEq, Clone, Copy, Hash)]
pub struct Positive(pub(crate) Decimal);
#[macro_export]
macro_rules! pos {
($val:expr) => {
$crate::Positive::new($val).unwrap()
};
}
#[macro_export]
macro_rules! spos {
($val:expr) => {
Some($crate::Positive::new($val).unwrap())
};
}
pub fn is_positive<T: 'static>() -> bool {
std::any::TypeId::of::<T>() == std::any::TypeId::of::<Positive>()
}
impl Positive {
pub const ZERO: Positive = Positive(Decimal::ZERO);
pub const ONE: Positive = Positive(Decimal::ONE);
pub const TWO: Positive = Positive(Decimal::TWO);
pub const INFINITY: Positive = Positive(Decimal::MAX);
pub const TEN: Positive = Positive(Decimal::TEN);
pub const HUNDRED: Positive = Positive(Decimal::ONE_HUNDRED);
pub const THOUSAND: Positive = Positive(Decimal::ONE_THOUSAND);
pub const PI: Positive = Positive(Decimal::PI);
pub fn new(value: f64) -> Result<Self, String> {
let dec = Decimal::from_f64(value);
match dec {
Some(value) if value >= Decimal::ZERO => Ok(Positive(value)),
Some(value) => Err(format!("Value must be positive, got {value}")),
None => Err("Failed to parse as Decimal".to_string()),
}
}
pub fn new_decimal(value: Decimal) -> Result<Self, String> {
if value >= Decimal::ZERO {
Ok(Positive(value))
} else {
Err(format!("Value must be positive, got {value}"))
}
}
pub fn value(&self) -> Decimal {
self.0
}
pub fn to_dec(&self) -> Decimal {
self.0
}
pub fn to_dec_ref(&self) -> &Decimal {
&self.0
}
pub fn to_dec_ref_mut(&mut self) -> &mut Decimal {
&mut self.0
}
pub fn to_f64(&self) -> f64 {
self.0.to_f64().unwrap()
}
pub fn to_i64(&self) -> i64 {
self.0.to_i64().unwrap()
}
pub fn to_u64(&self) -> u64 {
self.0.to_u64().unwrap()
}
pub fn to_usize(&self) -> usize {
self.0.to_usize().unwrap()
}
pub fn max(self, other: Positive) -> Positive {
if self.0 > other.0 { self } else { other }
}
pub fn min(self, other: Positive) -> Positive {
if self.0 < other.0 { self } else { other }
}
pub fn floor(&self) -> Positive {
Positive(self.0.floor())
}
pub fn powi(&self, n: i64) -> Positive {
Positive(self.0.powi(n))
}
pub fn pow(&self, n: Positive) -> Positive {
Positive(self.0.pow(n.to_dec()))
}
pub fn powu(&self, n: u64) -> Positive {
Positive(self.0.powu(n))
}
pub fn powd(&self, p0: Decimal) -> Positive {
Positive(self.0.powd(p0))
}
pub fn round(&self) -> Positive {
Positive(self.0.round())
}
pub fn round_to_nice_number(&self) -> Positive {
let magnitude = self.log10().floor();
let ten_pow = Positive::TEN.pow(magnitude);
let normalized = self / &ten_pow;
let nice_number = if normalized < dec!(1.5) {
Positive::ONE
} else if normalized < pos!(3.0) {
Positive::TWO
} else if normalized < pos!(7.0) {
pos!(5.0)
} else {
Positive::TEN
};
nice_number * pos!(10.0).powu(magnitude.to_u64())
}
pub fn sqrt(&self) -> Positive {
Positive(self.0.sqrt().unwrap())
}
pub fn ln(&self) -> Positive {
Positive(self.0.ln())
}
pub fn round_to(&self, decimal_places: u32) -> Positive {
Positive(self.0.round_dp(decimal_places))
}
pub fn format_fixed_places(&self, decimal_places: u32) -> String {
let rounded = self.round_to(decimal_places).to_f64();
format!("{:.1$}", rounded, decimal_places as usize)
}
pub fn exp(&self) -> Positive {
Positive(self.0.exp())
}
pub fn clamp(&self, min: Positive, max: Positive) -> Positive {
if self < &min {
min
} else if self > &max {
max
} else {
*self
}
}
pub fn is_zero(&self) -> bool {
self.0.is_zero()
}
pub fn ceiling(&self) -> Positive {
let value = self.to_dec();
let ceiling_value = value.ceil();
Positive::from(ceiling_value)
}
pub fn log10(&self) -> Positive {
Positive(self.0.log10())
}
pub fn sub_or_zero(&self, other: &Decimal) -> Positive {
if &self.0 > other {
Positive(self.0 - other)
} else {
Positive(Decimal::ZERO)
}
}
pub fn sub_or_none(&self, other: &Decimal) -> Option<Positive> {
if &self.0 >= other {
Some(Positive(self.0 - other))
} else {
None
}
}
pub fn is_multiple(&self, other: f64) -> bool {
let value = self.to_f64();
if !value.is_finite() {
return false;
}
let remainder = value % other;
remainder.abs() < f64::EPSILON || (other - remainder.abs()).abs() < f64::EPSILON
}
}
impl ToRound for Positive {
fn round(&self) -> Decimal {
self.round().to_dec()
}
fn round_to(&self, decimal_places: u32) -> Decimal {
self.round_to(decimal_places).to_dec()
}
}
impl PartialEq<&Positive> for Positive {
fn eq(&self, other: &&Positive) -> bool {
self == *other
}
}
impl From<Positive> for u64 {
fn from(pos_u64: Positive) -> Self {
pos_u64.0.to_u64().unwrap()
}
}
impl From<&Positive> for f64 {
fn from(value: &Positive) -> Self {
value.0.to_f64().unwrap_or(0.0)
}
}
impl From<Positive> for f64 {
fn from(value: Positive) -> Self {
value.0.to_f64().unwrap_or(0.0)
}
}
impl From<Positive> for usize {
fn from(value: Positive) -> Self {
value.0.to_f64().unwrap_or(0.0) as usize
}
}
impl PartialEq<&Positive> for f64 {
fn eq(&self, other: &&Positive) -> bool {
self == &other.0.to_f64().unwrap_or(0.0)
}
}
impl PartialOrd<&Positive> for f64 {
fn partial_cmp(&self, other: &&Positive) -> Option<std::cmp::Ordering> {
self.partial_cmp(&other.0.to_f64().unwrap_or(0.0))
}
}
impl PartialEq<Positive> for f64 {
fn eq(&self, other: &Positive) -> bool {
self == &other.0.to_f64().unwrap_or(0.0)
}
}
impl PartialOrd<Positive> for f64 {
fn partial_cmp(&self, other: &Positive) -> Option<std::cmp::Ordering> {
self.partial_cmp(&other.0.to_f64().unwrap_or(0.0))
}
}
impl Mul<Positive> for f64 {
type Output = f64;
fn mul(self, rhs: Positive) -> Self::Output {
self * rhs.to_f64()
}
}
impl Div<Positive> for f64 {
type Output = f64;
fn div(self, rhs: Positive) -> Self::Output {
self / rhs.to_f64()
}
}
impl Sub<Positive> for f64 {
type Output = f64;
fn sub(self, rhs: Positive) -> Self::Output {
self - rhs.to_f64()
}
}
impl Add<Positive> for f64 {
type Output = f64;
fn add(self, rhs: Positive) -> Self::Output {
self + rhs.to_f64()
}
}
impl FromStr for Positive {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.parse::<Decimal>() {
Ok(value) if value > Decimal::ZERO => Ok(Positive(value)),
Ok(value) => Err(format!("Value must be positive, got {value}")),
Err(e) => Err(format!("Failed to parse as Decimal: {e}")),
}
}
}
impl From<f64> for Positive {
fn from(value: f64) -> Self {
Positive::new(value).expect("Value must be positive")
}
}
impl From<usize> for Positive {
fn from(value: usize) -> Self {
Positive::new(value as f64).expect("Value must be positive")
}
}
impl From<Decimal> for Positive {
fn from(value: Decimal) -> Self {
Positive::new_decimal(value).expect("Value must be positive")
}
}
impl From<&Decimal> for Positive {
fn from(value: &Decimal) -> Self {
Positive::new_decimal(*value).expect("Value must be positive")
}
}
impl From<&Positive> for Positive {
fn from(value: &Positive) -> Self {
Positive(value.0)
}
}
impl From<&OptionChain> for Positive {
fn from(value: &OptionChain) -> Self {
value.underlying_price
}
}
impl From<OptionChain> for Positive {
fn from(value: OptionChain) -> Self {
value.underlying_price
}
}
impl From<&OptionSeries> for Positive {
fn from(value: &OptionSeries) -> Self {
value.underlying_price
}
}
impl From<OptionSeries> for Positive {
fn from(value: OptionSeries) -> Self {
value.underlying_price
}
}
impl Mul<f64> for Positive {
type Output = Positive;
fn mul(self, rhs: f64) -> Positive {
(self.to_f64() * rhs).into()
}
}
impl Div<f64> for Positive {
type Output = Positive;
fn div(self, rhs: f64) -> Positive {
(self.to_f64() / rhs).into()
}
}
impl Div<f64> for &Positive {
type Output = Positive;
fn div(self, rhs: f64) -> Positive {
(self.to_f64() / rhs).into()
}
}
impl Sub<f64> for Positive {
type Output = Positive;
fn sub(self, rhs: f64) -> Self::Output {
(self.to_f64() - rhs).into()
}
}
impl Add<f64> for Positive {
type Output = Positive;
fn add(self, rhs: f64) -> Self::Output {
(self.to_f64() + rhs).into()
}
}
impl PartialOrd<f64> for Positive {
fn partial_cmp(&self, other: &f64) -> Option<Ordering> {
self.0.to_f64().unwrap_or(0.0).partial_cmp(other)
}
}
impl PartialEq<f64> for &Positive {
fn eq(&self, other: &f64) -> bool {
self.0.to_f64().unwrap_or(0.0) == *other
}
}
impl PartialOrd<f64> for &Positive {
fn partial_cmp(&self, other: &f64) -> Option<Ordering> {
self.0.to_f64().unwrap_or(0.0).partial_cmp(other)
}
}
impl PartialEq<f64> for Positive {
fn eq(&self, other: &f64) -> bool {
self.to_f64() == *other
}
}
impl Display for Positive {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if *self == Positive::INFINITY {
write!(f, r#""infinity""#)
} else if self.0.scale() == 0 {
match self.0.to_i64() {
Some(val) => write!(f, "{val}"),
None => write!(f, "{}", self.0),
}
} else if let Some(precision) = f.precision() {
write!(f, "{:.1$}", self.0, precision)
} else {
let s = self.0.to_string();
let trimmed = s.trim_end_matches('0').trim_end_matches('.');
write!(f, "{trimmed}")
}
}
}
impl fmt::Debug for Positive {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if *self == Positive::INFINITY {
write!(f, r#""infinity""#)
} else if self.0.scale() == 0 {
write!(f, "{}", self.0.to_i64().unwrap())
} else {
write!(f, "{}", self.0)
}
}
}
impl PartialEq<Decimal> for Positive {
fn eq(&self, other: &Decimal) -> bool {
(self.0 - *other).abs() <= EPSILON * Decimal::from(100)
}
}
impl Serialize for Positive {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let value = self.0;
if *self == Positive::INFINITY {
return serializer.serialize_str("infinity");
}
if value.scale() == 0 {
serializer.serialize_i64(
value
.to_i64()
.ok_or_else(|| serde::ser::Error::custom("Failed to convert Decimal to i64"))?,
)
} else {
serializer.serialize_f64(
value
.to_f64()
.ok_or_else(|| serde::ser::Error::custom("Failed to convert Decimal to f64"))?,
)
}
}
}
impl<'de> Deserialize<'de> for Positive {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct PositiveVisitor;
impl Visitor<'_> for PositiveVisitor {
type Value = Positive;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a positive number or the string \"infinity\"")
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
if value.eq_ignore_ascii_case("infinity") {
return Ok(Positive::INFINITY);
}
Err(serde::de::Error::custom(format!(
"Invalid string: '{value}'. Expected \"infinity\"."
)))
}
fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
if value < 0 {
Err(serde::de::Error::custom("Expected a non-negative integer"))
} else {
Positive::new_decimal(Decimal::from(value)).map_err(serde::de::Error::custom)
}
}
fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Positive::new_decimal(Decimal::from(value)).map_err(serde::de::Error::custom)
}
fn visit_f64<E>(self, value: f64) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
if value.is_infinite() && value.is_sign_positive() {
return Ok(Positive::INFINITY);
}
let decimal = Decimal::from_f64(value)
.ok_or_else(|| serde::de::Error::custom("Failed to convert f64 to Decimal"))?;
if value < 0.0 {
Err(serde::de::Error::custom("Expected a non-negative float"))
} else {
Positive::new_decimal(decimal).map_err(serde::de::Error::custom)
}
}
}
deserializer.deserialize_any(PositiveVisitor)
}
}
impl Add for Positive {
type Output = Positive;
fn add(self, other: Positive) -> Positive {
Positive(self.0 + other.0)
}
}
impl Sub for Positive {
type Output = Positive;
fn sub(self, rhs: Self) -> Self::Output {
let result = self.0 - rhs.0;
if result < Decimal::ZERO {
panic!("Resulting value must be positive");
} else {
Positive(result)
}
}
}
impl Div for Positive {
type Output = Positive;
fn div(self, other: Positive) -> Self::Output {
Positive(self.0 / other.0)
}
}
impl Div for &Positive {
type Output = Positive;
fn div(self, other: &Positive) -> Self::Output {
Positive(self.0 / other.0)
}
}
impl Add<Decimal> for Positive {
type Output = Positive;
fn add(self, rhs: Decimal) -> Positive {
Positive(self.0 + rhs)
}
}
impl Add<&Decimal> for Positive {
type Output = Positive;
fn add(self, rhs: &Decimal) -> Self::Output {
(self.0 + rhs).into()
}
}
impl Sub<Decimal> for Positive {
type Output = Positive;
fn sub(self, rhs: Decimal) -> Positive {
Positive::new_decimal(self.0 - rhs).expect("Resulting value must be positive")
}
}
impl Sub<&Decimal> for Positive {
type Output = Positive;
fn sub(self, rhs: &Decimal) -> Self::Output {
Positive::new_decimal(self.0 - rhs).expect("Resulting value must be positive")
}
}
impl AddAssign for Positive {
fn add_assign(&mut self, other: Positive) {
self.0 += other.0;
}
}
impl AddAssign<Decimal> for Positive {
fn add_assign(&mut self, rhs: Decimal) {
self.0 += rhs;
}
}
impl MulAssign<Decimal> for Positive {
fn mul_assign(&mut self, rhs: Decimal) {
self.0 *= rhs;
}
}
impl Div<Decimal> for Positive {
type Output = Positive;
fn div(self, rhs: Decimal) -> Positive {
Positive(self.0 / rhs)
}
}
impl Div<&Decimal> for Positive {
type Output = Positive;
fn div(self, rhs: &Decimal) -> Self::Output {
(self.0 / rhs).into()
}
}
impl PartialOrd<Decimal> for Positive {
fn partial_cmp(&self, other: &Decimal) -> Option<std::cmp::Ordering> {
self.0.partial_cmp(other)
}
}
impl PartialOrd for Positive {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
fn le(&self, other: &Self) -> bool {
self.0 <= other.0
}
fn ge(&self, other: &Self) -> bool {
self.0 >= other.0
}
}
impl Eq for Positive {}
impl Ord for Positive {
fn cmp(&self, other: &Self) -> Ordering {
self.0.partial_cmp(&other.0).unwrap()
}
}
impl Neg for Positive {
type Output = Self;
fn neg(self) -> Self::Output {
panic!("Cannot negate a Positive value!");
}
}
impl Mul for Positive {
type Output = Positive;
fn mul(self, other: Positive) -> Positive {
Positive(self.0 * other.0)
}
}
impl Mul<Decimal> for Positive {
type Output = Positive;
fn mul(self, rhs: Decimal) -> Positive {
Positive(self.0 * rhs)
}
}
impl Default for Positive {
fn default() -> Self {
Positive::ZERO
}
}
impl AbsDiffEq for Positive {
type Epsilon = Decimal;
fn default_epsilon() -> Self::Epsilon {
EPSILON
}
fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool {
(self.0 - other.0).abs() <= epsilon
}
}
impl RelativeEq for Positive {
fn default_max_relative() -> Self::Epsilon {
EPSILON * Decimal::from(100)
}
fn relative_eq(
&self,
other: &Self,
epsilon: Self::Epsilon,
max_relative: Self::Epsilon,
) -> bool {
let abs_diff = (self.0 - other.0).abs();
let largest = self.0.abs().max(other.0.abs());
abs_diff <= epsilon || abs_diff <= max_relative * largest
}
}
impl Sum for Positive {
fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
let sum = iter.fold(Decimal::ZERO, |acc, x| acc + x.value());
Positive::new_decimal(sum).unwrap_or(Positive::ZERO)
}
}
impl<'a> Sum<&'a Positive> for Positive {
fn sum<I: Iterator<Item = &'a Positive>>(iter: I) -> Self {
let sum = iter.fold(Decimal::ZERO, |acc, x| acc + x.value());
Positive::new_decimal(sum).unwrap_or(Positive::ZERO)
}
}
#[cfg(test)]
mod tests_positive_decimal {
use super::*;
use rust_decimal_macros::dec;
#[test]
fn test_positive_decimal_creation() {
assert!(Positive::new_decimal(Decimal::ZERO).is_ok());
assert!(Positive::new_decimal(Decimal::ONE).is_ok());
assert!(Positive::new_decimal(Decimal::NEGATIVE_ONE).is_err());
}
#[test]
fn test_positive_decimal_value() {
let pos = Positive::new(5.0).unwrap();
assert_eq!(pos, 5.0);
}
#[test]
fn test_positive_decimal_from() {
let pos = Positive::new(3.0).unwrap();
let f: Decimal = pos.into();
assert_eq!(f, dec!(3.0));
}
#[test]
fn test_positive_decimal_eq() {
let pos = Positive::new_decimal(Decimal::TWO).unwrap();
assert_eq!(pos, dec!(2.0));
assert_ne!(pos, dec!(3.0));
}
#[test]
fn test_positive_decimal_display() {
let pos = Positive::new_decimal(dec!(4.5)).unwrap();
assert_eq!(format!("{pos}"), "4.5");
}
#[test]
fn test_positive_decimal_debug() {
let pos = Positive::new_decimal(dec!(4.5)).unwrap();
assert_eq!(format!("{pos:?}"), "4.5");
}
#[test]
fn test_positive_decimal_display_decimal_fix() {
let pos = Positive::new_decimal(dec!(4.578923789423789)).unwrap();
assert_eq!(format!("{pos:.2}"), "4.57");
assert_eq!(format!("{pos:.3}"), "4.578");
assert_eq!(format!("{pos:.0}"), "4");
}
#[test]
fn test_positive_decimal_add() {
let a = Positive::new_decimal(dec!(2.0)).unwrap();
let b = Positive::new_decimal(dec!(3.0)).unwrap();
assert_eq!((a + b).value(), dec!(5.0));
}
#[test]
fn test_positive_decimal_div() {
let a = Positive::new_decimal(dec!(6.0)).unwrap();
let b = Positive::new_decimal(dec!(2.0)).unwrap();
assert_eq!((a / b).value(), dec!(3.0));
}
#[test]
fn test_positive_decimal_div_decimal() {
let a = Positive::new_decimal(dec!(6.0)).unwrap();
assert_eq!((a / 2.0), 3.0);
}
#[test]
fn test_decimal_mul_positive_decimal() {
let a = dec!(2.0);
let b = Positive::new_decimal(dec!(3.0)).unwrap();
assert_eq!(a * b, dec!(6.0));
}
#[test]
fn test_positive_decimal_mul() {
let a = Positive::new_decimal(dec!(2.0)).unwrap();
let b = Positive::new_decimal(dec!(3.0)).unwrap();
assert_eq!((a * b).value(), dec!(6.0));
}
#[test]
fn test_positive_decimal_mul_decimal() {
let a = Positive::new_decimal(dec!(2.0)).unwrap();
assert_eq!((a * 3.0), 6.0);
}
#[test]
fn test_positive_decimal_default() {
assert_eq!(Positive::default().value(), Decimal::ZERO);
}
#[test]
fn test_decimal_div_positive_decimal() {
let a = dec!(6.0);
let b = Positive::new_decimal(dec!(2.0)).unwrap();
assert_eq!(a / b, dec!(3.0));
}
#[test]
fn test_constants() {
assert_eq!(Positive::ZERO.value(), Decimal::ZERO);
assert_eq!(Positive::ONE.value(), Decimal::ONE);
}
}
#[cfg(test)]
mod tests_positive_decimal_extended {
use super::*;
use rust_decimal_macros::dec;
#[test]
fn test_positive_decimal_ordering() {
let a = pos!(1.0);
let b = pos!(2.0);
let c = pos!(2.0);
assert!(a < b);
assert!(b > a);
assert!(b >= c);
assert!(b <= c);
}
#[test]
fn test_positive_decimal_add_assign() {
let mut a = pos!(1.0);
let b = pos!(2.0);
a += b;
assert_eq!(a.value(), dec!(3.0));
}
#[test]
fn test_positive_decimal_mul_assign() {
let mut a = Decimal::TWO;
a *= dec!(3.0);
assert_eq!(a, dec!(6.0));
}
#[test]
fn test_positive_decimal_from_string() {
assert_eq!(Positive::from_str("1.5").unwrap().value(), dec!(1.5));
assert!(Positive::from_str("-1.5").is_err());
assert!(Positive::from_str("invalid").is_err());
}
#[test]
fn test_positive_decimal_max_min() {
let a = pos!(1.0);
let b = pos!(2.0);
assert_eq!(a.max(b).value(), dec!(2.0));
assert_eq!(a.min(b).value(), dec!(1.0));
}
#[test]
fn test_positive_decimal_floor() {
let a = pos!(1.7);
assert_eq!(a.floor().value(), dec!(1.0));
}
#[test]
#[should_panic(expected = "Cannot negate a Positive value!")]
fn test_positive_decimal_neg() {
let a = pos!(1.0);
let _ = -a;
}
}
#[cfg(test)]
mod tests_positive_decimal_sum {
use super::*;
#[test]
fn test_sum_owned_values() {
let values = vec![pos!(1.0), pos!(2.0), pos!(3.0)];
let sum: Positive = values.into_iter().sum();
assert_eq!(sum.to_f64(), 6.0);
}
#[test]
fn test_sum_referenced_values() {
let values = [pos!(1.0), pos!(2.0), pos!(3.0)];
let sum: Positive = values.iter().sum();
assert_eq!(sum.to_f64(), 6.0);
}
#[test]
fn test_sum_empty_iterator() {
let values: Vec<Positive> = vec![];
let sum: Positive = values.into_iter().sum();
assert_eq!(sum.to_f64(), 0.0);
}
}
#[cfg(test)]
mod tests_eq {
use crate::Positive;
use rust_decimal_macros::dec;
#[test]
#[ignore = "This test is failing because of the precision limit"]
fn test_eq() {
let a = pos!(0.5848105371755788);
let b = Positive::new_decimal(dec!(0.5848105371755788)).unwrap();
assert_eq!(a, b);
}
}
#[cfg(test)]
mod tests_macros {
use super::*;
use rust_decimal::Decimal;
#[test]
fn test_pos_positive_values() {
assert_eq!(pos!(5.0).value(), Decimal::new(5, 0));
assert_eq!(pos!(1.5).value(), Decimal::new(15, 1));
assert_eq!(pos!(0.1).value(), Decimal::new(1, 1));
}
#[test]
fn test_pos_zero() {
assert_eq!(Positive::ZERO, Positive::ZERO);
}
#[test]
fn test_pos_small_decimals() {
assert_eq!(pos!(0.0001).value(), Decimal::new(1, 4));
assert_eq!(pos!(0.00001).value(), Decimal::new(1, 5));
assert_eq!(pos!(0.000001).value(), Decimal::new(1, 6));
}
#[test]
fn test_pos_large_decimals() {
let val = 0.1234567890123456;
let expected = Decimal::from_str("0.1234567890123456").unwrap();
assert_eq!(pos!(val).value(), expected);
}
#[test]
fn test_pos_precision_limits() {
let val = ((0.123_456_789_012_345_68_f64 * 1e16) as u64) as f64 / 1e16; let expected = Decimal::from_str("0.1234567890123456").unwrap();
assert_eq!(pos!(val).value(), expected);
}
#[test]
#[should_panic(expected = "Value must be positive, got -1")]
fn test_pos_negative_values() {
assert_eq!(pos!(-1.0), Positive::ZERO);
}
#[test]
fn test_pos_edge_cases() {
assert_eq!(
pos!(1e15).value(),
Decimal::from_str("1000000000000000").unwrap()
);
assert_eq!(
pos!(1e-15).value(),
Decimal::from_str("0.000000000000001").unwrap()
);
}
#[test]
fn test_pos_expressions() {
assert_eq!(pos!(2.0 + 3.0).value(), Decimal::new(5, 0));
assert_eq!(pos!(1.5 * 2.0).value(), Decimal::new(3, 0));
}
#[test]
fn test_pos_conversions() {
assert_eq!(pos!(5.0).value(), Decimal::new(5, 0));
assert_eq!(pos!(5.0).value(), Decimal::new(5, 0));
}
}
#[cfg(test)]
mod tests_serialization {
use super::*;
use rust_decimal_macros::dec; use serde_json;
#[test]
fn test_positive_serialization() {
let value = Positive(dec!(42.5));
let serialized = serde_json::to_string(&value).unwrap();
assert_eq!(serialized, "42.5");
}
#[test]
fn test_positive_deserialization() {
let json = "42.5";
let deserialized: Positive = serde_json::from_str(json).unwrap();
assert_eq!(deserialized, Positive(dec!(42.5)));
}
#[test]
fn test_positive_serialization_whole_number() {
let value = Positive(dec!(100));
let serialized = serde_json::to_string(&value).unwrap();
assert_eq!(serialized, "100");
}
#[test]
fn test_positive_deserialization_whole_number() {
let json = "100";
let deserialized: Positive = serde_json::from_str(json).unwrap();
assert_eq!(deserialized, Positive(dec!(100)));
}
#[test]
fn test_positive_roundtrip() {
let original = Positive(dec!(123.456));
let serialized = serde_json::to_string(&original).unwrap();
let deserialized: Positive = serde_json::from_str(&serialized).unwrap();
assert_eq!(original, deserialized);
}
#[test]
fn test_positive_high_precision() {
let value = Positive(dec!(12345.6789));
let serialized = serde_json::to_string(&value).unwrap();
let deserialized: Positive = serde_json::from_str(&serialized).unwrap();
assert_eq!(value, deserialized);
}
#[test]
fn test_positive_zero_deserialization() {
let json = "0";
let result = serde_json::from_str::<Positive>(json);
assert!(result.is_ok());
assert_eq!(result.unwrap(), Positive::ZERO);
}
#[test]
fn test_positive_negative_deserialization() {
let json = "-42.5";
let result = serde_json::from_str::<Positive>(json);
assert!(result.is_err());
}
#[test]
fn test_positive_infinity_serialization() {
let value = Positive::INFINITY;
let serialized = serde_json::to_string(&value).unwrap();
assert_eq!(serialized, r#""infinity""#);
}
#[test]
fn test_positive_infinity_deserialization() {
let json = r#""infinity""#;
let result = serde_json::from_str::<Positive>(json);
assert!(result.is_ok());
}
}
#[cfg(test)]
mod tests_format_fixed_places {
use crate::pos;
#[test]
fn test_format_fixed_places() {
let value = pos!(10.5);
assert_eq!(value.format_fixed_places(2), "10.50");
let value = pos!(10.0);
assert_eq!(value.format_fixed_places(3), "10.000");
let value = pos!(10.567);
assert_eq!(value.format_fixed_places(2), "10.57");
let value = pos!(0.1);
assert_eq!(value.format_fixed_places(4), "0.1000");
}
}