mod decimal64;
mod decimal64_no_scale;
mod encoding;
use std::cmp::Ordering;
use std::fmt;
use std::hash::{Hash, Hasher};
use std::str::FromStr;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
pub use decimal64::{Decimal64, MAX_DECIMAL64_PRECISION, MAX_DECIMAL64_SCALE};
pub use decimal64_no_scale::{
Decimal64NoScale, MAX_DECIMAL64_NO_SCALE_PRECISION, MAX_DECIMAL64_NO_SCALE_SCALE,
};
pub use encoding::DecimalError;
pub use encoding::SpecialValue;
use encoding::{
decode_special_value, decode_to_string, decode_to_string_with_scale, encode_decimal,
encode_decimal_with_constraints, encode_special_value, ENCODING_NAN, ENCODING_NEG_INFINITY,
ENCODING_POS_INFINITY,
};
#[derive(Clone)]
pub struct Decimal {
bytes: Vec<u8>,
}
impl Decimal {
pub fn with_precision_scale(
s: &str,
precision: Option<u32>,
scale: Option<i32>,
) -> Result<Self, DecimalError> {
let bytes = encode_decimal_with_constraints(s, precision, scale)?;
Ok(Self { bytes })
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, DecimalError> {
let _ = decode_to_string(bytes)?;
Ok(Self {
bytes: bytes.to_vec(),
})
}
#[inline]
pub fn from_bytes_unchecked(bytes: Vec<u8>) -> Self {
Self { bytes }
}
#[inline]
pub fn as_bytes(&self) -> &[u8] {
&self.bytes
}
#[inline]
pub fn into_bytes(self) -> Vec<u8> {
self.bytes
}
pub fn is_zero(&self) -> bool {
self.bytes.len() == 1 && self.bytes[0] == encoding::SIGN_ZERO
}
pub fn is_negative(&self) -> bool {
!self.bytes.is_empty() && self.bytes[0] == encoding::SIGN_NEGATIVE
}
pub fn is_positive(&self) -> bool {
!self.bytes.is_empty() && self.bytes[0] == encoding::SIGN_POSITIVE
}
pub fn is_pos_infinity(&self) -> bool {
self.bytes.as_slice() == ENCODING_POS_INFINITY
}
pub fn is_neg_infinity(&self) -> bool {
self.bytes.as_slice() == ENCODING_NEG_INFINITY
}
pub fn is_infinity(&self) -> bool {
self.is_pos_infinity() || self.is_neg_infinity()
}
pub fn is_nan(&self) -> bool {
self.bytes.as_slice() == ENCODING_NAN
}
pub fn is_special(&self) -> bool {
decode_special_value(&self.bytes).is_some()
}
pub fn is_finite(&self) -> bool {
!self.is_special()
}
#[inline]
pub fn byte_len(&self) -> usize {
self.bytes.len()
}
pub fn infinity() -> Self {
Self {
bytes: encode_special_value(SpecialValue::Infinity),
}
}
pub fn neg_infinity() -> Self {
Self {
bytes: encode_special_value(SpecialValue::NegInfinity),
}
}
pub fn nan() -> Self {
Self {
bytes: encode_special_value(SpecialValue::NaN),
}
}
pub fn to_string_with_scale(&self, scale: i32) -> String {
decode_to_string_with_scale(&self.bytes, scale).expect("Decimal contains valid bytes")
}
}
impl FromStr for Decimal {
type Err = DecimalError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let bytes = encode_decimal(s)?;
Ok(Self { bytes })
}
}
impl fmt::Display for Decimal {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = decode_to_string(&self.bytes).expect("Decimal contains valid bytes");
write!(f, "{}", s)
}
}
impl fmt::Debug for Decimal {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let value = decode_to_string(&self.bytes).expect("Decimal contains valid bytes");
f.debug_struct("Decimal")
.field("value", &value)
.field("bytes", &self.bytes)
.finish()
}
}
impl PartialEq for Decimal {
fn eq(&self, other: &Self) -> bool {
self.bytes == other.bytes
}
}
impl Eq for Decimal {}
impl PartialOrd for Decimal {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Decimal {
fn cmp(&self, other: &Self) -> Ordering {
self.bytes.cmp(&other.bytes)
}
}
impl Hash for Decimal {
fn hash<H: Hasher>(&self, state: &mut H) {
self.bytes.hash(state);
}
}
impl Serialize for Decimal {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
impl<'de> Deserialize<'de> for Decimal {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
Decimal::from_str(&s).map_err(serde::de::Error::custom)
}
}
macro_rules! impl_from_int {
($($t:ty),*) => {
$(
impl From<$t> for Decimal {
fn from(val: $t) -> Self {
Decimal::from_str(&val.to_string()).expect("Integer is always valid")
}
}
)*
};
}
impl_from_int!(i8, i16, i32, i64, i128, u8, u16, u32, u64, u128);
impl TryFrom<f64> for Decimal {
type Error = DecimalError;
fn try_from(val: f64) -> Result<Self, Self::Error> {
if val.is_nan() {
return Ok(Decimal::nan());
}
if val.is_infinite() {
return Ok(if val.is_sign_positive() {
Decimal::infinity()
} else {
Decimal::neg_infinity()
});
}
Decimal::from_str(&val.to_string())
}
}
impl Default for Decimal {
fn default() -> Self {
Decimal {
bytes: vec![encoding::SIGN_ZERO],
}
}
}
#[cfg(feature = "rust_decimal")]
mod rust_decimal_interop {
use super::{Decimal, DecimalError};
use std::str::FromStr;
impl TryFrom<rust_decimal::Decimal> for Decimal {
type Error = DecimalError;
fn try_from(value: rust_decimal::Decimal) -> Result<Self, Self::Error> {
Decimal::from_str(&value.to_string())
}
}
impl TryFrom<&Decimal> for rust_decimal::Decimal {
type Error = rust_decimal::Error;
fn try_from(value: &Decimal) -> Result<Self, Self::Error> {
use std::str::FromStr;
rust_decimal::Decimal::from_str(&value.to_string())
}
}
impl TryFrom<Decimal> for rust_decimal::Decimal {
type Error = rust_decimal::Error;
fn try_from(value: Decimal) -> Result<Self, Self::Error> {
rust_decimal::Decimal::try_from(&value)
}
}
}
#[cfg(feature = "bigdecimal")]
mod bigdecimal_interop {
use super::{Decimal, DecimalError};
use std::str::FromStr;
impl TryFrom<bigdecimal::BigDecimal> for Decimal {
type Error = DecimalError;
fn try_from(value: bigdecimal::BigDecimal) -> Result<Self, Self::Error> {
Decimal::from_str(&value.to_string())
}
}
impl TryFrom<&bigdecimal::BigDecimal> for Decimal {
type Error = DecimalError;
fn try_from(value: &bigdecimal::BigDecimal) -> Result<Self, Self::Error> {
Decimal::from_str(&value.to_string())
}
}
impl TryFrom<&Decimal> for bigdecimal::BigDecimal {
type Error = bigdecimal::ParseBigDecimalError;
fn try_from(value: &Decimal) -> Result<Self, Self::Error> {
use std::str::FromStr;
bigdecimal::BigDecimal::from_str(&value.to_string())
}
}
impl TryFrom<Decimal> for bigdecimal::BigDecimal {
type Error = bigdecimal::ParseBigDecimalError;
fn try_from(value: Decimal) -> Result<Self, Self::Error> {
bigdecimal::BigDecimal::try_from(&value)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_from_str() {
let d = Decimal::from_str("123.456").unwrap();
assert_eq!(d.to_string(), "123.456");
}
#[test]
fn test_zero() {
let d = Decimal::from_str("0").unwrap();
assert!(d.is_zero());
assert!(!d.is_negative());
assert!(!d.is_positive());
assert!(d.is_finite());
assert!(!d.is_special());
}
#[test]
fn test_negative() {
let d = Decimal::from_str("-123.456").unwrap();
assert!(d.is_negative());
assert!(!d.is_zero());
assert!(!d.is_positive());
assert!(d.is_finite());
}
#[test]
fn test_positive() {
let d = Decimal::from_str("123.456").unwrap();
assert!(d.is_positive());
assert!(!d.is_zero());
assert!(!d.is_negative());
assert!(d.is_finite());
}
#[test]
fn test_ordering() {
let values = vec!["-100", "-10", "-1", "-0.1", "0", "0.1", "1", "10", "100"];
let decimals: Vec<Decimal> = values
.iter()
.map(|s| Decimal::from_str(s).unwrap())
.collect();
for i in 0..decimals.len() - 1 {
assert!(
decimals[i] < decimals[i + 1],
"{} should be < {}",
values[i],
values[i + 1]
);
}
for i in 0..decimals.len() - 1 {
assert!(
decimals[i].as_bytes() < decimals[i + 1].as_bytes(),
"bytes of {} should be < bytes of {}",
values[i],
values[i + 1]
);
}
}
#[test]
fn test_roundtrip() {
let values = vec![
"0", "1", "-1", "123.456", "-123.456", "0.001", "0.1", "10", "100", "1000000",
"-1000000",
];
for s in values {
let d = Decimal::from_str(s).unwrap();
let bytes = d.as_bytes();
let restored = Decimal::from_bytes(bytes).unwrap();
assert_eq!(d, restored, "Roundtrip failed for {}", s);
}
}
#[test]
fn test_precision_scale() {
let d = Decimal::with_precision_scale("123.456", Some(10), Some(2)).unwrap();
assert_eq!(d.to_string(), "123.46");
let d = Decimal::with_precision_scale("12345.67", Some(5), Some(2)).unwrap();
assert_eq!(d.to_string(), "345.67");
let d = Decimal::with_precision_scale("99.999", Some(5), Some(2)).unwrap();
assert_eq!(d.to_string(), "100"); }
#[test]
fn test_from_integer() {
let d = Decimal::from(42i64);
assert_eq!(d.to_string(), "42");
let d = Decimal::from(-100i32);
assert_eq!(d.to_string(), "-100");
}
#[test]
fn test_from_f64() {
let d: Decimal = 123.456f64.try_into().unwrap();
assert_eq!(d.to_string(), "123.456");
let d: Decimal = (-99.5f64).try_into().unwrap();
assert_eq!(d.to_string(), "-99.5");
let d: Decimal = 0.0f64.try_into().unwrap();
assert!(d.is_zero());
let inf: Decimal = f64::INFINITY.try_into().unwrap();
assert!(inf.is_pos_infinity());
let neg_inf: Decimal = f64::NEG_INFINITY.try_into().unwrap();
assert!(neg_inf.is_neg_infinity());
let nan: Decimal = f64::NAN.try_into().unwrap();
assert!(nan.is_nan());
}
#[test]
fn test_serialization() {
let d = Decimal::from_str("123.456").unwrap();
let json = serde_json::to_string(&d).unwrap();
assert_eq!(json, "\"123.456\"");
let restored: Decimal = serde_json::from_str(&json).unwrap();
assert_eq!(d, restored);
}
#[test]
fn test_byte_efficiency() {
let d = Decimal::from_str("123456789").unwrap();
assert!(
d.byte_len() <= 10,
"Expected <= 10 bytes, got {}",
d.byte_len()
);
let d = Decimal::from_str("0.000001").unwrap();
assert!(
d.byte_len() <= 6,
"Expected <= 6 bytes, got {}",
d.byte_len()
);
}
#[test]
fn test_infinity_creation() {
let pos_inf = Decimal::infinity();
assert!(pos_inf.is_pos_infinity());
assert!(pos_inf.is_infinity());
assert!(!pos_inf.is_neg_infinity());
assert!(!pos_inf.is_nan());
assert!(pos_inf.is_special());
assert!(!pos_inf.is_finite());
assert_eq!(pos_inf.to_string(), "Infinity");
let neg_inf = Decimal::neg_infinity();
assert!(neg_inf.is_neg_infinity());
assert!(neg_inf.is_infinity());
assert!(!neg_inf.is_pos_infinity());
assert!(!neg_inf.is_nan());
assert!(neg_inf.is_special());
assert!(!neg_inf.is_finite());
assert_eq!(neg_inf.to_string(), "-Infinity");
}
#[test]
fn test_nan_creation() {
let nan = Decimal::nan();
assert!(nan.is_nan());
assert!(nan.is_special());
assert!(!nan.is_finite());
assert!(!nan.is_infinity());
assert!(!nan.is_zero());
assert_eq!(nan.to_string(), "NaN");
}
#[test]
fn test_special_value_from_str() {
let pos_inf = Decimal::from_str("Infinity").unwrap();
assert!(pos_inf.is_pos_infinity());
let neg_inf = Decimal::from_str("-Infinity").unwrap();
assert!(neg_inf.is_neg_infinity());
let nan = Decimal::from_str("NaN").unwrap();
assert!(nan.is_nan());
let inf = Decimal::from_str("infinity").unwrap();
assert!(inf.is_pos_infinity());
let inf = Decimal::from_str("INF").unwrap();
assert!(inf.is_pos_infinity());
}
#[test]
fn test_special_value_ordering() {
let neg_inf = Decimal::neg_infinity();
let neg_num = Decimal::from_str("-1000").unwrap();
let zero = Decimal::from_str("0").unwrap();
let pos_num = Decimal::from_str("1000").unwrap();
let pos_inf = Decimal::infinity();
let nan = Decimal::nan();
assert!(neg_inf < neg_num);
assert!(neg_num < zero);
assert!(zero < pos_num);
assert!(pos_num < pos_inf);
assert!(pos_inf < nan);
assert!(neg_inf.as_bytes() < neg_num.as_bytes());
assert!(neg_num.as_bytes() < zero.as_bytes());
assert!(zero.as_bytes() < pos_num.as_bytes());
assert!(pos_num.as_bytes() < pos_inf.as_bytes());
assert!(pos_inf.as_bytes() < nan.as_bytes());
}
#[test]
fn test_special_value_equality() {
let nan1 = Decimal::from_str("NaN").unwrap();
let nan2 = Decimal::from_str("nan").unwrap();
let nan3 = Decimal::nan();
assert_eq!(nan1, nan2);
assert_eq!(nan2, nan3);
let inf1 = Decimal::infinity();
let inf2 = Decimal::from_str("Infinity").unwrap();
assert_eq!(inf1, inf2);
let neg_inf1 = Decimal::neg_infinity();
let neg_inf2 = Decimal::from_str("-Infinity").unwrap();
assert_eq!(neg_inf1, neg_inf2);
}
#[test]
fn test_special_value_serialization() {
let inf = Decimal::infinity();
let json = serde_json::to_string(&inf).unwrap();
assert_eq!(json, "\"Infinity\"");
let restored: Decimal = serde_json::from_str(&json).unwrap();
assert_eq!(inf, restored);
let nan = Decimal::nan();
let json = serde_json::to_string(&nan).unwrap();
assert_eq!(json, "\"NaN\"");
let restored: Decimal = serde_json::from_str(&json).unwrap();
assert_eq!(nan, restored);
}
#[test]
fn test_special_value_byte_efficiency() {
assert_eq!(Decimal::infinity().byte_len(), 3);
assert_eq!(Decimal::neg_infinity().byte_len(), 3);
assert_eq!(Decimal::nan().byte_len(), 3);
}
#[test]
fn test_negative_scale() {
let d = Decimal::with_precision_scale("12345", Some(10), Some(-3)).unwrap();
assert_eq!(d.to_string(), "12000");
let d = Decimal::with_precision_scale("12500", Some(10), Some(-3)).unwrap();
assert_eq!(d.to_string(), "13000");
let d = Decimal::with_precision_scale("1234", Some(10), Some(-2)).unwrap();
assert_eq!(d.to_string(), "1200");
}
#[test]
fn test_negative_scale_with_precision() {
let d = Decimal::with_precision_scale("12345", Some(2), Some(-3)).unwrap();
assert_eq!(d.to_string(), "12000");
}
#[test]
fn test_invalid_format_errors() {
let result = Decimal::from_str("1.2.3");
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
DecimalError::InvalidFormat(_)
));
let result = Decimal::from_str("12abc");
assert!(result.is_err());
let result = Decimal::from_str("1e");
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
DecimalError::InvalidFormat(_)
));
let result = Decimal::from_str("1eabc");
assert!(result.is_err());
let d = Decimal::from_str("").unwrap();
assert!(d.is_zero());
let result = Decimal::from_str("-");
assert!(result.is_ok()); }
#[test]
fn test_leading_plus_sign() {
let d = Decimal::from_str("+123.456").unwrap();
assert_eq!(d.to_string(), "123.456");
assert!(d.is_positive());
}
#[test]
fn test_scientific_notation() {
let d = Decimal::from_str("1.5e10").unwrap();
assert_eq!(d.to_string(), "15000000000");
let d = Decimal::from_str("1.5E-3").unwrap();
assert_eq!(d.to_string(), "0.0015");
let d = Decimal::from_str("1e+5").unwrap();
assert_eq!(d.to_string(), "100000");
}
#[test]
fn test_leading_decimal_point() {
let d = Decimal::from_str(".5").unwrap();
assert_eq!(d.to_string(), "0.5");
let d = Decimal::from_str("-.25").unwrap();
assert_eq!(d.to_string(), "-0.25");
}
#[test]
fn test_trailing_zeros() {
let d = Decimal::from_str("100").unwrap();
assert_eq!(d.to_string(), "100");
let d = Decimal::from_str("1.500").unwrap();
assert_eq!(d.to_string(), "1.5");
}
#[test]
fn test_leading_zeros() {
let d = Decimal::from_str("007").unwrap();
assert_eq!(d.to_string(), "7");
let d = Decimal::from_str("00.123").unwrap();
assert_eq!(d.to_string(), "0.123");
}
#[test]
fn test_into_bytes() {
let d = Decimal::from_str("123.456").unwrap();
let bytes_ref = d.as_bytes().to_vec();
let bytes_owned = d.into_bytes();
assert_eq!(bytes_ref, bytes_owned);
}
#[test]
fn test_clone() {
let d1 = Decimal::from_str("123.456").unwrap();
let d2 = d1.clone();
assert_eq!(d1, d2);
assert_eq!(d1.as_bytes(), d2.as_bytes());
}
#[test]
fn test_hash() {
use std::collections::HashSet;
let mut set = HashSet::new();
set.insert(Decimal::from_str("123.456").unwrap());
set.insert(Decimal::from_str("123.456").unwrap()); set.insert(Decimal::from_str("789.012").unwrap());
assert_eq!(set.len(), 2);
assert!(set.contains(&Decimal::from_str("123.456").unwrap()));
}
#[test]
fn test_debug_format() {
let d = Decimal::from_str("123.456").unwrap();
let debug_str = format!("{:?}", d);
assert!(debug_str.contains("Decimal"));
assert!(debug_str.contains("123.456"));
}
#[test]
fn test_ord_trait() {
use std::cmp::Ordering;
let a = Decimal::from_str("1").unwrap();
let b = Decimal::from_str("2").unwrap();
let c = Decimal::from_str("1").unwrap();
assert_eq!(a.cmp(&b), Ordering::Less);
assert_eq!(b.cmp(&a), Ordering::Greater);
assert_eq!(a.cmp(&c), Ordering::Equal);
}
#[test]
fn test_from_bytes_invalid() {
let result = Decimal::from_bytes(&[]);
assert!(result.is_err());
let result = Decimal::from_bytes(&[0x00]);
assert!(result.is_err());
}
#[test]
fn test_deserialize_from_string_number() {
let d: Decimal = serde_json::from_str("\"42\"").unwrap();
assert_eq!(d.to_string(), "42");
let d: Decimal = serde_json::from_str("\"-100\"").unwrap();
assert_eq!(d.to_string(), "-100");
let d: Decimal = serde_json::from_str("\"1.5e10\"").unwrap();
assert_eq!(d.to_string(), "15000000000");
}
#[test]
fn test_from_various_integer_types() {
assert_eq!(Decimal::from(0i32).to_string(), "0");
assert_eq!(Decimal::from(i32::MAX).to_string(), "2147483647");
assert_eq!(Decimal::from(i32::MIN).to_string(), "-2147483648");
assert_eq!(Decimal::from(i64::MAX).to_string(), "9223372036854775807");
assert_eq!(Decimal::from(i64::MIN).to_string(), "-9223372036854775808");
}
#[test]
fn test_precision_overflow() {
let result = Decimal::from_str("1e20000");
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
DecimalError::PrecisionOverflow
));
let result = Decimal::from_str("1e-20000");
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
DecimalError::PrecisionOverflow
));
}
#[test]
fn test_all_zeros_variations() {
let d = Decimal::from_str("0").unwrap();
assert!(d.is_zero());
let d = Decimal::from_str("0.0").unwrap();
assert!(d.is_zero());
let d = Decimal::from_str("00.00").unwrap();
assert!(d.is_zero());
let d = Decimal::from_str("-0").unwrap();
assert!(d.is_zero());
}
#[test]
fn test_rounding_all_nines() {
let d = Decimal::with_precision_scale("99.999", Some(10), Some(2)).unwrap();
assert_eq!(d.to_string(), "100");
let d = Decimal::with_precision_scale("9.99", Some(10), Some(1)).unwrap();
assert_eq!(d.to_string(), "10");
let d = Decimal::with_precision_scale("999", Some(10), Some(-1)).unwrap();
assert_eq!(d.to_string(), "1000");
}
#[test]
fn test_negative_scale_small_number() {
let d = Decimal::with_precision_scale("4", Some(10), Some(-1)).unwrap();
assert_eq!(d.to_string(), "0");
let d = Decimal::with_precision_scale("5", Some(10), Some(-1)).unwrap();
assert_eq!(d.to_string(), "10");
let d = Decimal::with_precision_scale("-4", Some(10), Some(-1)).unwrap();
assert_eq!(d.to_string(), "0");
let d = Decimal::with_precision_scale("-5", Some(10), Some(-1)).unwrap();
assert_eq!(d.to_string(), "-10");
}
#[test]
fn test_precision_truncation() {
let d = Decimal::with_precision_scale("123456", Some(3), Some(0)).unwrap();
assert_eq!(d.to_string(), "456");
let d = Decimal::with_precision_scale("12345.67", Some(4), Some(2)).unwrap();
assert_eq!(d.to_string(), "45.67");
}
#[test]
fn test_very_small_numbers() {
let d = Decimal::from_str("0.000000001").unwrap();
assert_eq!(d.to_string(), "0.000000001");
assert!(d.is_positive());
let d = Decimal::from_str("-0.000000001").unwrap();
assert_eq!(d.to_string(), "-0.000000001");
assert!(d.is_negative());
}
#[test]
fn test_very_large_numbers() {
let d = Decimal::from_str("999999999999999999999999999999").unwrap();
assert_eq!(d.to_string(), "999999999999999999999999999999");
let d = Decimal::from_str("-999999999999999999999999999999").unwrap();
assert_eq!(d.to_string(), "-999999999999999999999999999999");
}
#[test]
fn test_max_exponent_boundary() {
let d = Decimal::from_str("1e16000").unwrap();
assert!(d.is_positive());
let result = Decimal::from_str("1e17000");
assert!(result.is_err());
}
#[test]
fn test_min_exponent_boundary() {
let d = Decimal::from_str("1e-16000").unwrap();
assert!(d.is_positive());
let result = Decimal::from_str("1e-17000");
assert!(result.is_err());
}
#[test]
fn test_odd_digit_count() {
let d = Decimal::from_str("12345").unwrap();
assert_eq!(d.to_string(), "12345");
let d = Decimal::from_str("1").unwrap();
assert_eq!(d.to_string(), "1");
let d = Decimal::from_str("123").unwrap();
assert_eq!(d.to_string(), "123");
}
#[test]
fn test_negative_number_ordering() {
let a = Decimal::from_str("-100").unwrap();
let b = Decimal::from_str("-10").unwrap();
let c = Decimal::from_str("-1").unwrap();
assert!(a < b);
assert!(b < c);
assert!(a.as_bytes() < b.as_bytes());
assert!(b.as_bytes() < c.as_bytes());
}
#[test]
fn test_from_bytes_unchecked_roundtrip() {
let original = Decimal::from_str("123.456").unwrap();
let bytes = original.as_bytes().to_vec();
let restored = Decimal::from_bytes_unchecked(bytes);
assert_eq!(original, restored);
}
#[test]
fn test_special_value_checks() {
let d = Decimal::from_str("123.456").unwrap();
assert!(!d.is_nan());
assert!(!d.is_infinity());
assert!(!d.is_pos_infinity());
assert!(!d.is_neg_infinity());
assert!(!d.is_special());
assert!(d.is_finite());
}
#[test]
fn test_equality_and_hash_consistency() {
use std::collections::HashMap;
let d1 = Decimal::from_str("123.456").unwrap();
let d2 = Decimal::from_str("123.456").unwrap();
let d3 = Decimal::from_str("123.457").unwrap();
assert_eq!(d1, d2);
assert_ne!(d1, d3);
let mut map = HashMap::new();
map.insert(d1.clone(), "first");
map.insert(d2.clone(), "second"); assert_eq!(map.len(), 1);
assert_eq!(map.get(&d1), Some(&"second"));
}
#[test]
fn test_scale_zero() {
let d = Decimal::with_precision_scale("123.999", Some(10), Some(0)).unwrap();
assert_eq!(d.to_string(), "124"); }
#[test]
fn test_only_fractional_with_precision_scale() {
let d = Decimal::with_precision_scale(".5", Some(10), Some(2)).unwrap();
assert_eq!(d.to_string(), "0.5");
}
#[test]
fn test_default_impl() {
let d = Decimal::default();
assert!(d.is_zero());
assert_eq!(d.to_string(), "0");
}
#[test]
fn test_precision_zero_integer_digits() {
let d = Decimal::with_precision_scale("123.456", Some(2), Some(2)).unwrap();
assert_eq!(d.to_string(), "0.46");
}
#[test]
fn test_negative_with_precision_truncation() {
let d = Decimal::with_precision_scale("-123.456", Some(3), Some(2)).unwrap();
assert_eq!(d.to_string(), "-3.46");
}
#[test]
fn test_invalid_sign_byte() {
let result = Decimal::from_bytes(&[0x01, 0x40, 0x00, 0x12]);
assert!(result.is_err());
let result = Decimal::from_bytes(&[0x7F, 0x40, 0x00, 0x12]);
assert!(result.is_err());
let result = Decimal::from_bytes(&[0xFE, 0x40, 0x00, 0x12]);
assert!(result.is_err());
}
#[test]
fn test_invalid_bcd_encoding() {
let invalid_bytes = vec![
0xFF, 0x80, 0x00, 0xAB, ];
let result = Decimal::from_bytes(&invalid_bytes);
assert!(result.is_err());
let invalid_bytes = vec![
0xFF, 0x80, 0x00, 0xA1, ];
let result = Decimal::from_bytes(&invalid_bytes);
assert!(result.is_err());
let invalid_bytes = vec![
0xFF, 0x80, 0x00, 0x1B, ];
let result = Decimal::from_bytes(&invalid_bytes);
assert!(result.is_err());
}
#[test]
fn test_reserved_exponent_positive() {
let bytes_with_reserved_exp = vec![
0xFF, 0xFF, 0xFF, 0x12, ];
let result = Decimal::from_bytes(&bytes_with_reserved_exp);
assert!(result.is_err());
let bytes_with_reserved_exp = vec![
0xFF, 0xFF, 0xFE, 0x12, ];
let result = Decimal::from_bytes(&bytes_with_reserved_exp);
assert!(result.is_err());
}
#[test]
fn test_reserved_exponent_negative() {
let bytes_with_reserved_exp = vec![
0x00, 0x00, 0x00, 0x12, ];
let result = Decimal::from_bytes(&bytes_with_reserved_exp);
assert!(result.is_err());
}
#[test]
fn test_empty_mantissa_bytes() {
let bytes_no_mantissa = vec![
0xFF, 0x80, 0x00, ];
let d = Decimal::from_bytes(&bytes_no_mantissa).unwrap();
assert_eq!(d.to_string(), "0");
}
#[cfg(feature = "rust_decimal")]
mod rust_decimal_tests {
use super::*;
#[test]
fn test_from_rust_decimal() {
use rust_decimal::Decimal as RustDecimal;
let rd = RustDecimal::new(12345, 2); let d: Decimal = rd.try_into().unwrap();
assert_eq!(d.to_string(), "123.45");
}
#[test]
fn test_to_rust_decimal() {
use rust_decimal::Decimal as RustDecimal;
let d = Decimal::from_str("123.45").unwrap();
let rd: RustDecimal = (&d).try_into().unwrap();
assert_eq!(rd.to_string(), "123.45");
}
#[test]
fn test_rust_decimal_roundtrip() {
use rust_decimal::Decimal as RustDecimal;
let values = vec!["0", "1", "-1", "123.456", "-999.999", "0.001"];
for s in values {
let d = Decimal::from_str(s).unwrap();
let rd: RustDecimal = (&d).try_into().unwrap();
let d2: Decimal = rd.try_into().unwrap();
assert_eq!(d, d2, "Roundtrip failed for {}", s);
}
}
#[test]
fn test_rust_decimal_arithmetic() {
use rust_decimal::Decimal as RustDecimal;
let a = Decimal::from_str("100.50").unwrap();
let b = Decimal::from_str("25.25").unwrap();
let ra: RustDecimal = (&a).try_into().unwrap();
let rb: RustDecimal = (&b).try_into().unwrap();
let sum = ra + rb;
let result: Decimal = sum.try_into().unwrap();
assert_eq!(result.to_string(), "125.75");
}
#[test]
fn test_rust_decimal_from_owned() {
use rust_decimal::Decimal as RustDecimal;
let d = Decimal::from_str("456.789").unwrap();
let rd: RustDecimal = d.try_into().unwrap();
assert_eq!(rd.to_string(), "456.789");
}
#[test]
fn test_rust_decimal_special_values_fail() {
use rust_decimal::Decimal as RustDecimal;
let inf = Decimal::infinity();
let result: Result<RustDecimal, _> = (&inf).try_into();
assert!(result.is_err());
let nan = Decimal::nan();
let result: Result<RustDecimal, _> = (&nan).try_into();
assert!(result.is_err());
}
}
#[cfg(feature = "bigdecimal")]
mod bigdecimal_tests {
use super::*;
#[test]
fn test_from_bigdecimal() {
use bigdecimal::BigDecimal;
use std::str::FromStr;
let bd = BigDecimal::from_str("123.45").unwrap();
let d: Decimal = bd.try_into().unwrap();
assert_eq!(d.to_string(), "123.45");
}
#[test]
fn test_to_bigdecimal() {
use bigdecimal::BigDecimal;
let d = Decimal::from_str("123.45").unwrap();
let bd: BigDecimal = (&d).try_into().unwrap();
assert_eq!(bd.to_string(), "123.45");
}
#[test]
fn test_bigdecimal_roundtrip() {
use bigdecimal::BigDecimal;
let values = vec!["0", "1", "-1", "123.456", "-999.999", "0.001"];
for s in values {
let d = Decimal::from_str(s).unwrap();
let bd: BigDecimal = (&d).try_into().unwrap();
let d2: Decimal = bd.try_into().unwrap();
assert_eq!(d, d2, "Roundtrip failed for {}", s);
}
}
#[test]
fn test_bigdecimal_from_owned() {
use bigdecimal::BigDecimal;
let d = Decimal::from_str("456.789").unwrap();
let bd: BigDecimal = d.try_into().unwrap();
assert_eq!(bd.to_string(), "456.789");
}
#[test]
fn test_bigdecimal_from_ref() {
use bigdecimal::BigDecimal;
use std::str::FromStr;
let bd = BigDecimal::from_str("789.012").unwrap();
let d: Decimal = (&bd).try_into().unwrap();
assert_eq!(d.to_string(), "789.012");
}
#[test]
fn test_bigdecimal_special_values_fail() {
use bigdecimal::BigDecimal;
let inf = Decimal::infinity();
let result: Result<BigDecimal, _> = (&inf).try_into();
assert!(result.is_err());
let nan = Decimal::nan();
let result: Result<BigDecimal, _> = (&nan).try_into();
assert!(result.is_err());
}
}
}