use crate::constants::{EPSILON, EPSILON_CMP};
use crate::error::PositiveError;
use approx::{AbsDiffEq, RelativeEq};
use num_traits::{FromPrimitive, ToPrimitive};
use rust_decimal::{Decimal, MathematicalOps, RoundingStrategy};
use rust_decimal_macros::dec;
use serde::de::Visitor;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::borrow::Borrow;
use std::cmp::{Ordering, PartialEq};
use std::fmt;
use std::fmt::Display;
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 {
value >= Decimal::ZERO
}
#[inline]
#[must_use]
pub(crate) fn min_bound() -> Decimal {
Decimal::ZERO
}
#[inline]
#[must_use]
pub(crate) fn max_bound() -> Decimal {
Decimal::MAX
}
#[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)
}
#[inline]
pub(crate) fn dec_add(
lhs: Decimal,
rhs: Decimal,
op: &'static str,
) -> Result<Decimal, PositiveError> {
lhs.checked_add(rhs)
.ok_or_else(|| PositiveError::arithmetic_error(op, "overflow"))
}
#[inline]
pub(crate) fn dec_sub(
lhs: Decimal,
rhs: Decimal,
op: &'static str,
) -> Result<Decimal, PositiveError> {
lhs.checked_sub(rhs)
.ok_or_else(|| PositiveError::arithmetic_error(op, "overflow"))
}
#[inline]
pub(crate) fn dec_mul(
lhs: Decimal,
rhs: Decimal,
op: &'static str,
) -> Result<Decimal, PositiveError> {
lhs.checked_mul(rhs)
.ok_or_else(|| PositiveError::arithmetic_error(op, "overflow"))
}
#[inline]
pub(crate) fn dec_div(
lhs: Decimal,
rhs: Decimal,
op: &'static str,
) -> Result<Decimal, PositiveError> {
if rhs.is_zero() {
return Err(PositiveError::arithmetic_error(op, "division by zero"));
}
lhs.checked_div(rhs)
.ok_or_else(|| PositiveError::arithmetic_error(op, "overflow"))
}
#[inline]
fn cmp_decimal_f64(lhs: Decimal, rhs: f64) -> Option<Ordering> {
if rhs.is_nan() {
return None;
}
if rhs.is_infinite() {
return Some(if rhs.is_sign_positive() {
Ordering::Less
} else {
Ordering::Greater
});
}
match Decimal::from_f64(rhs) {
Some(rhs_dec) => {
if rhs_dec.is_zero() && rhs != 0.0 {
return Some(if rhs > 0.0 {
if lhs.is_zero() {
Ordering::Less
} else {
Ordering::Greater
}
} else {
Ordering::Greater
});
}
lhs.partial_cmp(&rhs_dec)
}
None => Some(if rhs > 0.0 {
Ordering::Less
} else {
Ordering::Greater
}),
}
}
#[inline]
pub(crate) fn dec_rem(
lhs: Decimal,
rhs: Decimal,
op: &'static str,
) -> Result<Decimal, PositiveError> {
if rhs.is_zero() {
return Err(PositiveError::arithmetic_error(op, "division by zero"));
}
lhs.checked_rem(rhs)
.ok_or_else(|| PositiveError::arithmetic_error(op, "overflow"))
}
#[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")
}
#[cold]
#[inline(never)]
pub(crate) fn domain_panic(op: &'static str) -> ! {
panic!("Positive domain error in {op}: value is outside the operation's domain")
}
#[inline]
pub(crate) fn validate_precision(decimal_places: u32) -> Result<u32, PositiveError> {
if decimal_places > Decimal::MAX_SCALE {
return Err(PositiveError::invalid_precision(
decimal_places,
"decimal supports at most 28 decimal places",
));
}
Ok(decimal_places)
}
#[cold]
#[inline(never)]
pub(crate) fn inverted_range_panic() -> ! {
panic!("Positive clamp range is inverted: min is greater than max")
}
#[cold]
#[inline(never)]
pub(crate) fn precision_panic(decimal_places: u32) -> ! {
panic!(
"Positive precision {decimal_places} is invalid: decimal supports at most {} decimal places",
Decimal::MAX_SCALE
)
}
#[cold]
#[inline(never)]
pub(crate) fn conversion_panic(target: &'static str) -> ! {
panic!("Positive conversion to {target} failed: value is out of range")
}
#[inline]
pub(crate) fn unwrap_or_panic(
result: Result<Positive, PositiveError>,
op: &'static str,
) -> Positive {
match result {
Ok(value) => value,
Err(PositiveError::OutOfBounds { .. }) => invariant_panic(op),
Err(PositiveError::ArithmeticError { .. }) => overflow_panic(op),
Err(PositiveError::InvalidPrecision { precision, .. }) => precision_panic(precision),
Err(
error @ (PositiveError::InvalidValue { .. } | PositiveError::ConversionError { .. }),
) => panic!("Positive {op} failed: {error}"),
}
}
impl Positive {
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 MAX: Positive = crate::constants::MAX;
#[deprecated(
since = "0.6.0",
note = "renamed to `Positive::MAX`: the value is Decimal::MAX, not an infinity"
)]
#[allow(deprecated)]
pub const INFINITY: Positive = crate::constants::INFINITY;
pub const DAYS_IN_A_YEAR: Positive = crate::constants::DAYS_IN_A_YEAR;
#[must_use = "constructor returns a Result; ignoring the Positive discards a validated invariant"]
pub fn new(value: f64) -> Result<Self, PositiveError> {
if value.is_nan() {
return Err(PositiveError::invalid_value("NaN", "value is not a number"));
}
if value.is_infinite() {
return Err(PositiveError::invalid_value(
&value.to_string(),
"value is infinite and has no decimal representation",
));
}
match Decimal::from_f64(value) {
Some(dec) if is_valid_positive_value(dec) => Ok(Positive(dec)),
Some(dec) => Err(PositiveError::out_of_bounds(dec, min_bound(), max_bound())),
None => Err(PositiveError::conversion_error(
"f64",
"Positive",
"value is outside the range representable as a decimal",
)),
}
}
#[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::out_of_bounds(
value,
min_bound(),
max_bound(),
))
}
}
#[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
}
#[inline]
#[must_use]
pub fn to_f64(&self) -> f64 {
self.0.as_f64()
}
#[inline]
#[must_use]
pub fn to_f64_checked(&self) -> Option<f64> {
Some(self.to_f64())
}
#[deprecated(
since = "0.6.0",
note = "`to_f64` is infallible and returns the same value; this alias will be removed after 0.6.0"
)]
#[inline]
#[must_use]
pub fn to_f64_lossy(&self) -> f64 {
self.to_f64()
}
#[deprecated(
since = "0.6.0",
note = "panics for valid values above i64::MAX; use `i64::try_from(value)` or `to_i64_checked`"
)]
#[must_use]
pub fn to_i64(&self) -> i64 {
match self.0.to_i64() {
Some(value) => value,
None => conversion_panic("i64"),
}
}
#[inline]
#[must_use]
pub fn to_i64_checked(&self) -> Option<i64> {
self.0.to_i64()
}
#[deprecated(
since = "0.6.0",
note = "panics for valid values above u64::MAX; use `u64::try_from(value)` or `to_u64_checked`"
)]
#[must_use]
pub fn to_u64(&self) -> u64 {
match self.0.to_u64() {
Some(value) => value,
None => conversion_panic("u64"),
}
}
#[inline]
#[must_use]
pub fn to_u64_checked(&self) -> Option<u64> {
self.0.to_u64()
}
#[deprecated(
since = "0.6.0",
note = "panics for valid values above usize::MAX; use `usize::try_from(value)` or `to_usize_checked`"
)]
#[must_use]
pub fn to_usize(&self) -> usize {
match self.0.to_usize() {
Some(value) => value,
None => conversion_panic("usize"),
}
}
#[inline]
#[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 {
unwrap_or_panic(self.checked_floor(), "floor")
}
#[must_use = "checked arithmetic returns a Result; ignoring it silences the invariant error"]
pub fn checked_floor(&self) -> Result<Positive, PositiveError> {
Positive::new_decimal(self.0.floor())
}
#[must_use]
pub fn powi(&self, n: i64) -> Positive {
unwrap_or_panic(self.checked_powi(n), "powi")
}
#[must_use = "checked arithmetic returns a Result; ignoring it silences the domain error"]
pub fn checked_powi(&self, n: i64) -> Result<Positive, PositiveError> {
let result = self.0.checked_powi(n).ok_or_else(|| {
PositiveError::arithmetic_error("powi", "power is undefined or overflows")
})?;
Positive::new_decimal(result)
}
#[must_use]
pub fn pow(&self, n: Positive) -> Positive {
unwrap_or_panic(self.checked_pow(n), "pow")
}
#[must_use = "checked arithmetic returns a Result; ignoring it silences the domain error"]
pub fn checked_pow(&self, n: Positive) -> Result<Positive, PositiveError> {
self.checked_powd(n.to_dec())
}
#[must_use]
pub fn powu(&self, n: u64) -> Positive {
unwrap_or_panic(self.checked_powu(n), "powu")
}
#[must_use = "checked arithmetic returns a Result; ignoring it silences the overflow error"]
pub fn checked_powu(&self, n: u64) -> Result<Positive, PositiveError> {
let result = self
.0
.checked_powu(n)
.ok_or_else(|| PositiveError::arithmetic_error("powu", "power overflows"))?;
Positive::new_decimal(result)
}
#[must_use]
pub fn powd(&self, p0: Decimal) -> Positive {
unwrap_or_panic(self.checked_powd(p0), "powd")
}
#[must_use = "checked arithmetic returns a Result; ignoring it silences the domain error"]
pub fn checked_powd(&self, p0: Decimal) -> Result<Positive, PositiveError> {
if self.0.is_zero() && p0.is_sign_negative() {
return Err(PositiveError::arithmetic_error(
"powd",
"zero to a negative power is undefined",
));
}
let result = self.0.checked_powd(p0).ok_or_else(|| {
PositiveError::arithmetic_error("powd", "power is undefined or overflows")
})?;
Positive::new_decimal(result)
}
#[must_use]
pub fn round(&self) -> Positive {
unwrap_or_panic(self.checked_round(), "round")
}
#[must_use = "checked arithmetic returns a Result; ignoring it silences the invariant error"]
pub fn checked_round(&self) -> Result<Positive, PositiveError> {
Positive::new_decimal(self.0.round())
}
#[must_use]
pub fn round_to_nice_number(&self) -> Positive {
unwrap_or_panic(self.checked_round_to_nice_number(), "round_to_nice_number")
}
#[must_use = "checked arithmetic returns a Result; ignoring it silences the domain error"]
pub fn checked_round_to_nice_number(&self) -> Result<Positive, PositiveError> {
if self.0.is_zero() {
return Positive::new_decimal(Decimal::ZERO);
}
let magnitude = self
.0
.checked_log10()
.ok_or_else(|| {
PositiveError::arithmetic_error(
"round_to_nice_number",
"base-10 logarithm is undefined for this value",
)
})?
.floor();
let ten_pow = Decimal::TEN.checked_powd(magnitude).ok_or_else(|| {
PositiveError::arithmetic_error("round_to_nice_number", "magnitude overflows")
})?;
let normalized = dec_div(self.0, ten_pow, "round_to_nice_number")?;
let nice_number = if normalized < dec!(1.5) {
Decimal::ONE
} else if normalized < dec!(3.0) {
dec!(2)
} else if normalized < dec!(7.0) {
dec!(5)
} else {
dec!(10)
};
Positive::new_decimal(dec_mul(nice_number, ten_pow, "round_to_nice_number")?)
}
#[must_use]
pub fn sqrt(&self) -> Positive {
unwrap_or_panic(self.checked_sqrt(), "sqrt")
}
#[must_use = "checked arithmetic returns a Result; ignoring it silences the error"]
pub fn checked_sqrt(&self) -> Result<Positive, PositiveError> {
let root = self.0.sqrt().ok_or_else(|| {
PositiveError::arithmetic_error("sqrt", "square root calculation failed")
})?;
Positive::new_decimal(root)
}
#[deprecated(
since = "0.6.0",
note = "renamed to `checked_sqrt` for consistency with the rest of the checked API"
)]
#[must_use = "checked arithmetic returns a Result; ignoring it silences the error"]
pub fn sqrt_checked(&self) -> Result<Positive, PositiveError> {
self.checked_sqrt()
}
#[inline]
#[must_use]
pub fn ln(&self) -> Decimal {
match self.checked_ln() {
Ok(value) => value,
Err(_) => domain_panic("ln"),
}
}
#[inline]
#[must_use = "checked mathematics returns a Result; ignoring it silences the domain error"]
pub fn checked_ln(&self) -> Result<Decimal, PositiveError> {
self.0.checked_ln().ok_or_else(|| {
PositiveError::arithmetic_error("ln", "natural logarithm is undefined for zero")
})
}
#[inline]
#[must_use]
pub fn log10(&self) -> Decimal {
match self.checked_log10() {
Ok(value) => value,
Err(_) => domain_panic("log10"),
}
}
#[inline]
#[must_use = "checked mathematics returns a Result; ignoring it silences the domain error"]
pub fn checked_log10(&self) -> Result<Decimal, PositiveError> {
self.0.checked_log10().ok_or_else(|| {
PositiveError::arithmetic_error("log10", "base-10 logarithm is undefined for zero")
})
}
#[inline]
#[must_use]
pub fn round_to(&self, decimal_places: u32) -> Positive {
match validate_precision(decimal_places) {
Ok(_) => unwrap_or_panic(self.checked_round_to(decimal_places), "round_to"),
Err(_) => precision_panic(decimal_places),
}
}
#[inline]
#[must_use = "checked arithmetic returns a Result; ignoring it silences the invariant error"]
pub fn checked_round_to(&self, decimal_places: u32) -> Result<Positive, PositiveError> {
let decimal_places = validate_precision(decimal_places)?;
Positive::new_decimal(self.0.round_dp(decimal_places))
}
#[inline]
#[must_use]
pub fn format_fixed_places(&self, decimal_places: u32) -> String {
match self.checked_format_fixed_places(decimal_places) {
Ok(formatted) => formatted,
Err(_) => precision_panic(decimal_places),
}
}
#[inline]
#[must_use = "checked formatting returns a Result; ignoring it silences the precision error"]
pub fn checked_format_fixed_places(
&self,
decimal_places: u32,
) -> Result<String, PositiveError> {
let decimal_places = validate_precision(decimal_places)?;
let rounded = self.0.round_dp(decimal_places);
let mut text = rounded.to_string();
if decimal_places == 0 {
return Ok(text);
}
let fractional_len = match text.find('.') {
Some(point) => text.len() - point - 1,
None => {
text.push('.');
0
}
};
for _ in fractional_len..decimal_places as usize {
text.push('0');
}
Ok(text)
}
#[inline]
#[must_use]
pub fn exp(&self) -> Positive {
unwrap_or_panic(self.checked_exp(), "exp")
}
#[inline]
#[must_use = "checked mathematics returns a Result; ignoring it silences the overflow error"]
pub fn checked_exp(&self) -> Result<Positive, PositiveError> {
let result = self
.0
.checked_exp()
.ok_or_else(|| PositiveError::arithmetic_error("exp", "result overflows"))?;
Positive::new_decimal(result)
}
#[must_use]
pub fn clamp(self, min: Positive, max: Positive) -> Positive {
match self.checked_clamp(min, max) {
Ok(clamped) => clamped,
Err(_) => inverted_range_panic(),
}
}
#[must_use = "checked clamping returns a Result; ignoring it silences the inverted-range error"]
pub fn checked_clamp(self, min: Positive, max: Positive) -> Result<Positive, PositiveError> {
if min > max {
return Err(PositiveError::out_of_bounds(min.0, min_bound(), max.0));
}
Ok(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 approx_eq_dec(&self, other: Decimal, epsilon: Decimal) -> bool {
match dec_sub(self.0, other, "approx_eq_dec") {
Ok(difference) => difference.abs() <= epsilon,
Err(_) => false,
}
}
#[inline]
#[must_use]
pub fn ceiling(&self) -> Positive {
unwrap_or_panic(self.checked_ceiling(), "ceiling")
}
#[inline]
#[must_use = "checked arithmetic returns a Result; ignoring it silences the invariant error"]
pub fn checked_ceiling(&self) -> Result<Positive, PositiveError> {
Positive::new_decimal(self.0.ceil())
}
#[must_use]
#[deprecated(
since = "0.5.1",
note = "saturating arithmetic hides underflow and overflow; use `checked_sub` or `checked_sub_dec` and handle the error, or explicitly floor at zero with `new_decimal(self.0.saturating_sub(*other))`. Removal is scheduled for the release after 0.6.0"
)]
pub fn sub_or_zero(&self, other: &Decimal) -> Positive {
if &self.0 <= other {
return Positive::ZERO;
}
unwrap_or_panic(self.checked_sub_dec(*other), "sub_or_zero")
}
#[must_use]
pub fn sub_or_none(&self, other: &Decimal) -> Option<Positive> {
if &self.0 >= other {
dec_sub(self.0, *other, "sub_or_none")
.ok()
.and_then(|result| Positive::new_decimal(result).ok())
} else {
None
}
}
#[must_use = "checked arithmetic returns a Result; ignoring it silences the overflow error"]
pub fn checked_add(&self, rhs: &Self) -> Result<Self, PositiveError> {
Positive::new_decimal(dec_add(self.0, rhs.0, "addition")?)
}
#[must_use = "checked arithmetic returns a Result; ignoring it silences the overflow error"]
pub fn checked_mul(&self, rhs: &Self) -> Result<Self, PositiveError> {
Positive::new_decimal(dec_mul(self.0, rhs.0, "multiplication")?)
}
#[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(dec_sub(self.0, rhs.0, "subtraction")?)
}
#[deprecated(
since = "0.6.0",
note = "saturating arithmetic hides underflow; use `checked_sub` and handle the error, or `sub_or_zero` to floor at zero explicitly. Removal is scheduled for the release after 0.6.0"
)]
#[must_use]
pub fn saturating_sub(&self, rhs: &Self) -> Self {
self.checked_sub(rhs).unwrap_or(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> {
let quotient = dec_div(self.0, rhs.0, "division")?;
Positive::new_decimal(round_div(quotient))
}
#[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> {
let quotient = dec_div(self.0, rhs.0, "division")?;
Positive::new_decimal(quotient.round_dp_with_strategy(DIV_SCALE, strategy))
}
#[must_use = "checked arithmetic returns a Result; ignoring it silences the error"]
pub fn checked_add_dec(self, rhs: Decimal) -> Result<Positive, PositiveError> {
Positive::new_decimal(dec_add(self.0, rhs, "add_decimal")?)
}
#[must_use = "checked arithmetic returns a Result; ignoring it silences the error"]
pub fn checked_sub_dec(self, rhs: Decimal) -> Result<Positive, PositiveError> {
Positive::new_decimal(dec_sub(self.0, rhs, "sub_decimal")?)
}
#[must_use = "checked arithmetic returns a Result; ignoring it silences the error"]
pub fn checked_mul_dec(self, rhs: Decimal) -> Result<Positive, PositiveError> {
Positive::new_decimal(dec_mul(self.0, rhs, "mul_decimal")?)
}
#[must_use = "checked arithmetic returns a Result; ignoring it silences the error"]
pub fn checked_div_dec(self, rhs: Decimal) -> Result<Positive, PositiveError> {
let quotient = dec_div(self.0, rhs, "div_decimal")?;
Positive::new_decimal(round_div(quotient))
}
#[must_use = "checked arithmetic returns a Result; ignoring it silences the error"]
pub fn checked_rem(&self, rhs: &Self) -> Result<Self, PositiveError> {
Positive::new_decimal(dec_rem(self.0, rhs.0, "remainder")?)
}
#[must_use = "checked aggregation returns a Result; ignoring it silences the overflow error"]
pub fn checked_sum<I, T>(iter: I) -> Result<Positive, PositiveError>
where
I: IntoIterator<Item = T>,
T: Borrow<Positive>,
{
let mut total = Decimal::ZERO;
for value in iter {
total = dec_add(total, value.borrow().0, "sum")?;
}
Positive::new_decimal(total)
}
#[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")
})?;
Positive::new_decimal(dec_add(self.0, rhs_dec, "add_f64")?)
}
#[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")
})?;
Positive::new_decimal(dec_sub(self.0, rhs_dec, "sub_f64")?)
}
#[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")
})?;
Positive::new_decimal(dec_mul(self.0, rhs_dec, "mul_f64")?)
}
#[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")
})?;
Positive::new_decimal(round_div(dec_div(self.0, rhs_dec, "div_f64")?))
}
#[deprecated(
since = "0.5.0",
note = "use `is_multiple_of_dec` for Decimal-native precision; removal is scheduled for the release after 0.6.0"
)]
#[must_use]
pub fn is_multiple(&self, other: f64) -> bool {
if !other.is_finite() || other == 0.0 {
return false;
}
let Some(value) = self.to_f64_checked() else {
return false;
};
if !value.is_finite() {
return false;
}
let remainder = value % other;
remainder.abs() < f64::EPSILON || (other.abs() - remainder.abs()).abs() < f64::EPSILON
}
#[inline]
#[must_use]
pub fn is_multiple_of_dec(&self, other: Decimal) -> bool {
dec_rem(self.0, other, "is_multiple_of_dec")
.map(|remainder| remainder.is_zero())
.unwrap_or(false)
}
#[inline]
#[must_use]
pub fn is_multiple_of(&self, other: &Positive) -> bool {
self.is_multiple_of_dec(other.0)
}
#[inline]
#[must_use]
pub fn is_multiple_of_within(&self, other: &Positive, tolerance: Decimal) -> bool {
let Ok(remainder) = dec_rem(self.0, other.0, "is_multiple_of_within") else {
return false;
};
let distance_to_zero = remainder.abs();
if distance_to_zero <= tolerance {
return true;
}
match dec_sub(other.0, distance_to_zero, "is_multiple_of_within") {
Ok(distance_to_divisor) => distance_to_divisor.abs() <= tolerance,
Err(_) => false,
}
}
#[inline]
#[must_use]
pub(crate) const fn from_decimal_const(value: Decimal) -> Self {
Positive(value)
}
}
#[inline]
fn try_to_integer<T>(
value: Positive,
convert: impl FnOnce(&Decimal) -> Option<T>,
target: &'static str,
reason: &'static str,
) -> Result<T, PositiveError> {
convert(&value.0).ok_or_else(|| PositiveError::conversion_error("Positive", target, reason))
}
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 TryFrom<Positive> for u64 {
type Error = PositiveError;
#[inline]
fn try_from(value: Positive) -> Result<Self, Self::Error> {
try_to_integer(value, Decimal::to_u64, "u64", "value exceeds u64::MAX")
}
}
impl TryFrom<Positive> for i64 {
type Error = PositiveError;
#[inline]
fn try_from(value: Positive) -> Result<Self, Self::Error> {
try_to_integer(value, Decimal::to_i64, "i64", "value exceeds i64::MAX")
}
}
impl From<&Positive> for f64 {
#[inline]
fn from(value: &Positive) -> Self {
value.to_f64()
}
}
impl From<Positive> for f64 {
#[inline]
fn from(value: Positive) -> Self {
value.to_f64()
}
}
impl TryFrom<Positive> for usize {
type Error = PositiveError;
#[inline]
fn try_from(value: Positive) -> Result<Self, Self::Error> {
try_to_integer(
value,
Decimal::to_usize,
"usize",
"value exceeds usize::MAX",
)
}
}
impl PartialEq<&Positive> for f64 {
#[inline]
fn eq(&self, other: &&Positive) -> bool {
cmp_decimal_f64(other.0, *self) == Some(Ordering::Equal)
}
}
impl PartialOrd<&Positive> for f64 {
#[inline]
fn partial_cmp(&self, other: &&Positive) -> Option<Ordering> {
cmp_decimal_f64(other.0, *self).map(Ordering::reverse)
}
}
impl PartialEq<Positive> for f64 {
#[inline]
fn eq(&self, other: &Positive) -> bool {
cmp_decimal_f64(other.0, *self) == Some(Ordering::Equal)
}
}
impl PartialOrd<Positive> for f64 {
#[inline]
fn partial_cmp(&self, other: &Positive) -> Option<Ordering> {
cmp_decimal_f64(other.0, *self).map(Ordering::reverse)
}
}
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 = PositiveError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.parse::<Decimal>() {
Ok(value) => Positive::new_decimal(value),
Err(e) => Err(PositiveError::invalid_value(
s,
&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_decimal(Decimal::from(value))
}
}
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"));
unwrap_or_panic(self.checked_mul_dec(rhs_dec), "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"));
guard_nonzero_divisor(rhs_dec, "div_f64");
unwrap_or_panic(self.checked_div_dec(rhs_dec), "div_f64")
}
}
impl Div<f64> for &Positive {
type Output = Positive;
#[inline]
fn div(self, rhs: f64) -> Positive {
*self / rhs
}
}
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"));
unwrap_or_panic(self.checked_sub_dec(rhs_dec), "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"));
unwrap_or_panic(self.checked_add_dec(rhs_dec), "add_f64")
}
}
impl PartialOrd<f64> for Positive {
#[inline]
fn partial_cmp(&self, other: &f64) -> Option<Ordering> {
cmp_decimal_f64(self.0, *other)
}
}
impl PartialEq<f64> for &Positive {
#[inline]
fn eq(&self, other: &f64) -> bool {
cmp_decimal_f64(self.0, *other) == Some(Ordering::Equal)
}
}
impl PartialOrd<f64> for &Positive {
#[inline]
fn partial_cmp(&self, other: &f64) -> Option<Ordering> {
cmp_decimal_f64(self.0, *other)
}
}
impl PartialEq<f64> for Positive {
#[inline]
fn eq(&self, other: &f64) -> bool {
cmp_decimal_f64(self.0, *other) == Some(Ordering::Equal)
}
}
impl Display for Positive {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
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 {
write!(f, "{}", self.0.normalize())
}
}
impl PartialEq<Decimal> for Positive {
#[inline]
fn eq(&self, other: &Decimal) -> bool {
self.0 == *other
}
}
impl Serialize for Positive {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
Serialize::serialize(&self.0, serializer)
}
}
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 decimal, as a string or a number")
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
let decimal = Decimal::from_str_exact(value).map_err(|error| {
serde::de::Error::custom(format!("invalid decimal string '{value}': {error}"))
})?;
Positive::new_decimal(decimal).map_err(serde::de::Error::custom)
}
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,
{
let decimal = Decimal::from_f64(value).ok_or_else(|| {
serde::de::Error::custom(format!(
"number {value} is not representable as a decimal"
))
})?;
Positive::new_decimal(decimal).map_err(serde::de::Error::custom)
}
}
if deserializer.is_human_readable() {
deserializer.deserialize_any(PositiveVisitor)
} else {
deserializer.deserialize_str(PositiveVisitor)
}
}
}
#[inline]
fn guard_nonzero_divisor(divisor: Decimal, op: &'static str) {
if divisor.is_zero() {
invariant_panic(op);
}
}
#[inline]
fn dec_or_panic(result: Result<Decimal, PositiveError>, op: &'static str) -> Decimal {
match result {
Ok(value) => value,
Err(PositiveError::ArithmeticError { .. }) => overflow_panic(op),
Err(
error @ (PositiveError::InvalidValue { .. }
| PositiveError::ConversionError { .. }
| PositiveError::OutOfBounds { .. }
| PositiveError::InvalidPrecision { .. }),
) => panic!("Positive {op} failed: {error}"),
}
}
impl Add for Positive {
type Output = Positive;
#[inline]
fn add(self, other: Positive) -> Positive {
unwrap_or_panic(self.checked_add(&other), "add")
}
}
impl Sub for Positive {
type Output = Positive;
#[inline]
fn sub(self, rhs: Self) -> Self::Output {
unwrap_or_panic(self.checked_sub(&rhs), "sub")
}
}
impl Mul for Positive {
type Output = Positive;
#[inline]
fn mul(self, other: Positive) -> Positive {
unwrap_or_panic(self.checked_mul(&other), "mul")
}
}
impl Div for Positive {
type Output = Positive;
#[inline]
fn div(self, other: Positive) -> Self::Output {
guard_nonzero_divisor(other.0, "div");
unwrap_or_panic(self.checked_div(&other), "div")
}
}
impl Div for &Positive {
type Output = Positive;
#[inline]
fn div(self, other: &Positive) -> Self::Output {
guard_nonzero_divisor(other.0, "div");
unwrap_or_panic(self.checked_div(other), "div")
}
}
impl Add<Decimal> for Positive {
type Output = Positive;
#[inline]
fn add(self, rhs: Decimal) -> Positive {
unwrap_or_panic(self.checked_add_dec(rhs), "add_decimal")
}
}
impl Add<&Decimal> for Positive {
type Output = Positive;
#[inline]
fn add(self, rhs: &Decimal) -> Self::Output {
unwrap_or_panic(self.checked_add_dec(*rhs), "add_decimal")
}
}
impl Sub<Decimal> for Positive {
type Output = Positive;
#[inline]
fn sub(self, rhs: Decimal) -> Positive {
unwrap_or_panic(self.checked_sub_dec(rhs), "sub_decimal")
}
}
impl Sub<&Decimal> for Positive {
type Output = Positive;
#[inline]
fn sub(self, rhs: &Decimal) -> Self::Output {
unwrap_or_panic(self.checked_sub_dec(*rhs), "sub_decimal")
}
}
impl Mul<Decimal> for Positive {
type Output = Positive;
#[inline]
fn mul(self, rhs: Decimal) -> Positive {
unwrap_or_panic(self.checked_mul_dec(rhs), "mul_decimal")
}
}
impl Div<Decimal> for Positive {
type Output = Positive;
#[inline]
fn div(self, rhs: Decimal) -> Positive {
guard_nonzero_divisor(rhs, "div_decimal");
unwrap_or_panic(self.checked_div_dec(rhs), "div_decimal")
}
}
impl Div<&Decimal> for Positive {
type Output = Positive;
#[inline]
fn div(self, rhs: &Decimal) -> Self::Output {
guard_nonzero_divisor(*rhs, "div_decimal");
unwrap_or_panic(self.checked_div_dec(*rhs), "div_decimal")
}
}
impl AddAssign for Positive {
#[inline]
fn add_assign(&mut self, other: Positive) {
*self = unwrap_or_panic(self.checked_add(&other), "add_assign");
}
}
impl AddAssign<Decimal> for Positive {
#[inline]
fn add_assign(&mut self, rhs: Decimal) {
*self = unwrap_or_panic(self.checked_add_dec(rhs), "add_assign_decimal");
}
}
impl MulAssign<Decimal> for Positive {
#[inline]
fn mul_assign(&mut self, rhs: Decimal) {
*self = unwrap_or_panic(self.checked_mul_dec(rhs), "mul_assign_decimal");
}
}
impl PartialOrd<Decimal> for Positive {
#[inline]
fn partial_cmp(&self, other: &Decimal) -> Option<Ordering> {
self.0.partial_cmp(other)
}
}
impl PartialOrd<Positive> for Decimal {
#[inline]
fn partial_cmp(&self, other: &Positive) -> Option<Ordering> {
self.partial_cmp(&other.0)
}
}
impl Mul<Positive> for Decimal {
type Output = Decimal;
#[inline]
fn mul(self, rhs: Positive) -> Decimal {
dec_or_panic(
dec_mul(self, rhs.0, "mul_decimal_by_positive"),
"mul_decimal_by_positive",
)
}
}
impl Div<Positive> for Decimal {
type Output = Decimal;
#[inline]
fn div(self, rhs: Positive) -> Decimal {
guard_nonzero_divisor(rhs.0, "div_decimal_by_positive");
dec_or_panic(
dec_div(self, rhs.0, "div_decimal_by_positive"),
"div_decimal_by_positive",
)
}
}
impl Sub<Positive> for Decimal {
type Output = Decimal;
#[inline]
fn sub(self, rhs: Positive) -> Decimal {
dec_or_panic(
dec_sub(self, rhs.0, "sub_decimal_by_positive"),
"sub_decimal_by_positive",
)
}
}
impl Sub<&Positive> for Decimal {
type Output = Decimal;
#[inline]
fn sub(self, rhs: &Positive) -> Decimal {
self - *rhs
}
}
impl Add<Positive> for Decimal {
type Output = Decimal;
#[inline]
fn add(self, rhs: Positive) -> Decimal {
dec_or_panic(
dec_add(self, rhs.0, "add_decimal_by_positive"),
"add_decimal_by_positive",
)
}
}
impl Add<&Positive> for Decimal {
type Output = Decimal;
#[inline]
fn add(self, rhs: &Positive) -> Decimal {
self + *rhs
}
}
impl std::ops::AddAssign<Positive> for Decimal {
#[inline]
fn add_assign(&mut self, rhs: Positive) {
*self = dec_or_panic(
dec_add(*self, rhs.0, "add_assign_decimal_by_positive"),
"add_assign_decimal_by_positive",
);
}
}
impl std::ops::AddAssign<&Positive> for Decimal {
#[inline]
fn add_assign(&mut self, rhs: &Positive) {
*self += *rhs;
}
}
impl std::ops::MulAssign<Positive> for Decimal {
#[inline]
fn mul_assign(&mut self, rhs: Positive) {
*self = dec_or_panic(
dec_mul(*self, rhs.0, "mul_assign_decimal_by_positive"),
"mul_assign_decimal_by_positive",
);
}
}
impl std::ops::MulAssign<&Positive> for Decimal {
#[inline]
fn mul_assign(&mut self, rhs: &Positive) {
*self *= *rhs;
}
}
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
}
}
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 {
match dec_sub(self.0, other.0, "abs_diff_eq") {
Ok(difference) => difference.abs() <= epsilon,
Err(_) => false,
}
}
}
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 Ok(difference) = dec_sub(self.0, other.0, "relative_eq") else {
return false;
};
let abs_diff = difference.abs();
if abs_diff <= epsilon {
return true;
}
if max_relative.is_sign_negative() {
return false;
}
let largest = self.0.abs().max(other.0.abs());
match dec_mul(max_relative, largest, "relative_eq") {
Ok(tolerance) => abs_diff <= tolerance,
Err(_) => true,
}
}
}
impl Sum for Positive {
fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
match Positive::checked_sum(iter) {
Ok(total) => total,
Err(_) => overflow_panic("sum"),
}
}
}
impl<'a> Sum<&'a Positive> for Positive {
fn sum<I: Iterator<Item = &'a Positive>>(iter: I) -> Self {
match Positive::checked_sum(iter) {
Ok(total) => total,
Err(_) => overflow_panic("sum"),
}
}
}