mod arithmetic;
mod compare;
mod text;
mod wire;
#[cfg(test)]
mod tests;
use crate::NumericValue;
use std::fmt::{Display, Formatter};
use std::str::FromStr;
pub(crate) const MAX_SUPPORTED_SCALE: u32 = 28;
pub(crate) const DEFAULT_DIVISION_SCALE: u32 = 18;
pub(crate) const DECIMAL_DIGIT_BUFFER_LEN: usize = 39;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct DecimalParts {
mantissa: i128,
scale: u32,
}
impl DecimalParts {
#[must_use]
pub const fn mantissa(&self) -> i128 {
self.mantissa
}
#[must_use]
pub const fn scale(&self) -> u32 {
self.scale
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ParseDecimalError {
reason: ParseDecimalErrorReason,
}
impl ParseDecimalError {
pub(crate) const fn new(reason: ParseDecimalErrorReason) -> Self {
Self { reason }
}
#[must_use]
pub const fn reason(&self) -> ParseDecimalErrorReason {
self.reason
}
}
impl std::error::Error for ParseDecimalError {}
impl Display for ParseDecimalError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str("decimal parse error")
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[repr(u8)]
pub enum ParseDecimalErrorReason {
Empty,
ExponentNotationUnsupported,
FractionalLengthOverflow,
ScaleOverflow,
MantissaOverflow,
ScaleExceedsSupportedRange,
InvalidSignificand,
InvalidDigits,
}
#[derive(Clone, Copy, Debug, Default)]
pub struct Decimal {
mantissa: i128,
scale: u32,
}
impl Decimal {
pub const ZERO: Self = Self {
mantissa: 0,
scale: 0,
};
#[must_use]
pub const fn max_supported_scale() -> u32 {
MAX_SUPPORTED_SCALE
}
#[must_use]
pub const fn new(num: i64, scale: u32) -> Self {
assert!(
scale <= MAX_SUPPORTED_SCALE,
"decimal scale exceeds supported range"
);
Self::new_unchecked(num, scale)
}
#[must_use]
pub const fn try_new(num: i64, scale: u32) -> Option<Self> {
if scale > MAX_SUPPORTED_SCALE {
return None;
}
Some(Self::new_unchecked(num, scale))
}
#[must_use]
pub(crate) const fn new_unchecked(num: i64, scale: u32) -> Self {
Self {
mantissa: num as i128,
scale,
}
}
pub fn from_num<N: NumericValue>(n: N) -> Option<Self> {
n.try_to_decimal()
}
#[must_use]
pub const fn from_i64(n: i64) -> Option<Self> {
Some(Self {
mantissa: n as i128,
scale: 0,
})
}
#[must_use]
pub const fn from_u64(n: u64) -> Option<Self> {
Some(Self {
mantissa: n as i128,
scale: 0,
})
}
#[must_use]
pub const fn from_i128(n: i128) -> Option<Self> {
Some(Self {
mantissa: n,
scale: 0,
})
}
#[must_use]
pub fn from_u128(n: u128) -> Option<Self> {
Some(Self {
mantissa: i128::try_from(n).ok()?,
scale: 0,
})
}
#[must_use]
pub fn from_f32_lossy(n: f32) -> Option<Self> {
if !n.is_finite() {
return None;
}
Self::from_str(&n.to_string()).ok()
}
#[must_use]
pub fn from_f64_lossy(n: f64) -> Option<Self> {
if !n.is_finite() {
return None;
}
Self::from_str(&n.to_string()).ok()
}
#[must_use]
pub const fn parts(&self) -> DecimalParts {
DecimalParts {
mantissa: self.mantissa,
scale: self.scale,
}
}
#[must_use]
pub const fn is_integer(&self) -> bool {
self.scale == 0
}
#[must_use]
pub fn scale_to_integer(&self, target_scale: u32) -> Option<i128> {
if self.scale > target_scale {
return None;
}
let factor = Self::checked_pow10(target_scale - self.scale)?;
self.mantissa.checked_mul(factor)
}
#[must_use]
pub fn to_i32(&self) -> Option<i32> {
self.to_i64().and_then(|value| i32::try_from(value).ok())
}
#[must_use]
pub fn to_i64(&self) -> Option<i64> {
let integer = Self::decimal_integer_value(self.mantissa, self.scale)?;
i64::try_from(integer).ok()
}
#[must_use]
pub fn to_i128(&self) -> Option<i128> {
Self::decimal_integer_value(self.mantissa, self.scale)
}
#[must_use]
pub fn to_u64(&self) -> Option<u64> {
let integer = Self::decimal_integer_value(self.mantissa, self.scale)?;
u64::try_from(integer).ok()
}
#[must_use]
pub fn to_u128(&self) -> Option<u128> {
let integer = Self::decimal_integer_value(self.mantissa, self.scale)?;
u128::try_from(integer).ok()
}
#[must_use]
#[expect(clippy::cast_possible_truncation)]
pub fn to_f32(&self) -> Option<f32> {
self.to_f64().and_then(|value| {
let float = value as f32;
if float.is_finite() { Some(float) } else { None }
})
}
#[must_use]
#[expect(clippy::cast_precision_loss)]
pub fn to_f64(&self) -> Option<f64> {
let divisor = 10f64.powi(i32::try_from(self.scale).ok()?);
let value = (self.mantissa as f64) / divisor;
if value.is_finite() { Some(value) } else { None }
}
#[must_use]
pub const fn try_from_i128_with_scale(num: i128, scale: u32) -> Option<Self> {
Self::checked_from_mantissa_scale(num, scale)
}
#[must_use]
pub const fn from_i128_with_scale(num: i128, scale: u32) -> Self {
Self::try_from_i128_with_scale(num, scale).expect("decimal invariant")
}
#[must_use]
pub const fn normalize(&self) -> Self {
let (mantissa, scale) = self.normalized_parts();
Self { mantissa, scale }
}
#[must_use]
pub const fn is_sign_negative(&self) -> bool {
self.mantissa < 0
}
#[must_use]
pub const fn scale(&self) -> u32 {
self.scale
}
#[must_use]
pub const fn mantissa(&self) -> i128 {
self.mantissa
}
#[must_use]
pub const fn is_zero(&self) -> bool {
self.mantissa == 0
}
const fn normalized_parts(&self) -> (i128, u32) {
Self::normalize_parts(self.mantissa, self.scale)
}
const fn checked_from_mantissa_scale(mantissa: i128, scale: u32) -> Option<Self> {
if scale <= MAX_SUPPORTED_SCALE {
return Some(Self { mantissa, scale });
}
let mut m = mantissa;
let mut s = scale;
while s > MAX_SUPPORTED_SCALE {
if m == 0 {
return Some(Self {
mantissa: 0,
scale: MAX_SUPPORTED_SCALE,
});
}
if m % 10 != 0 {
return None;
}
m /= 10;
s -= 1;
}
Some(Self {
mantissa: m,
scale: s,
})
}
const fn checked_pow10(power: u32) -> Option<i128> {
10i128.checked_pow(power)
}
fn decimal_integer_value(mantissa: i128, scale: u32) -> Option<i128> {
if scale == 0 {
return Some(mantissa);
}
let divisor = Self::checked_pow10(scale)?;
if mantissa % divisor != 0 {
return None;
}
Some(mantissa / divisor)
}
const fn normalize_parts(mantissa: i128, scale: u32) -> (i128, u32) {
if mantissa == 0 {
return (0, 0);
}
let mut m = mantissa;
let mut s = scale;
while s > 0 {
if m % 10 != 0 {
break;
}
m /= 10;
s -= 1;
}
(m, s)
}
const fn saturating_extreme(scale: u32, negative: bool) -> Self {
let mantissa = if negative { i128::MIN } else { i128::MAX };
Self { mantissa, scale }
}
}
impl NumericValue for Decimal {
fn try_to_decimal(&self) -> Option<Self> {
Some(*self)
}
fn try_from_decimal(value: Decimal) -> Option<Self> {
Some(value)
}
}