use crate::constants::{EPSILON, EPSILON_CMP};
use crate::error::PositiveError;
use approx::{AbsDiffEq, RelativeEq};
use num_traits::{FromPrimitive, Pow, ToPrimitive};
use rust_decimal::{Decimal, MathematicalOps, RoundingStrategy};
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;
#[cfg(not(feature = "non-zero"))]
use std::iter::Sum;
use std::ops::{Add, AddAssign, Div, Mul, MulAssign, Sub};
use std::str::FromStr;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct Positive(Decimal);
#[inline]
#[must_use]
pub fn is_valid_positive_value(value: Decimal) -> bool {
#[cfg(feature = "non-zero")]
{
value > Decimal::ZERO
}
#[cfg(not(feature = "non-zero"))]
{
value >= Decimal::ZERO
}
}
#[inline]
#[must_use]
fn min_bound() -> f64 {
#[cfg(feature = "non-zero")]
{
f64::MIN_POSITIVE
}
#[cfg(not(feature = "non-zero"))]
{
0.0
}
}
#[must_use]
pub fn is_positive<T: 'static>() -> bool {
std::any::TypeId::of::<T>() == std::any::TypeId::of::<Positive>()
}
pub const DIV_ROUNDING_STRATEGY: RoundingStrategy = RoundingStrategy::MidpointNearestEven;
pub(crate) const DIV_SCALE: u32 = 28;
#[inline]
pub(crate) fn round_div(result: Decimal) -> Decimal {
result.round_dp_with_strategy(DIV_SCALE, DIV_ROUNDING_STRATEGY)
}
#[cold]
#[inline(never)]
pub(crate) fn overflow_panic(op: &'static str) -> ! {
panic!("Positive arithmetic overflow in {op}")
}
#[cold]
#[inline(never)]
pub(crate) fn invariant_panic(op: &'static str) -> ! {
panic!("Positive invariant broken in {op}: result would be non-positive")
}
impl Positive {
#[cfg(not(feature = "non-zero"))]
pub const ZERO: Positive = crate::constants::ZERO;
pub const ONE: Positive = crate::constants::ONE;
pub const TWO: Positive = crate::constants::TWO;
pub const THREE: Positive = crate::constants::THREE;
pub const FOUR: Positive = crate::constants::FOUR;
pub const FIVE: Positive = crate::constants::FIVE;
pub const SIX: Positive = crate::constants::SIX;
pub const SEVEN: Positive = crate::constants::SEVEN;
pub const EIGHT: Positive = crate::constants::EIGHT;
pub const NINE: Positive = crate::constants::NINE;
pub const TEN: Positive = crate::constants::TEN;
pub const FIFTEEN: Positive = crate::constants::FIFTEEN;
pub const TWENTY: Positive = crate::constants::TWENTY;
pub const TWENTY_FIVE: Positive = crate::constants::TWENTY_FIVE;
pub const THIRTY: Positive = crate::constants::THIRTY;
pub const THIRTY_FIVE: Positive = crate::constants::THIRTY_FIVE;
pub const FORTY: Positive = crate::constants::FORTY;
pub const FORTY_FIVE: Positive = crate::constants::FORTY_FIVE;
pub const FIFTY: Positive = crate::constants::FIFTY;
pub const FIFTY_FIVE: Positive = crate::constants::FIFTY_FIVE;
pub const SIXTY: Positive = crate::constants::SIXTY;
pub const SIXTY_FIVE: Positive = crate::constants::SIXTY_FIVE;
pub const SEVENTY: Positive = crate::constants::SEVENTY;
pub const SEVENTY_FIVE: Positive = crate::constants::SEVENTY_FIVE;
pub const EIGHTY: Positive = crate::constants::EIGHTY;
pub const EIGHTY_FIVE: Positive = crate::constants::EIGHTY_FIVE;
pub const NINETY: Positive = crate::constants::NINETY;
pub const NINETY_FIVE: Positive = crate::constants::NINETY_FIVE;
pub const HUNDRED: Positive = crate::constants::HUNDRED;
pub const TWO_HUNDRED: Positive = crate::constants::TWO_HUNDRED;
pub const THREE_HUNDRED: Positive = crate::constants::THREE_HUNDRED;
pub const FOUR_HUNDRED: Positive = crate::constants::FOUR_HUNDRED;
pub const FIVE_HUNDRED: Positive = crate::constants::FIVE_HUNDRED;
pub const SIX_HUNDRED: Positive = crate::constants::SIX_HUNDRED;
pub const SEVEN_HUNDRED: Positive = crate::constants::SEVEN_HUNDRED;
pub const EIGHT_HUNDRED: Positive = crate::constants::EIGHT_HUNDRED;
pub const NINE_HUNDRED: Positive = crate::constants::NINE_HUNDRED;
pub const THOUSAND: Positive = crate::constants::THOUSAND;
pub const TWO_THOUSAND: Positive = crate::constants::TWO_THOUSAND;
pub const THREE_THOUSAND: Positive = crate::constants::THREE_THOUSAND;
pub const FOUR_THOUSAND: Positive = crate::constants::FOUR_THOUSAND;
pub const FIVE_THOUSAND: Positive = crate::constants::FIVE_THOUSAND;
pub const SIX_THOUSAND: Positive = crate::constants::SIX_THOUSAND;
pub const SEVEN_THOUSAND: Positive = crate::constants::SEVEN_THOUSAND;
pub const EIGHT_THOUSAND: Positive = crate::constants::EIGHT_THOUSAND;
pub const NINE_THOUSAND: Positive = crate::constants::NINE_THOUSAND;
pub const TEN_THOUSAND: Positive = crate::constants::TEN_THOUSAND;
pub const PI: Positive = crate::constants::PI;
pub const E: Positive = crate::constants::E;
pub const INFINITY: Positive = crate::constants::INFINITY;
#[must_use = "constructor returns a Result; ignoring the Positive discards a validated invariant"]
pub fn new(value: f64) -> Result<Self, PositiveError> {
let dec = Decimal::from_f64(value);
match dec {
Some(value) if is_valid_positive_value(value) => Ok(Positive(value)),
Some(value) => Err(PositiveError::OutOfBounds {
value: value.to_f64().unwrap_or(0.0),
min: min_bound(),
max: f64::MAX,
}),
None => Err(PositiveError::ConversionError {
from_type: "f64".to_string(),
to_type: "Positive".to_string(),
reason: "failed to parse Decimal".to_string(),
}),
}
}
#[must_use = "constructor returns a Result; ignoring the Positive discards a validated invariant"]
pub fn new_decimal(value: Decimal) -> Result<Self, PositiveError> {
if is_valid_positive_value(value) {
Ok(Positive(value))
} else {
Err(PositiveError::OutOfBounds {
value: value.to_f64().unwrap_or(0.0),
min: min_bound(),
max: f64::INFINITY,
})
}
}
#[inline]
#[must_use]
pub fn value(&self) -> Decimal {
self.0
}
#[inline]
#[must_use]
pub fn to_dec(&self) -> Decimal {
self.0
}
#[inline]
#[must_use]
pub fn to_dec_ref(&self) -> &Decimal {
&self.0
}
#[must_use]
pub fn to_f64(&self) -> f64 {
self.0
.to_f64()
.expect("Decimal to f64 conversion failed - value out of range")
}
#[inline]
#[must_use]
pub fn to_f64_checked(&self) -> Option<f64> {
self.0.to_f64()
}
#[inline]
#[must_use]
pub fn to_f64_lossy(&self) -> f64 {
self.0.to_f64().unwrap_or(0.0)
}
#[must_use]
pub fn to_i64(&self) -> i64 {
self.0
.to_i64()
.expect("Decimal to i64 conversion failed - value out of range")
}
#[must_use]
pub fn to_i64_checked(&self) -> Option<i64> {
self.0.to_i64()
}
#[must_use]
pub fn to_u64(&self) -> u64 {
self.0
.to_u64()
.expect("Decimal to u64 conversion failed - value out of range")
}
#[must_use]
pub fn to_u64_checked(&self) -> Option<u64> {
self.0.to_u64()
}
#[must_use]
pub fn to_usize(&self) -> usize {
self.0
.to_usize()
.expect("Decimal to usize conversion failed - value out of range")
}
#[must_use]
pub fn to_usize_checked(&self) -> Option<usize> {
self.0.to_usize()
}
#[must_use]
pub fn max(self, other: Positive) -> Positive {
if self.0 > other.0 { self } else { other }
}
#[must_use]
pub fn min(self, other: Positive) -> Positive {
if self.0 < other.0 { self } else { other }
}
#[must_use]
pub fn floor(&self) -> Positive {
Positive(self.0.floor())
}
#[must_use]
pub fn powi(&self, n: i64) -> Positive {
Positive(self.0.powi(n))
}
#[must_use]
pub fn pow(&self, n: Positive) -> Positive {
Positive(self.0.pow(n.to_dec()))
}
#[must_use]
pub fn powu(&self, n: u64) -> Positive {
Positive(self.0.powu(n))
}
#[must_use]
pub fn powd(&self, p0: Decimal) -> Positive {
Positive(self.0.powd(p0))
}
#[must_use]
pub fn round(&self) -> Positive {
Positive(self.0.round())
}
#[must_use]
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_or_panic!(3.0) {
Positive::TWO
} else if normalized < pos_or_panic!(7.0) {
pos_or_panic!(5.0)
} else {
Positive::TEN
};
nice_number * pos_or_panic!(10.0).powu(magnitude.to_u64())
}
#[must_use]
pub fn sqrt(&self) -> Positive {
Positive(self.0.sqrt().expect("Square root calculation failed"))
}
pub fn sqrt_checked(&self) -> Result<Positive, PositiveError> {
self.0.sqrt().map(Positive).ok_or_else(|| {
PositiveError::arithmetic_error("sqrt", "square root calculation failed")
})
}
#[inline]
#[must_use]
pub fn ln(&self) -> Positive {
Positive(self.0.ln())
}
#[inline]
#[must_use]
pub fn round_to(&self, decimal_places: u32) -> Positive {
Positive(self.0.round_dp(decimal_places))
}
#[inline]
#[must_use]
pub fn format_fixed_places(&self, decimal_places: u32) -> String {
format!(
"{:.1$}",
self.0.round_dp(decimal_places),
decimal_places as usize
)
}
#[inline]
#[must_use]
pub fn exp(&self) -> Positive {
Positive(self.0.exp())
}
#[must_use]
pub fn clamp(&self, min: Positive, max: Positive) -> Positive {
if self < &min {
min
} else if self > &max {
max
} else {
*self
}
}
#[inline]
#[must_use]
pub fn is_zero(&self) -> bool {
self.0.is_zero()
}
#[inline]
#[must_use]
pub fn ceiling(&self) -> Positive {
Positive(self.to_dec().ceil())
}
#[inline]
#[must_use]
pub fn log10(&self) -> Positive {
Positive(self.0.log10())
}
#[cfg(not(feature = "non-zero"))]
#[must_use]
pub fn sub_or_zero(&self, other: &Decimal) -> Positive {
if &self.0 > other {
Positive(self.0 - other)
} else {
Positive(Decimal::ZERO)
}
}
#[must_use]
pub fn sub_or_none(&self, other: &Decimal) -> Option<Positive> {
if &self.0 >= other {
Some(Positive(self.0 - other))
} else {
None
}
}
#[must_use = "checked arithmetic returns a Result; ignoring it silences the overflow/underflow error"]
pub fn checked_sub(&self, rhs: &Self) -> Result<Self, PositiveError> {
Positive::new_decimal(self.0 - rhs.0)
}
#[cfg(not(feature = "non-zero"))]
#[must_use]
pub fn saturating_sub(&self, rhs: &Self) -> Self {
if self.0 > rhs.0 {
Positive(self.0 - rhs.0)
} else {
Positive::ZERO
}
}
#[must_use = "checked arithmetic returns a Result; ignoring it silences the division-by-zero error"]
pub fn checked_div(&self, rhs: &Self) -> Result<Self, PositiveError> {
if rhs.is_zero() {
Err(PositiveError::arithmetic_error(
"division",
"division by zero",
))
} else {
Ok(Positive(round_div(self.0 / rhs.0)))
}
}
#[must_use = "checked arithmetic returns a Result; ignoring it silences the error"]
pub fn checked_div_with_strategy(
&self,
rhs: &Self,
strategy: RoundingStrategy,
) -> Result<Self, PositiveError> {
if rhs.is_zero() {
return Err(PositiveError::arithmetic_error(
"division",
"division by zero",
));
}
let result = self
.0
.checked_div(rhs.0)
.ok_or_else(|| PositiveError::arithmetic_error("division", "overflow"))?;
Ok(Positive(result.round_dp_with_strategy(DIV_SCALE, strategy)))
}
#[must_use = "checked arithmetic returns a Result; ignoring it silences the error"]
pub fn checked_add_f64(self, rhs: f64) -> Result<Positive, PositiveError> {
let rhs_dec = Decimal::from_f64(rhs).ok_or_else(|| {
PositiveError::conversion_error("f64", "Decimal", "value not representable as Decimal")
})?;
let result = self
.0
.checked_add(rhs_dec)
.ok_or_else(|| PositiveError::arithmetic_error("add_f64", "overflow"))?;
Positive::new_decimal(result)
}
#[must_use = "checked arithmetic returns a Result; ignoring it silences the error"]
pub fn checked_sub_f64(self, rhs: f64) -> Result<Positive, PositiveError> {
let rhs_dec = Decimal::from_f64(rhs).ok_or_else(|| {
PositiveError::conversion_error("f64", "Decimal", "value not representable as Decimal")
})?;
let result = self
.0
.checked_sub(rhs_dec)
.ok_or_else(|| PositiveError::arithmetic_error("sub_f64", "overflow"))?;
Positive::new_decimal(result)
}
#[must_use = "checked arithmetic returns a Result; ignoring it silences the error"]
pub fn checked_mul_f64(self, rhs: f64) -> Result<Positive, PositiveError> {
let rhs_dec = Decimal::from_f64(rhs).ok_or_else(|| {
PositiveError::conversion_error("f64", "Decimal", "value not representable as Decimal")
})?;
let result = self
.0
.checked_mul(rhs_dec)
.ok_or_else(|| PositiveError::arithmetic_error("mul_f64", "overflow"))?;
Positive::new_decimal(result)
}
#[must_use = "checked arithmetic returns a Result; ignoring it silences the error"]
pub fn checked_div_f64(self, rhs: f64) -> Result<Positive, PositiveError> {
let rhs_dec = Decimal::from_f64(rhs).ok_or_else(|| {
PositiveError::conversion_error("f64", "Decimal", "value not representable as Decimal")
})?;
if rhs_dec.is_zero() {
return Err(PositiveError::arithmetic_error(
"div_f64",
"division by zero",
));
}
let result = self
.0
.checked_div(rhs_dec)
.ok_or_else(|| PositiveError::arithmetic_error("div_f64", "overflow"))?;
Positive::new_decimal(round_div(result))
}
#[deprecated(
since = "0.5.0",
note = "use `is_multiple_of_dec` for Decimal-native precision"
)]
#[must_use]
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
}
#[inline]
#[must_use]
pub fn is_multiple_of_dec(&self, other: Decimal) -> bool {
if other.is_zero() {
return false;
}
self.0
.checked_rem(other)
.map(|r| r.is_zero())
.unwrap_or(false)
}
#[inline]
#[must_use]
pub fn is_multiple_of(&self, other: &Positive) -> bool {
if other.is_zero() {
return false;
}
self.0
.checked_rem(other.0)
.map(|r| r.abs() < EPSILON)
.unwrap_or(false)
}
#[inline]
#[must_use]
pub const unsafe fn new_unchecked(value: Decimal) -> Self {
Positive(value)
}
#[inline]
#[must_use]
pub(crate) const fn from_decimal_const(value: Decimal) -> Self {
Positive(value)
}
}
impl From<Positive> for Decimal {
#[inline]
fn from(value: Positive) -> Self {
value.0
}
}
impl PartialEq<&Positive> for Positive {
#[inline]
fn eq(&self, other: &&Positive) -> bool {
self == *other
}
}
impl From<Positive> for u64 {
#[inline]
fn from(pos_u64: Positive) -> Self {
pos_u64.0.to_u64().unwrap_or(0)
}
}
impl From<&Positive> for f64 {
#[inline]
fn from(value: &Positive) -> Self {
value.0.to_f64().unwrap_or(0.0)
}
}
impl From<Positive> for f64 {
#[inline]
fn from(value: Positive) -> Self {
value.0.to_f64().unwrap_or(0.0)
}
}
impl From<Positive> for usize {
#[inline]
fn from(value: Positive) -> Self {
value.to_dec().to_u64().unwrap_or(0) as usize
}
}
impl PartialEq<&Positive> for f64 {
#[inline]
fn eq(&self, other: &&Positive) -> bool {
self == &other.0.to_f64().unwrap_or(0.0)
}
}
impl PartialOrd<&Positive> for f64 {
#[inline]
fn partial_cmp(&self, other: &&Positive) -> Option<Ordering> {
self.partial_cmp(&other.0.to_f64().unwrap_or(0.0))
}
}
impl PartialEq<Positive> for f64 {
#[inline]
fn eq(&self, other: &Positive) -> bool {
self == &other.0.to_f64().unwrap_or(0.0)
}
}
impl PartialOrd<Positive> for f64 {
#[inline]
fn partial_cmp(&self, other: &Positive) -> Option<Ordering> {
self.partial_cmp(&other.0.to_f64().unwrap_or(0.0))
}
}
impl Mul<Positive> for f64 {
type Output = f64;
#[inline]
fn mul(self, rhs: Positive) -> Self::Output {
self * rhs.to_f64()
}
}
impl Div<Positive> for f64 {
type Output = f64;
#[inline]
fn div(self, rhs: Positive) -> Self::Output {
self / rhs.to_f64()
}
}
impl Sub<Positive> for f64 {
type Output = f64;
#[inline]
fn sub(self, rhs: Positive) -> Self::Output {
self - rhs.to_f64()
}
}
impl Add<Positive> for f64 {
type Output = f64;
#[inline]
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 is_valid_positive_value(value) => Ok(Positive(value)),
Ok(value) => Err(format!("Value must be positive, got {value}")),
Err(e) => Err(format!("Failed to parse as Decimal: {e}")),
}
}
}
impl TryFrom<f64> for Positive {
type Error = PositiveError;
fn try_from(value: f64) -> Result<Self, Self::Error> {
Positive::new(value)
}
}
impl TryFrom<usize> for Positive {
type Error = PositiveError;
fn try_from(value: usize) -> Result<Self, Self::Error> {
Positive::new(value as f64)
}
}
impl TryFrom<Decimal> for Positive {
type Error = PositiveError;
fn try_from(value: Decimal) -> Result<Self, Self::Error> {
Positive::new_decimal(value)
}
}
impl TryFrom<&Decimal> for Positive {
type Error = PositiveError;
fn try_from(value: &Decimal) -> Result<Self, Self::Error> {
Positive::new_decimal(*value)
}
}
impl TryFrom<i64> for Positive {
type Error = PositiveError;
fn try_from(value: i64) -> Result<Self, Self::Error> {
Positive::new_decimal(Decimal::from(value))
}
}
impl TryFrom<u64> for Positive {
type Error = PositiveError;
fn try_from(value: u64) -> Result<Self, Self::Error> {
Positive::new_decimal(Decimal::from(value))
}
}
impl From<&Positive> for Positive {
#[inline]
fn from(value: &Positive) -> Self {
Positive(value.0)
}
}
impl Mul<f64> for Positive {
type Output = Positive;
#[inline]
fn mul(self, rhs: f64) -> Positive {
let rhs_dec = Decimal::from_f64(rhs).unwrap_or_else(|| invariant_panic("mul_f64"));
let result = match self.0.checked_mul(rhs_dec) {
Some(v) => v,
None => overflow_panic("mul_f64"),
};
if is_valid_positive_value(result) {
Positive(result)
} else {
invariant_panic("mul_f64")
}
}
}
impl Div<f64> for Positive {
type Output = Positive;
#[inline]
fn div(self, rhs: f64) -> Positive {
let rhs_dec = Decimal::from_f64(rhs).unwrap_or_else(|| invariant_panic("div_f64"));
if rhs_dec.is_zero() {
invariant_panic("div_f64");
}
let result = match self.0.checked_div(rhs_dec) {
Some(v) => round_div(v),
None => overflow_panic("div_f64"),
};
if is_valid_positive_value(result) {
Positive(result)
} else {
invariant_panic("div_f64")
}
}
}
impl Div<f64> for &Positive {
type Output = Positive;
#[inline]
fn div(self, rhs: f64) -> Positive {
let rhs_dec = Decimal::from_f64(rhs).unwrap_or_else(|| invariant_panic("div_f64"));
if rhs_dec.is_zero() {
invariant_panic("div_f64");
}
let result = match self.0.checked_div(rhs_dec) {
Some(v) => round_div(v),
None => overflow_panic("div_f64"),
};
if is_valid_positive_value(result) {
Positive(result)
} else {
invariant_panic("div_f64")
}
}
}
impl Sub<f64> for Positive {
type Output = Positive;
#[inline]
fn sub(self, rhs: f64) -> Self::Output {
let rhs_dec = Decimal::from_f64(rhs).unwrap_or_else(|| invariant_panic("sub_f64"));
let result = match self.0.checked_sub(rhs_dec) {
Some(v) => v,
None => overflow_panic("sub_f64"),
};
if is_valid_positive_value(result) {
Positive(result)
} else {
invariant_panic("sub_f64")
}
}
}
impl Add<f64> for Positive {
type Output = Positive;
#[inline]
fn add(self, rhs: f64) -> Self::Output {
let rhs_dec = Decimal::from_f64(rhs).unwrap_or_else(|| invariant_panic("add_f64"));
let result = match self.0.checked_add(rhs_dec) {
Some(v) => v,
None => overflow_panic("add_f64"),
};
if is_valid_positive_value(result) {
Positive(result)
} else {
invariant_panic("add_f64")
}
}
}
impl PartialOrd<f64> for Positive {
#[inline]
fn partial_cmp(&self, other: &f64) -> Option<Ordering> {
self.0.to_f64().unwrap_or(0.0).partial_cmp(other)
}
}
impl PartialEq<f64> for &Positive {
#[inline]
fn eq(&self, other: &f64) -> bool {
self.0.to_f64().unwrap_or(0.0) == *other
}
}
impl PartialOrd<f64> for &Positive {
#[inline]
fn partial_cmp(&self, other: &f64) -> Option<Ordering> {
self.0.to_f64().unwrap_or(0.0).partial_cmp(other)
}
}
impl PartialEq<f64> for Positive {
#[inline]
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 {
return write!(f, "{}", f64::MAX);
}
if let Some(precision) = f.precision() {
return write!(f, "{:.1$}", self.0, precision);
}
write!(f, "{}", self.0.normalize())
}
}
impl fmt::Debug for Positive {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if *self == Positive::INFINITY {
return write!(f, "{}", f64::MAX);
}
write!(f, "{}", self.0.normalize())
}
}
impl PartialEq<Decimal> for Positive {
#[inline]
fn eq(&self, other: &Decimal) -> bool {
(self.0 - *other).abs() <= EPSILON_CMP
}
}
impl Serialize for Positive {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
if *self == Positive::INFINITY {
return serializer.serialize_f64(f64::MAX);
}
if self.0.scale() == 0 {
serializer.serialize_i64(
self.0
.to_i64()
.ok_or_else(|| serde::ser::Error::custom("Failed to convert to i64"))?,
)
} else {
serializer.serialize_f64(
self.0
.to_f64()
.ok_or_else(|| serde::ser::Error::custom("Failed to convert 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")
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Err(serde::de::Error::custom(format!(
"Invalid string: '{value}'. Expected a positive number."
)))
}
fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
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);
}
if value == f64::MAX {
return Ok(Positive::INFINITY);
}
let decimal = Decimal::from_f64(value)
.ok_or_else(|| serde::de::Error::custom("Failed to convert f64 to Decimal"))?;
Positive::new_decimal(decimal).map_err(serde::de::Error::custom)
}
}
deserializer.deserialize_any(PositiveVisitor)
}
}
impl Add for Positive {
type Output = Positive;
#[inline]
fn add(self, other: Positive) -> Positive {
match self.0.checked_add(other.0) {
Some(v) => Positive(v),
None => overflow_panic("add"),
}
}
}
impl Sub for Positive {
type Output = Positive;
#[inline]
fn sub(self, rhs: Self) -> Self::Output {
let result = match self.0.checked_sub(rhs.0) {
Some(v) => v,
None => overflow_panic("sub"),
};
if is_valid_positive_value(result) {
Positive(result)
} else {
invariant_panic("sub")
}
}
}
impl Div for Positive {
type Output = Positive;
#[inline]
fn div(self, other: Positive) -> Self::Output {
if other.0.is_zero() {
invariant_panic("div");
}
match self.0.checked_div(other.0) {
Some(v) => Positive(round_div(v)),
None => overflow_panic("div"),
}
}
}
impl Div for &Positive {
type Output = Positive;
#[inline]
fn div(self, other: &Positive) -> Self::Output {
if other.0.is_zero() {
invariant_panic("div");
}
match self.0.checked_div(other.0) {
Some(v) => Positive(round_div(v)),
None => overflow_panic("div"),
}
}
}
impl Add<Decimal> for Positive {
type Output = Positive;
#[inline]
fn add(self, rhs: Decimal) -> Positive {
let result = match self.0.checked_add(rhs) {
Some(v) => v,
None => overflow_panic("add_decimal"),
};
if is_valid_positive_value(result) {
Positive(result)
} else {
invariant_panic("add_decimal")
}
}
}
impl Add<&Decimal> for Positive {
type Output = Positive;
#[inline]
fn add(self, rhs: &Decimal) -> Self::Output {
let result = match self.0.checked_add(*rhs) {
Some(v) => v,
None => overflow_panic("add_decimal"),
};
if is_valid_positive_value(result) {
Positive(result)
} else {
invariant_panic("add_decimal")
}
}
}
impl Sub<Decimal> for Positive {
type Output = Positive;
#[inline]
fn sub(self, rhs: Decimal) -> Positive {
let result = match self.0.checked_sub(rhs) {
Some(v) => v,
None => overflow_panic("sub_decimal"),
};
if is_valid_positive_value(result) {
Positive(result)
} else {
invariant_panic("sub_decimal")
}
}
}
impl Sub<&Decimal> for Positive {
type Output = Positive;
#[inline]
fn sub(self, rhs: &Decimal) -> Self::Output {
let result = match self.0.checked_sub(*rhs) {
Some(v) => v,
None => overflow_panic("sub_decimal"),
};
if is_valid_positive_value(result) {
Positive(result)
} else {
invariant_panic("sub_decimal")
}
}
}
impl AddAssign for Positive {
#[inline]
fn add_assign(&mut self, other: Positive) {
match self.0.checked_add(other.0) {
Some(v) => self.0 = v,
None => overflow_panic("add_assign"),
}
}
}
impl AddAssign<Decimal> for Positive {
#[inline]
fn add_assign(&mut self, rhs: Decimal) {
let result = match self.0.checked_add(rhs) {
Some(v) => v,
None => overflow_panic("add_assign_decimal"),
};
if is_valid_positive_value(result) {
self.0 = result;
} else {
invariant_panic("add_assign_decimal");
}
}
}
impl MulAssign<Decimal> for Positive {
#[inline]
fn mul_assign(&mut self, rhs: Decimal) {
let result = match self.0.checked_mul(rhs) {
Some(v) => v,
None => overflow_panic("mul_assign_decimal"),
};
if is_valid_positive_value(result) {
self.0 = result;
} else {
invariant_panic("mul_assign_decimal");
}
}
}
impl Div<Decimal> for Positive {
type Output = Positive;
#[inline]
fn div(self, rhs: Decimal) -> Positive {
if rhs.is_zero() {
invariant_panic("div_decimal");
}
let result = match self.0.checked_div(rhs) {
Some(v) => round_div(v),
None => overflow_panic("div_decimal"),
};
if is_valid_positive_value(result) {
Positive(result)
} else {
invariant_panic("div_decimal")
}
}
}
impl Div<&Decimal> for Positive {
type Output = Positive;
#[inline]
fn div(self, rhs: &Decimal) -> Self::Output {
if rhs.is_zero() {
invariant_panic("div_decimal");
}
let result = match self.0.checked_div(*rhs) {
Some(v) => round_div(v),
None => overflow_panic("div_decimal"),
};
if is_valid_positive_value(result) {
Positive(result)
} else {
invariant_panic("div_decimal")
}
}
}
impl PartialOrd<Decimal> for Positive {
#[inline]
fn partial_cmp(&self, other: &Decimal) -> Option<Ordering> {
self.0.partial_cmp(other)
}
}
impl Mul for Positive {
type Output = Positive;
#[inline]
fn mul(self, other: Positive) -> Positive {
match self.0.checked_mul(other.0) {
Some(v) => Positive(v),
None => overflow_panic("mul"),
}
}
}
impl Mul<Decimal> for Positive {
type Output = Positive;
#[inline]
fn mul(self, rhs: Decimal) -> Positive {
let result = match self.0.checked_mul(rhs) {
Some(v) => v,
None => overflow_panic("mul_decimal"),
};
if is_valid_positive_value(result) {
Positive(result)
} else {
invariant_panic("mul_decimal")
}
}
}
impl Mul<Positive> for Decimal {
type Output = Decimal;
#[inline]
fn mul(self, rhs: Positive) -> Decimal {
match self.checked_mul(rhs.0) {
Some(v) => v,
None => overflow_panic("mul_decimal_by_positive"),
}
}
}
impl Div<Positive> for Decimal {
type Output = Decimal;
#[inline]
fn div(self, rhs: Positive) -> Decimal {
if rhs.0.is_zero() {
invariant_panic("div_decimal_by_positive");
}
match self.checked_div(rhs.0) {
Some(v) => v,
None => overflow_panic("div_decimal_by_positive"),
}
}
}
impl Sub<Positive> for Decimal {
type Output = Decimal;
#[inline]
fn sub(self, rhs: Positive) -> Decimal {
match self.checked_sub(rhs.0) {
Some(v) => v,
None => overflow_panic("sub_decimal_by_positive"),
}
}
}
impl Sub<&Positive> for Decimal {
type Output = Decimal;
#[inline]
fn sub(self, rhs: &Positive) -> Decimal {
match self.checked_sub(rhs.0) {
Some(v) => v,
None => overflow_panic("sub_decimal_by_positive"),
}
}
}
impl Add<Positive> for Decimal {
type Output = Decimal;
#[inline]
fn add(self, rhs: Positive) -> Decimal {
match self.checked_add(rhs.0) {
Some(v) => v,
None => overflow_panic("add_decimal_by_positive"),
}
}
}
impl Add<&Positive> for Decimal {
type Output = Decimal;
#[inline]
fn add(self, rhs: &Positive) -> Decimal {
match self.checked_add(rhs.0) {
Some(v) => v,
None => overflow_panic("add_decimal_by_positive"),
}
}
}
impl std::ops::AddAssign<Positive> for Decimal {
#[inline]
fn add_assign(&mut self, rhs: Positive) {
match self.checked_add(rhs.0) {
Some(v) => *self = v,
None => overflow_panic("add_assign_decimal_by_positive"),
}
}
}
impl std::ops::AddAssign<&Positive> for Decimal {
#[inline]
fn add_assign(&mut self, rhs: &Positive) {
match self.checked_add(rhs.0) {
Some(v) => *self = v,
None => overflow_panic("add_assign_decimal_by_positive"),
}
}
}
impl std::ops::MulAssign<Positive> for Decimal {
#[inline]
fn mul_assign(&mut self, rhs: Positive) {
match self.checked_mul(rhs.0) {
Some(v) => *self = v,
None => overflow_panic("mul_assign_decimal_by_positive"),
}
}
}
impl std::ops::MulAssign<&Positive> for Decimal {
#[inline]
fn mul_assign(&mut self, rhs: &Positive) {
match self.checked_mul(rhs.0) {
Some(v) => *self = v,
None => overflow_panic("mul_assign_decimal_by_positive"),
}
}
}
impl PartialEq<Positive> for Decimal {
#[inline]
fn eq(&self, other: &Positive) -> bool {
*self == other.0
}
}
impl From<&Positive> for Decimal {
#[inline]
fn from(pos: &Positive) -> Self {
pos.0
}
}
#[cfg(not(feature = "non-zero"))]
impl Default for Positive {
fn default() -> Self {
Positive::ZERO
}
}
#[cfg(feature = "non-zero")]
impl Default for Positive {
fn default() -> Self {
Positive::ONE
}
}
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_CMP
}
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
}
}
#[cfg(not(feature = "non-zero"))]
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)
}
}
#[cfg(not(feature = "non-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)
}
}