Skip to main content

decimal_bytes/
lib.rs

1//! # decimal-bytes
2//!
3//! Arbitrary precision decimals with lexicographically sortable byte encoding.
4//!
5//! This crate provides three decimal types optimized for database storage:
6//!
7//! - **[`Decimal`]**: Variable-length arbitrary precision (up to 131,072 digits)
8//! - **[`Decimal64`]**: Fixed 8-byte representation with embedded scale (precision ≤ 16 digits)
9//! - **[`Decimal64NoScale`]**: Fixed 8-byte representation with external scale (precision ≤ 18 digits)
10//!
11//! All types support PostgreSQL special values (NaN, ±Infinity) with correct sort ordering.
12//!
13//! ## When to Use Which
14//!
15//! | Type | Precision | Scale | Storage | Best For |
16//! |------|-----------|-------|---------|----------|
17//! | `Decimal64NoScale` | ≤ 18 digits | External | 8 bytes | **Columnar storage**, aggregates |
18//! | `Decimal64` | ≤ 16 digits | Embedded | 8 bytes | Self-contained values |
19//! | `Decimal` | Unlimited | Unlimited | Variable | Scientific, very large numbers |
20//!
21//! ## Decimal64NoScale (Recommended for Columnar Storage)
22//!
23//! ```
24//! use decimal_bytes::Decimal64NoScale;
25//!
26//! // Scale is provided externally (e.g., from schema metadata)
27//! let scale = 2;
28//! let a = Decimal64NoScale::new("100.50", scale).unwrap();
29//! let b = Decimal64NoScale::new("200.25", scale).unwrap();
30//!
31//! // Aggregates work correctly - just sum the raw i64 values!
32//! let sum = a.value() + b.value();  // 30075
33//! let result = Decimal64NoScale::from_raw(sum);
34//! assert_eq!(result.to_string_with_scale(scale), "300.75");
35//! ```
36//!
37//! ## Decimal64 Example
38//!
39//! ```
40//! use decimal_bytes::Decimal64;
41//!
42//! // Create with embedded scale
43//! let price = Decimal64::new("99.99", 2).unwrap();
44//! assert_eq!(price.to_string(), "99.99");
45//! assert_eq!(price.scale(), 2);  // Scale is embedded!
46//!
47//! // With precision and scale (PostgreSQL NUMERIC semantics)
48//! let d = Decimal64::with_precision_scale("123.456", Some(5), Some(2)).unwrap();
49//! assert_eq!(d.to_string(), "123.46"); // Rounded
50//!
51//! // Parse with automatic scale detection
52//! let d: Decimal64 = "123.456".parse().unwrap();
53//! assert_eq!(d.scale(), 3);
54//!
55//! // Special values
56//! let inf = Decimal64::infinity();
57//! let nan = Decimal64::nan();
58//! assert!(price < inf);
59//! assert!(inf < nan);
60//! ```
61//!
62//! ## Decimal Example (Arbitrary Precision)
63//!
64//! ```
65//! use decimal_bytes::Decimal;
66//! use std::str::FromStr;
67//!
68//! // Create decimals
69//! let a = Decimal::from_str("123.456").unwrap();
70//! let b = Decimal::from_str("123.457").unwrap();
71//!
72//! // Byte comparison matches numerical comparison
73//! assert!(a.as_bytes() < b.as_bytes());
74//! assert!(a < b);
75//!
76//! // Special values (PostgreSQL compatible)
77//! let inf = Decimal::infinity();
78//! let nan = Decimal::nan();
79//! assert!(a < inf);
80//! assert!(inf < nan);
81//! ```
82//!
83//! ## Sort Order
84//!
85//! The lexicographic byte order matches PostgreSQL NUMERIC:
86//!
87//! ```text
88//! -Infinity < negative numbers < zero < positive numbers < +Infinity < NaN
89//! ```
90//!
91//! Both `Decimal` and `Decimal64` support this sort order including special values.
92//!
93//! ## Special Value Semantics (PostgreSQL vs IEEE 754)
94//!
95//! The `Decimal` type follows **PostgreSQL semantics** for special values:
96//!
97//! | Behavior | PostgreSQL / decimal-bytes | IEEE 754 float |
98//! |----------|---------------------------|----------------|
99//! | `NaN == NaN` | `true` | `false` |
100//! | `NaN` ordering | Greatest value (> Infinity) | Unordered |
101//! | `Infinity == Infinity` | `true` | `true` |
102//!
103//! ```
104//! use decimal_bytes::Decimal;
105//!
106//! let nan1 = Decimal::nan();
107//! let nan2 = Decimal::nan();
108//! let inf = Decimal::infinity();
109//!
110//! // NaN equals itself (PostgreSQL behavior, unlike IEEE 754)
111//! assert_eq!(nan1, nan2);
112//!
113//! // NaN is greater than everything, including Infinity
114//! assert!(nan1 > inf);
115//! ```
116//!
117//! This makes `Decimal` suitable for use in indexes, sorting, and deduplication
118//! where consistent ordering and equality semantics are required.
119
120mod decimal64;
121mod decimal64_no_scale;
122mod encoding;
123
124use std::cmp::Ordering;
125use std::fmt;
126use std::hash::{Hash, Hasher};
127use std::str::FromStr;
128
129use serde::{Deserialize, Deserializer, Serialize, Serializer};
130
131pub use decimal64::{Decimal64, MAX_DECIMAL64_PRECISION, MAX_DECIMAL64_SCALE};
132pub use decimal64_no_scale::{
133    Decimal64NoScale, MAX_DECIMAL64_NO_SCALE_PRECISION, MAX_DECIMAL64_NO_SCALE_SCALE,
134};
135pub use encoding::DecimalError;
136pub use encoding::SpecialValue;
137use encoding::{
138    decode_special_value, decode_to_string, decode_to_string_with_scale, encode_decimal,
139    encode_decimal_with_constraints, encode_special_value, ENCODING_NAN, ENCODING_NEG_INFINITY,
140    ENCODING_POS_INFINITY,
141};
142
143/// An arbitrary precision decimal number stored as sortable bytes.
144///
145/// The internal byte representation is designed to be lexicographically sortable,
146/// meaning that comparing the bytes directly yields the same result as comparing
147/// the numerical values. This enables efficient range queries in databases.
148///
149/// # Storage Efficiency
150///
151/// The encoding uses:
152/// - 1 byte for the sign
153/// - Variable bytes for the exponent (typically 1-3 bytes)
154/// - 4 bits per decimal digit (BCD encoding, 2 digits per byte)
155#[derive(Clone)]
156pub struct Decimal {
157    bytes: Vec<u8>,
158}
159
160impl Decimal {
161    /// Creates a new Decimal with precision and scale constraints.
162    ///
163    /// Values that exceed the constraints are truncated/rounded to fit.
164    /// This is compatible with SQL NUMERIC(precision, scale) semantics.
165    ///
166    /// - `precision`: Maximum total number of significant digits (None = unlimited)
167    /// - `scale`: Digits after decimal point; negative values round to left of decimal
168    ///
169    /// # PostgreSQL Compatibility
170    ///
171    /// Supports negative scale (rounds to powers of 10):
172    /// - `scale = -3` rounds to nearest 1000
173    /// - `NUMERIC(2, -3)` allows values like -99000 to 99000
174    ///
175    /// # Examples
176    ///
177    /// ```
178    /// use decimal_bytes::Decimal;
179    ///
180    /// // NUMERIC(5, 2) - up to 5 digits total, 2 after decimal
181    /// let d = Decimal::with_precision_scale("123.456", Some(5), Some(2)).unwrap();
182    /// assert_eq!(d.to_string(), "123.46"); // Rounded to 2 decimal places
183    ///
184    /// // NUMERIC(2, -3) - rounds to nearest 1000, max 2 significant digits
185    /// let d = Decimal::with_precision_scale("12345", Some(2), Some(-3)).unwrap();
186    /// assert_eq!(d.to_string(), "12000"); // Rounded to nearest 1000
187    /// ```
188    pub fn with_precision_scale(
189        s: &str,
190        precision: Option<u32>,
191        scale: Option<i32>,
192    ) -> Result<Self, DecimalError> {
193        let bytes = encode_decimal_with_constraints(s, precision, scale)?;
194        Ok(Self { bytes })
195    }
196
197    /// Creates a Decimal from raw bytes.
198    ///
199    /// The bytes must be a valid encoding produced by `as_bytes()`.
200    /// Returns an error if the bytes are invalid.
201    ///
202    /// # Examples
203    ///
204    /// ```
205    /// use decimal_bytes::Decimal;
206    /// use std::str::FromStr;
207    ///
208    /// let original = Decimal::from_str("123.456").unwrap();
209    /// let bytes = original.as_bytes();
210    /// let restored = Decimal::from_bytes(bytes).unwrap();
211    /// assert_eq!(original, restored);
212    /// ```
213    pub fn from_bytes(bytes: &[u8]) -> Result<Self, DecimalError> {
214        // Validate by attempting to decode
215        let _ = decode_to_string(bytes)?;
216        Ok(Self {
217            bytes: bytes.to_vec(),
218        })
219    }
220
221    /// Creates a Decimal from raw bytes without validation.
222    ///
223    /// # Safety
224    ///
225    /// The caller must ensure the bytes are a valid encoding.
226    /// Using invalid bytes may cause panics or incorrect results.
227    #[inline]
228    pub fn from_bytes_unchecked(bytes: Vec<u8>) -> Self {
229        Self { bytes }
230    }
231
232    /// Returns the raw byte representation.
233    ///
234    /// These bytes are lexicographically sortable - comparing them directly
235    /// yields the same result as comparing the numerical values.
236    #[inline]
237    pub fn as_bytes(&self) -> &[u8] {
238        &self.bytes
239    }
240
241    /// Consumes the Decimal and returns the underlying bytes.
242    #[inline]
243    pub fn into_bytes(self) -> Vec<u8> {
244        self.bytes
245    }
246
247    /// Returns true if this decimal represents zero.
248    pub fn is_zero(&self) -> bool {
249        self.bytes.len() == 1 && self.bytes[0] == encoding::SIGN_ZERO
250    }
251
252    /// Returns true if this decimal is negative.
253    pub fn is_negative(&self) -> bool {
254        !self.bytes.is_empty() && self.bytes[0] == encoding::SIGN_NEGATIVE
255    }
256
257    /// Returns true if this decimal is positive (and not zero).
258    pub fn is_positive(&self) -> bool {
259        !self.bytes.is_empty() && self.bytes[0] == encoding::SIGN_POSITIVE
260    }
261
262    /// Returns true if this decimal represents positive infinity.
263    pub fn is_pos_infinity(&self) -> bool {
264        self.bytes.as_slice() == ENCODING_POS_INFINITY
265    }
266
267    /// Returns true if this decimal represents negative infinity.
268    pub fn is_neg_infinity(&self) -> bool {
269        self.bytes.as_slice() == ENCODING_NEG_INFINITY
270    }
271
272    /// Returns true if this decimal represents positive or negative infinity.
273    pub fn is_infinity(&self) -> bool {
274        self.is_pos_infinity() || self.is_neg_infinity()
275    }
276
277    /// Returns true if this decimal represents NaN (Not a Number).
278    pub fn is_nan(&self) -> bool {
279        self.bytes.as_slice() == ENCODING_NAN
280    }
281
282    /// Returns true if this decimal is a special value (Infinity or NaN).
283    pub fn is_special(&self) -> bool {
284        decode_special_value(&self.bytes).is_some()
285    }
286
287    /// Returns true if this decimal is a finite number (not Infinity or NaN).
288    pub fn is_finite(&self) -> bool {
289        !self.is_special()
290    }
291
292    /// Returns the number of bytes used to store this decimal.
293    #[inline]
294    pub fn byte_len(&self) -> usize {
295        self.bytes.len()
296    }
297
298    /// Creates positive infinity.
299    ///
300    /// Infinity is greater than all finite numbers but less than NaN.
301    /// Two positive infinities are equal to each other.
302    ///
303    /// # Example
304    ///
305    /// ```
306    /// use decimal_bytes::Decimal;
307    /// use std::str::FromStr;
308    ///
309    /// let inf = Decimal::infinity();
310    /// let big = Decimal::from_str("999999999999").unwrap();
311    /// assert!(inf > big);
312    /// assert_eq!(inf, Decimal::infinity());
313    /// ```
314    pub fn infinity() -> Self {
315        Self {
316            bytes: encode_special_value(SpecialValue::Infinity),
317        }
318    }
319
320    /// Creates negative infinity.
321    ///
322    /// Negative infinity is less than all finite numbers and positive infinity.
323    /// Two negative infinities are equal to each other.
324    ///
325    /// # Example
326    ///
327    /// ```
328    /// use decimal_bytes::Decimal;
329    /// use std::str::FromStr;
330    ///
331    /// let neg_inf = Decimal::neg_infinity();
332    /// let small = Decimal::from_str("-999999999999").unwrap();
333    /// assert!(neg_inf < small);
334    /// assert_eq!(neg_inf, Decimal::neg_infinity());
335    /// ```
336    pub fn neg_infinity() -> Self {
337        Self {
338            bytes: encode_special_value(SpecialValue::NegInfinity),
339        }
340    }
341
342    /// Creates NaN (Not a Number).
343    ///
344    /// # PostgreSQL Semantics
345    ///
346    /// Unlike IEEE 754 floating-point where `NaN != NaN`, this follows PostgreSQL
347    /// semantics where:
348    /// - `NaN == NaN` is `true`
349    /// - `NaN` is the greatest value (greater than positive infinity)
350    /// - All NaN values are equal regardless of how they were created
351    ///
352    /// This makes NaN usable in sorting, indexing, and deduplication.
353    ///
354    /// # Example
355    ///
356    /// ```
357    /// use decimal_bytes::Decimal;
358    /// use std::str::FromStr;
359    ///
360    /// let nan1 = Decimal::nan();
361    /// let nan2 = Decimal::from_str("NaN").unwrap();
362    /// let inf = Decimal::infinity();
363    ///
364    /// // NaN equals itself (PostgreSQL behavior)
365    /// assert_eq!(nan1, nan2);
366    ///
367    /// // NaN is greater than everything
368    /// assert!(nan1 > inf);
369    /// ```
370    pub fn nan() -> Self {
371        Self {
372            bytes: encode_special_value(SpecialValue::NaN),
373        }
374    }
375
376    /// Converts this decimal to a string with a specific scale (number of decimal places).
377    ///
378    /// This ensures the output has exactly `scale` decimal places, adding trailing
379    /// zeros if needed. Useful for PostgreSQL NUMERIC display formatting where
380    /// the scale defines the display format.
381    ///
382    /// Special values (NaN, Infinity, -Infinity) are returned as-is without scale formatting.
383    ///
384    /// # Arguments
385    /// * `scale` - Number of decimal places to ensure in the output. Must be non-negative.
386    ///
387    /// # Examples
388    ///
389    /// ```
390    /// use decimal_bytes::Decimal;
391    /// use std::str::FromStr;
392    ///
393    /// let d = Decimal::from_str("1").unwrap();
394    /// assert_eq!(d.to_string_with_scale(18), "1.000000000000000000");
395    ///
396    /// let d = Decimal::from_str("1.5").unwrap();
397    /// assert_eq!(d.to_string_with_scale(3), "1.500");
398    ///
399    /// // Special values are unchanged
400    /// let nan = Decimal::nan();
401    /// assert_eq!(nan.to_string_with_scale(10), "NaN");
402    /// ```
403    pub fn to_string_with_scale(&self, scale: i32) -> String {
404        decode_to_string_with_scale(&self.bytes, scale).expect("Decimal contains valid bytes")
405    }
406}
407
408impl FromStr for Decimal {
409    type Err = DecimalError;
410
411    /// Creates a new Decimal from a string representation.
412    ///
413    /// # Examples
414    ///
415    /// ```
416    /// use decimal_bytes::Decimal;
417    /// use std::str::FromStr;
418    ///
419    /// let d = Decimal::from_str("123.456").unwrap();
420    /// let d = Decimal::from_str("-0.001").unwrap();
421    /// let d = Decimal::from_str("1e10").unwrap();
422    /// // Or use parse:
423    /// let d: Decimal = "42.5".parse().unwrap();
424    /// ```
425    fn from_str(s: &str) -> Result<Self, Self::Err> {
426        let bytes = encode_decimal(s)?;
427        Ok(Self { bytes })
428    }
429}
430
431impl fmt::Display for Decimal {
432    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
433        let s = decode_to_string(&self.bytes).expect("Decimal contains valid bytes");
434        write!(f, "{}", s)
435    }
436}
437
438impl fmt::Debug for Decimal {
439    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
440        let value = decode_to_string(&self.bytes).expect("Decimal contains valid bytes");
441        f.debug_struct("Decimal")
442            .field("value", &value)
443            .field("bytes", &self.bytes)
444            .finish()
445    }
446}
447
448impl PartialEq for Decimal {
449    fn eq(&self, other: &Self) -> bool {
450        self.bytes == other.bytes
451    }
452}
453
454impl Eq for Decimal {}
455
456impl PartialOrd for Decimal {
457    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
458        Some(self.cmp(other))
459    }
460}
461
462impl Ord for Decimal {
463    fn cmp(&self, other: &Self) -> Ordering {
464        // Byte comparison is equivalent to numerical comparison
465        self.bytes.cmp(&other.bytes)
466    }
467}
468
469impl Hash for Decimal {
470    fn hash<H: Hasher>(&self, state: &mut H) {
471        self.bytes.hash(state);
472    }
473}
474
475impl Serialize for Decimal {
476    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
477    where
478        S: Serializer,
479    {
480        // Serialize as string for human readability in JSON
481        serializer.serialize_str(&self.to_string())
482    }
483}
484
485impl<'de> Deserialize<'de> for Decimal {
486    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
487    where
488        D: Deserializer<'de>,
489    {
490        let s = String::deserialize(deserializer)?;
491        Decimal::from_str(&s).map_err(serde::de::Error::custom)
492    }
493}
494
495// Conversion from integer types
496macro_rules! impl_from_int {
497    ($($t:ty),*) => {
498        $(
499            impl From<$t> for Decimal {
500                fn from(val: $t) -> Self {
501                    // Integer conversion is infallible - always produces valid decimal bytes.
502                    // Uses string conversion internally since the byte encoding is designed
503                    // around decimal string parsing (BCD encoding of digit pairs).
504                    Decimal::from_str(&val.to_string()).expect("Integer is always valid")
505                }
506            }
507        )*
508    };
509}
510
511impl_from_int!(i8, i16, i32, i64, i128, u8, u16, u32, u64, u128);
512
513impl TryFrom<f64> for Decimal {
514    type Error = DecimalError;
515
516    /// Converts an f64 to a Decimal.
517    ///
518    /// Special values are handled:
519    /// - `f64::NAN` → `Decimal::nan()`
520    /// - `f64::INFINITY` → `Decimal::infinity()`
521    /// - `f64::NEG_INFINITY` → `Decimal::neg_infinity()`
522    ///
523    /// Note: Due to f64's limited precision (~15-17 significant digits),
524    /// very precise decimal values may lose precision when converted from f64.
525    /// For exact decimal representation, use `Decimal::from_str()` instead.
526    ///
527    /// # Example
528    ///
529    /// ```
530    /// use decimal_bytes::Decimal;
531    ///
532    /// let d: Decimal = 123.456f64.try_into().unwrap();
533    /// assert_eq!(d.to_string(), "123.456");
534    ///
535    /// let inf: Decimal = f64::INFINITY.try_into().unwrap();
536    /// assert!(inf.is_pos_infinity());
537    ///
538    /// let nan: Decimal = f64::NAN.try_into().unwrap();
539    /// assert!(nan.is_nan());
540    /// ```
541    fn try_from(val: f64) -> Result<Self, Self::Error> {
542        if val.is_nan() {
543            return Ok(Decimal::nan());
544        }
545        if val.is_infinite() {
546            return Ok(if val.is_sign_positive() {
547                Decimal::infinity()
548            } else {
549                Decimal::neg_infinity()
550            });
551        }
552        Decimal::from_str(&val.to_string())
553    }
554}
555
556impl Default for Decimal {
557    fn default() -> Self {
558        Decimal {
559            bytes: vec![encoding::SIGN_ZERO],
560        }
561    }
562}
563
564// ============================================================================
565// Optional: rust_decimal interop (enabled with "rust_decimal" feature)
566// ============================================================================
567
568#[cfg(feature = "rust_decimal")]
569mod rust_decimal_interop {
570    use super::{Decimal, DecimalError};
571    use std::str::FromStr;
572
573    impl TryFrom<rust_decimal::Decimal> for Decimal {
574        type Error = DecimalError;
575
576        /// Converts a `rust_decimal::Decimal` to a `decimal_bytes::Decimal`.
577        ///
578        /// # Example
579        ///
580        /// ```ignore
581        /// use rust_decimal::Decimal as RustDecimal;
582        /// use decimal_bytes::Decimal;
583        ///
584        /// let rd = RustDecimal::new(12345, 2); // 123.45
585        /// let d: Decimal = rd.try_into().unwrap();
586        /// assert_eq!(d.to_string(), "123.45");
587        /// ```
588        fn try_from(value: rust_decimal::Decimal) -> Result<Self, Self::Error> {
589            Decimal::from_str(&value.to_string())
590        }
591    }
592
593    impl TryFrom<&Decimal> for rust_decimal::Decimal {
594        type Error = rust_decimal::Error;
595
596        /// Converts a `decimal_bytes::Decimal` to a `rust_decimal::Decimal`.
597        ///
598        /// Note: This may fail if the decimal exceeds rust_decimal's precision limits
599        /// (28-29 significant digits) or if it's a special value (Infinity, NaN).
600        ///
601        /// # Example
602        ///
603        /// ```ignore
604        /// use rust_decimal::Decimal as RustDecimal;
605        /// use decimal_bytes::Decimal;
606        ///
607        /// let d = Decimal::from_str("123.45").unwrap();
608        /// let rd: RustDecimal = (&d).try_into().unwrap();
609        /// assert_eq!(rd.to_string(), "123.45");
610        /// ```
611        fn try_from(value: &Decimal) -> Result<Self, Self::Error> {
612            use std::str::FromStr;
613            rust_decimal::Decimal::from_str(&value.to_string())
614        }
615    }
616
617    impl TryFrom<Decimal> for rust_decimal::Decimal {
618        type Error = rust_decimal::Error;
619
620        fn try_from(value: Decimal) -> Result<Self, Self::Error> {
621            rust_decimal::Decimal::try_from(&value)
622        }
623    }
624}
625
626// The rust_decimal_interop module only contains trait implementations,
627// so there's nothing to re-export. The TryFrom impls are automatically available.
628
629// ============================================================================
630// Optional: bigdecimal interop (enabled with "bigdecimal" feature)
631// ============================================================================
632
633#[cfg(feature = "bigdecimal")]
634mod bigdecimal_interop {
635    use super::{Decimal, DecimalError};
636    use std::str::FromStr;
637
638    impl TryFrom<bigdecimal::BigDecimal> for Decimal {
639        type Error = DecimalError;
640
641        /// Converts a `bigdecimal::BigDecimal` to a `decimal_bytes::Decimal`.
642        fn try_from(value: bigdecimal::BigDecimal) -> Result<Self, Self::Error> {
643            Decimal::from_str(&value.to_string())
644        }
645    }
646
647    impl TryFrom<&bigdecimal::BigDecimal> for Decimal {
648        type Error = DecimalError;
649
650        fn try_from(value: &bigdecimal::BigDecimal) -> Result<Self, Self::Error> {
651            Decimal::from_str(&value.to_string())
652        }
653    }
654
655    impl TryFrom<&Decimal> for bigdecimal::BigDecimal {
656        type Error = bigdecimal::ParseBigDecimalError;
657
658        /// Converts a `decimal_bytes::Decimal` to a `bigdecimal::BigDecimal`.
659        ///
660        /// Note: This may fail for special values (Infinity, NaN) which
661        /// bigdecimal doesn't support.
662        fn try_from(value: &Decimal) -> Result<Self, Self::Error> {
663            use std::str::FromStr;
664            bigdecimal::BigDecimal::from_str(&value.to_string())
665        }
666    }
667
668    impl TryFrom<Decimal> for bigdecimal::BigDecimal {
669        type Error = bigdecimal::ParseBigDecimalError;
670
671        fn try_from(value: Decimal) -> Result<Self, Self::Error> {
672            bigdecimal::BigDecimal::try_from(&value)
673        }
674    }
675}
676
677// The bigdecimal_interop module only contains trait implementations,
678// so there's nothing to re-export. The TryFrom impls are automatically available.
679
680#[cfg(test)]
681mod tests {
682    use super::*;
683
684    #[test]
685    fn test_from_str() {
686        let d = Decimal::from_str("123.456").unwrap();
687        assert_eq!(d.to_string(), "123.456");
688    }
689
690    #[test]
691    fn test_zero() {
692        let d = Decimal::from_str("0").unwrap();
693        assert!(d.is_zero());
694        assert!(!d.is_negative());
695        assert!(!d.is_positive());
696        assert!(d.is_finite());
697        assert!(!d.is_special());
698    }
699
700    #[test]
701    fn test_negative() {
702        let d = Decimal::from_str("-123.456").unwrap();
703        assert!(d.is_negative());
704        assert!(!d.is_zero());
705        assert!(!d.is_positive());
706        assert!(d.is_finite());
707    }
708
709    #[test]
710    fn test_positive() {
711        let d = Decimal::from_str("123.456").unwrap();
712        assert!(d.is_positive());
713        assert!(!d.is_zero());
714        assert!(!d.is_negative());
715        assert!(d.is_finite());
716    }
717
718    #[test]
719    fn test_ordering() {
720        let values = vec!["-100", "-10", "-1", "-0.1", "0", "0.1", "1", "10", "100"];
721        let decimals: Vec<Decimal> = values
722            .iter()
723            .map(|s| Decimal::from_str(s).unwrap())
724            .collect();
725
726        // Check that ordering is correct
727        for i in 0..decimals.len() - 1 {
728            assert!(
729                decimals[i] < decimals[i + 1],
730                "{} should be < {}",
731                values[i],
732                values[i + 1]
733            );
734        }
735
736        // Check that byte ordering matches
737        for i in 0..decimals.len() - 1 {
738            assert!(
739                decimals[i].as_bytes() < decimals[i + 1].as_bytes(),
740                "bytes of {} should be < bytes of {}",
741                values[i],
742                values[i + 1]
743            );
744        }
745    }
746
747    #[test]
748    fn test_roundtrip() {
749        let values = vec![
750            "0", "1", "-1", "123.456", "-123.456", "0.001", "0.1", "10", "100", "1000000",
751            "-1000000",
752        ];
753
754        for s in values {
755            let d = Decimal::from_str(s).unwrap();
756            let bytes = d.as_bytes();
757            let restored = Decimal::from_bytes(bytes).unwrap();
758            assert_eq!(d, restored, "Roundtrip failed for {}", s);
759        }
760    }
761
762    #[test]
763    fn test_precision_scale() {
764        // Round to 2 decimal places
765        let d = Decimal::with_precision_scale("123.456", Some(10), Some(2)).unwrap();
766        assert_eq!(d.to_string(), "123.46");
767
768        // When precision is exceeded, least significant integer digits are kept
769        let d = Decimal::with_precision_scale("12345.67", Some(5), Some(2)).unwrap();
770        assert_eq!(d.to_string(), "345.67"); // 5 digits total, 2 after decimal = 3 integer digits max
771
772        // Rounding within precision limits
773        let d = Decimal::with_precision_scale("99.999", Some(5), Some(2)).unwrap();
774        assert_eq!(d.to_string(), "100"); // Rounds up, fits in precision
775    }
776
777    #[test]
778    fn test_from_integer() {
779        let d = Decimal::from(42i64);
780        assert_eq!(d.to_string(), "42");
781
782        let d = Decimal::from(-100i32);
783        assert_eq!(d.to_string(), "-100");
784    }
785
786    #[test]
787    fn test_from_f64() {
788        // Normal f64 values
789        let d: Decimal = 123.456f64.try_into().unwrap();
790        assert_eq!(d.to_string(), "123.456");
791
792        let d: Decimal = (-99.5f64).try_into().unwrap();
793        assert_eq!(d.to_string(), "-99.5");
794
795        let d: Decimal = 0.0f64.try_into().unwrap();
796        assert!(d.is_zero());
797
798        // Special values
799        let inf: Decimal = f64::INFINITY.try_into().unwrap();
800        assert!(inf.is_pos_infinity());
801
802        let neg_inf: Decimal = f64::NEG_INFINITY.try_into().unwrap();
803        assert!(neg_inf.is_neg_infinity());
804
805        let nan: Decimal = f64::NAN.try_into().unwrap();
806        assert!(nan.is_nan());
807    }
808
809    #[test]
810    fn test_serialization() {
811        let d = Decimal::from_str("123.456").unwrap();
812        let json = serde_json::to_string(&d).unwrap();
813        assert_eq!(json, "\"123.456\"");
814
815        let restored: Decimal = serde_json::from_str(&json).unwrap();
816        assert_eq!(d, restored);
817    }
818
819    #[test]
820    fn test_byte_efficiency() {
821        // Check that storage is reasonably efficient
822        let d = Decimal::from_str("123456789").unwrap();
823        // 1 byte sign + ~2 bytes exponent + ~5 bytes mantissa (9 digits / 2)
824        assert!(
825            d.byte_len() <= 10,
826            "Expected <= 10 bytes, got {}",
827            d.byte_len()
828        );
829
830        let d = Decimal::from_str("0.000001").unwrap();
831        // Should be compact for small numbers too
832        assert!(
833            d.byte_len() <= 6,
834            "Expected <= 6 bytes, got {}",
835            d.byte_len()
836        );
837    }
838
839    // ==================== Special Values Tests ====================
840
841    #[test]
842    fn test_infinity_creation() {
843        let pos_inf = Decimal::infinity();
844        assert!(pos_inf.is_pos_infinity());
845        assert!(pos_inf.is_infinity());
846        assert!(!pos_inf.is_neg_infinity());
847        assert!(!pos_inf.is_nan());
848        assert!(pos_inf.is_special());
849        assert!(!pos_inf.is_finite());
850        assert_eq!(pos_inf.to_string(), "Infinity");
851
852        let neg_inf = Decimal::neg_infinity();
853        assert!(neg_inf.is_neg_infinity());
854        assert!(neg_inf.is_infinity());
855        assert!(!neg_inf.is_pos_infinity());
856        assert!(!neg_inf.is_nan());
857        assert!(neg_inf.is_special());
858        assert!(!neg_inf.is_finite());
859        assert_eq!(neg_inf.to_string(), "-Infinity");
860    }
861
862    #[test]
863    fn test_nan_creation() {
864        let nan = Decimal::nan();
865        assert!(nan.is_nan());
866        assert!(nan.is_special());
867        assert!(!nan.is_finite());
868        assert!(!nan.is_infinity());
869        assert!(!nan.is_zero());
870        assert_eq!(nan.to_string(), "NaN");
871    }
872
873    #[test]
874    fn test_special_value_from_str() {
875        let pos_inf = Decimal::from_str("Infinity").unwrap();
876        assert!(pos_inf.is_pos_infinity());
877
878        let neg_inf = Decimal::from_str("-Infinity").unwrap();
879        assert!(neg_inf.is_neg_infinity());
880
881        let nan = Decimal::from_str("NaN").unwrap();
882        assert!(nan.is_nan());
883
884        // Case-insensitive
885        let inf = Decimal::from_str("infinity").unwrap();
886        assert!(inf.is_pos_infinity());
887
888        let inf = Decimal::from_str("INF").unwrap();
889        assert!(inf.is_pos_infinity());
890    }
891
892    #[test]
893    fn test_special_value_ordering() {
894        // PostgreSQL order: -Infinity < negatives < zero < positives < Infinity < NaN
895        let neg_inf = Decimal::neg_infinity();
896        let neg_num = Decimal::from_str("-1000").unwrap();
897        let zero = Decimal::from_str("0").unwrap();
898        let pos_num = Decimal::from_str("1000").unwrap();
899        let pos_inf = Decimal::infinity();
900        let nan = Decimal::nan();
901
902        assert!(neg_inf < neg_num);
903        assert!(neg_num < zero);
904        assert!(zero < pos_num);
905        assert!(pos_num < pos_inf);
906        assert!(pos_inf < nan);
907
908        // Verify byte ordering matches
909        assert!(neg_inf.as_bytes() < neg_num.as_bytes());
910        assert!(neg_num.as_bytes() < zero.as_bytes());
911        assert!(zero.as_bytes() < pos_num.as_bytes());
912        assert!(pos_num.as_bytes() < pos_inf.as_bytes());
913        assert!(pos_inf.as_bytes() < nan.as_bytes());
914    }
915
916    #[test]
917    fn test_special_value_equality() {
918        // All NaNs are equal (PostgreSQL semantics)
919        let nan1 = Decimal::from_str("NaN").unwrap();
920        let nan2 = Decimal::from_str("nan").unwrap();
921        let nan3 = Decimal::nan();
922        assert_eq!(nan1, nan2);
923        assert_eq!(nan2, nan3);
924
925        // Infinities are equal to themselves
926        let inf1 = Decimal::infinity();
927        let inf2 = Decimal::from_str("Infinity").unwrap();
928        assert_eq!(inf1, inf2);
929
930        let neg_inf1 = Decimal::neg_infinity();
931        let neg_inf2 = Decimal::from_str("-Infinity").unwrap();
932        assert_eq!(neg_inf1, neg_inf2);
933    }
934
935    #[test]
936    fn test_special_value_serialization() {
937        let inf = Decimal::infinity();
938        let json = serde_json::to_string(&inf).unwrap();
939        assert_eq!(json, "\"Infinity\"");
940        let restored: Decimal = serde_json::from_str(&json).unwrap();
941        assert_eq!(inf, restored);
942
943        let nan = Decimal::nan();
944        let json = serde_json::to_string(&nan).unwrap();
945        assert_eq!(json, "\"NaN\"");
946        let restored: Decimal = serde_json::from_str(&json).unwrap();
947        assert_eq!(nan, restored);
948    }
949
950    #[test]
951    fn test_special_value_byte_efficiency() {
952        // Special values should be compact (3 bytes each)
953        assert_eq!(Decimal::infinity().byte_len(), 3);
954        assert_eq!(Decimal::neg_infinity().byte_len(), 3);
955        assert_eq!(Decimal::nan().byte_len(), 3);
956    }
957
958    // ==================== Negative Scale Tests ====================
959
960    #[test]
961    fn test_negative_scale() {
962        // Round to nearest 1000
963        let d = Decimal::with_precision_scale("12345", Some(10), Some(-3)).unwrap();
964        assert_eq!(d.to_string(), "12000");
965
966        // Round up
967        let d = Decimal::with_precision_scale("12500", Some(10), Some(-3)).unwrap();
968        assert_eq!(d.to_string(), "13000");
969
970        // Round to nearest 100
971        let d = Decimal::with_precision_scale("1234", Some(10), Some(-2)).unwrap();
972        assert_eq!(d.to_string(), "1200");
973    }
974
975    #[test]
976    fn test_negative_scale_with_precision() {
977        // NUMERIC(2, -3): 2 significant digits, round to nearest 1000
978        let d = Decimal::with_precision_scale("12345", Some(2), Some(-3)).unwrap();
979        assert_eq!(d.to_string(), "12000");
980    }
981
982    // ==================== Error Handling Tests ====================
983
984    #[test]
985    fn test_invalid_format_errors() {
986        // Multiple decimal points
987        let result = Decimal::from_str("1.2.3");
988        assert!(result.is_err());
989        assert!(matches!(
990            result.unwrap_err(),
991            DecimalError::InvalidFormat(_)
992        ));
993
994        // Invalid characters
995        let result = Decimal::from_str("12abc");
996        assert!(result.is_err());
997
998        // Missing exponent after 'e'
999        let result = Decimal::from_str("1e");
1000        assert!(result.is_err());
1001        assert!(matches!(
1002            result.unwrap_err(),
1003            DecimalError::InvalidFormat(_)
1004        ));
1005
1006        // Invalid exponent (non-numeric after 'e')
1007        let result = Decimal::from_str("1eabc");
1008        assert!(result.is_err());
1009
1010        // Empty string gives zero
1011        let d = Decimal::from_str("").unwrap();
1012        assert!(d.is_zero());
1013
1014        // Just a sign with no digits
1015        let result = Decimal::from_str("-");
1016        assert!(result.is_ok()); // Parses as zero
1017    }
1018
1019    #[test]
1020    fn test_leading_plus_sign() {
1021        let d = Decimal::from_str("+123.456").unwrap();
1022        assert_eq!(d.to_string(), "123.456");
1023        assert!(d.is_positive());
1024    }
1025
1026    #[test]
1027    fn test_scientific_notation() {
1028        let d = Decimal::from_str("1.5e10").unwrap();
1029        assert_eq!(d.to_string(), "15000000000");
1030
1031        let d = Decimal::from_str("1.5E-3").unwrap();
1032        assert_eq!(d.to_string(), "0.0015");
1033
1034        let d = Decimal::from_str("1e+5").unwrap();
1035        assert_eq!(d.to_string(), "100000");
1036    }
1037
1038    #[test]
1039    fn test_leading_decimal_point() {
1040        // ".5" should be parsed as "0.5"
1041        let d = Decimal::from_str(".5").unwrap();
1042        assert_eq!(d.to_string(), "0.5");
1043
1044        let d = Decimal::from_str("-.25").unwrap();
1045        assert_eq!(d.to_string(), "-0.25");
1046    }
1047
1048    #[test]
1049    fn test_trailing_zeros() {
1050        let d = Decimal::from_str("100").unwrap();
1051        assert_eq!(d.to_string(), "100");
1052
1053        let d = Decimal::from_str("1.500").unwrap();
1054        assert_eq!(d.to_string(), "1.5");
1055    }
1056
1057    #[test]
1058    fn test_leading_zeros() {
1059        let d = Decimal::from_str("007").unwrap();
1060        assert_eq!(d.to_string(), "7");
1061
1062        let d = Decimal::from_str("00.123").unwrap();
1063        assert_eq!(d.to_string(), "0.123");
1064    }
1065
1066    // ==================== Additional Trait Tests ====================
1067
1068    #[test]
1069    fn test_into_bytes() {
1070        let d = Decimal::from_str("123.456").unwrap();
1071        let bytes_ref = d.as_bytes().to_vec();
1072        let bytes_owned = d.into_bytes();
1073        assert_eq!(bytes_ref, bytes_owned);
1074    }
1075
1076    #[test]
1077    fn test_clone() {
1078        let d1 = Decimal::from_str("123.456").unwrap();
1079        let d2 = d1.clone();
1080        assert_eq!(d1, d2);
1081        assert_eq!(d1.as_bytes(), d2.as_bytes());
1082    }
1083
1084    #[test]
1085    fn test_hash() {
1086        use std::collections::HashSet;
1087
1088        let mut set = HashSet::new();
1089        set.insert(Decimal::from_str("123.456").unwrap());
1090        set.insert(Decimal::from_str("123.456").unwrap()); // Duplicate
1091        set.insert(Decimal::from_str("789.012").unwrap());
1092
1093        assert_eq!(set.len(), 2);
1094        assert!(set.contains(&Decimal::from_str("123.456").unwrap()));
1095    }
1096
1097    #[test]
1098    fn test_debug_format() {
1099        let d = Decimal::from_str("123.456").unwrap();
1100        let debug_str = format!("{:?}", d);
1101        assert!(debug_str.contains("Decimal"));
1102        assert!(debug_str.contains("123.456"));
1103    }
1104
1105    #[test]
1106    fn test_ord_trait() {
1107        use std::cmp::Ordering;
1108
1109        let a = Decimal::from_str("1").unwrap();
1110        let b = Decimal::from_str("2").unwrap();
1111        let c = Decimal::from_str("1").unwrap();
1112
1113        assert_eq!(a.cmp(&b), Ordering::Less);
1114        assert_eq!(b.cmp(&a), Ordering::Greater);
1115        assert_eq!(a.cmp(&c), Ordering::Equal);
1116    }
1117
1118    #[test]
1119    fn test_from_bytes_invalid() {
1120        // Empty bytes should fail
1121        let result = Decimal::from_bytes(&[]);
1122        assert!(result.is_err());
1123
1124        // Single invalid sign byte
1125        let result = Decimal::from_bytes(&[0x00]);
1126        assert!(result.is_err());
1127    }
1128
1129    #[test]
1130    fn test_deserialize_from_string_number() {
1131        // JSON string numbers deserialize correctly
1132        let d: Decimal = serde_json::from_str("\"42\"").unwrap();
1133        assert_eq!(d.to_string(), "42");
1134
1135        let d: Decimal = serde_json::from_str("\"-100\"").unwrap();
1136        assert_eq!(d.to_string(), "-100");
1137
1138        let d: Decimal = serde_json::from_str("\"1.5e10\"").unwrap();
1139        assert_eq!(d.to_string(), "15000000000");
1140    }
1141
1142    #[test]
1143    fn test_from_various_integer_types() {
1144        assert_eq!(Decimal::from(0i32).to_string(), "0");
1145        assert_eq!(Decimal::from(i32::MAX).to_string(), "2147483647");
1146        assert_eq!(Decimal::from(i32::MIN).to_string(), "-2147483648");
1147        assert_eq!(Decimal::from(i64::MAX).to_string(), "9223372036854775807");
1148        assert_eq!(Decimal::from(i64::MIN).to_string(), "-9223372036854775808");
1149    }
1150
1151    #[test]
1152    fn test_precision_overflow() {
1153        // Exponent too large
1154        let result = Decimal::from_str("1e20000");
1155        assert!(result.is_err());
1156        assert!(matches!(
1157            result.unwrap_err(),
1158            DecimalError::PrecisionOverflow
1159        ));
1160
1161        // Exponent too small (negative)
1162        let result = Decimal::from_str("1e-20000");
1163        assert!(result.is_err());
1164        assert!(matches!(
1165            result.unwrap_err(),
1166            DecimalError::PrecisionOverflow
1167        ));
1168    }
1169
1170    #[test]
1171    fn test_all_zeros_variations() {
1172        let d = Decimal::from_str("0").unwrap();
1173        assert!(d.is_zero());
1174
1175        let d = Decimal::from_str("0.0").unwrap();
1176        assert!(d.is_zero());
1177
1178        let d = Decimal::from_str("00.00").unwrap();
1179        assert!(d.is_zero());
1180
1181        let d = Decimal::from_str("-0").unwrap();
1182        assert!(d.is_zero());
1183        // Note: -0 normalizes to 0
1184    }
1185
1186    // ==================== Additional Edge Case Tests ====================
1187
1188    #[test]
1189    fn test_rounding_all_nines() {
1190        // Rounding 99.999 with scale 2 should become 100.00 -> 100
1191        let d = Decimal::with_precision_scale("99.999", Some(10), Some(2)).unwrap();
1192        assert_eq!(d.to_string(), "100");
1193
1194        // Rounding 9.99 with scale 1 should become 10.0 -> 10
1195        let d = Decimal::with_precision_scale("9.99", Some(10), Some(1)).unwrap();
1196        assert_eq!(d.to_string(), "10");
1197
1198        // 999 rounding to nearest 10 (scale -1) should become 1000
1199        let d = Decimal::with_precision_scale("999", Some(10), Some(-1)).unwrap();
1200        assert_eq!(d.to_string(), "1000");
1201    }
1202
1203    #[test]
1204    fn test_negative_scale_small_number() {
1205        // Number smaller than rounding unit, rounds to 0
1206        let d = Decimal::with_precision_scale("4", Some(10), Some(-1)).unwrap();
1207        assert_eq!(d.to_string(), "0");
1208
1209        // Number >= half the rounding unit, rounds up
1210        let d = Decimal::with_precision_scale("5", Some(10), Some(-1)).unwrap();
1211        assert_eq!(d.to_string(), "10");
1212
1213        // Negative number smaller than rounding unit
1214        let d = Decimal::with_precision_scale("-4", Some(10), Some(-1)).unwrap();
1215        assert_eq!(d.to_string(), "0");
1216
1217        // Negative number >= half unit
1218        let d = Decimal::with_precision_scale("-5", Some(10), Some(-1)).unwrap();
1219        assert_eq!(d.to_string(), "-10");
1220    }
1221
1222    #[test]
1223    fn test_precision_truncation() {
1224        // When precision is exceeded, truncate from left
1225        let d = Decimal::with_precision_scale("123456", Some(3), Some(0)).unwrap();
1226        assert_eq!(d.to_string(), "456");
1227
1228        // With decimal places
1229        let d = Decimal::with_precision_scale("12345.67", Some(4), Some(2)).unwrap();
1230        assert_eq!(d.to_string(), "45.67");
1231    }
1232
1233    #[test]
1234    fn test_very_small_numbers() {
1235        let d = Decimal::from_str("0.000000001").unwrap();
1236        assert_eq!(d.to_string(), "0.000000001");
1237        assert!(d.is_positive());
1238
1239        let d = Decimal::from_str("-0.000000001").unwrap();
1240        assert_eq!(d.to_string(), "-0.000000001");
1241        assert!(d.is_negative());
1242    }
1243
1244    #[test]
1245    fn test_very_large_numbers() {
1246        let d = Decimal::from_str("999999999999999999999999999999").unwrap();
1247        assert_eq!(d.to_string(), "999999999999999999999999999999");
1248
1249        let d = Decimal::from_str("-999999999999999999999999999999").unwrap();
1250        assert_eq!(d.to_string(), "-999999999999999999999999999999");
1251    }
1252
1253    #[test]
1254    fn test_max_exponent_boundary() {
1255        // Just under max exponent should work
1256        let d = Decimal::from_str("1e16000").unwrap();
1257        assert!(d.is_positive());
1258
1259        // Just over should fail
1260        let result = Decimal::from_str("1e17000");
1261        assert!(result.is_err());
1262    }
1263
1264    #[test]
1265    fn test_min_exponent_boundary() {
1266        // Just above min exponent should work
1267        let d = Decimal::from_str("1e-16000").unwrap();
1268        assert!(d.is_positive());
1269
1270        // Just below should fail
1271        let result = Decimal::from_str("1e-17000");
1272        assert!(result.is_err());
1273    }
1274
1275    #[test]
1276    fn test_odd_digit_count() {
1277        // Odd number of digits (tests BCD padding)
1278        let d = Decimal::from_str("12345").unwrap();
1279        assert_eq!(d.to_string(), "12345");
1280
1281        let d = Decimal::from_str("1").unwrap();
1282        assert_eq!(d.to_string(), "1");
1283
1284        let d = Decimal::from_str("123").unwrap();
1285        assert_eq!(d.to_string(), "123");
1286    }
1287
1288    #[test]
1289    fn test_negative_number_ordering() {
1290        // Verify negative number byte ordering is correct
1291        let a = Decimal::from_str("-100").unwrap();
1292        let b = Decimal::from_str("-10").unwrap();
1293        let c = Decimal::from_str("-1").unwrap();
1294
1295        // -100 < -10 < -1 numerically
1296        assert!(a < b);
1297        assert!(b < c);
1298
1299        // Byte ordering should match
1300        assert!(a.as_bytes() < b.as_bytes());
1301        assert!(b.as_bytes() < c.as_bytes());
1302    }
1303
1304    #[test]
1305    fn test_from_bytes_unchecked_roundtrip() {
1306        let original = Decimal::from_str("123.456").unwrap();
1307        let bytes = original.as_bytes().to_vec();
1308        let restored = Decimal::from_bytes_unchecked(bytes);
1309        assert_eq!(original, restored);
1310    }
1311
1312    #[test]
1313    fn test_special_value_checks() {
1314        let d = Decimal::from_str("123.456").unwrap();
1315        assert!(!d.is_nan());
1316        assert!(!d.is_infinity());
1317        assert!(!d.is_pos_infinity());
1318        assert!(!d.is_neg_infinity());
1319        assert!(!d.is_special());
1320        assert!(d.is_finite());
1321    }
1322
1323    #[test]
1324    fn test_equality_and_hash_consistency() {
1325        use std::collections::HashMap;
1326
1327        let d1 = Decimal::from_str("123.456").unwrap();
1328        let d2 = Decimal::from_str("123.456").unwrap();
1329        let d3 = Decimal::from_str("123.457").unwrap();
1330
1331        // Equal values should be equal
1332        assert_eq!(d1, d2);
1333        assert_ne!(d1, d3);
1334
1335        // Equal values should have same hash (can be used as map keys)
1336        let mut map = HashMap::new();
1337        map.insert(d1.clone(), "first");
1338        map.insert(d2.clone(), "second"); // Should overwrite
1339        assert_eq!(map.len(), 1);
1340        assert_eq!(map.get(&d1), Some(&"second"));
1341    }
1342
1343    #[test]
1344    fn test_scale_zero() {
1345        // Scale 0 should keep integer part only
1346        let d = Decimal::with_precision_scale("123.999", Some(10), Some(0)).unwrap();
1347        assert_eq!(d.to_string(), "124"); // Rounds up
1348    }
1349
1350    #[test]
1351    fn test_only_fractional_with_precision_scale() {
1352        let d = Decimal::with_precision_scale(".5", Some(10), Some(2)).unwrap();
1353        assert_eq!(d.to_string(), "0.5");
1354    }
1355
1356    #[test]
1357    fn test_default_impl() {
1358        let d = Decimal::default();
1359        assert!(d.is_zero());
1360        assert_eq!(d.to_string(), "0");
1361    }
1362
1363    #[test]
1364    fn test_precision_zero_integer_digits() {
1365        // When precision equals scale, no integer digits are allowed
1366        // This triggers the max_integer_digits == 0 branch
1367        let d = Decimal::with_precision_scale("123.456", Some(2), Some(2)).unwrap();
1368        assert_eq!(d.to_string(), "0.46");
1369    }
1370
1371    #[test]
1372    fn test_negative_with_precision_truncation() {
1373        // Negative number that gets truncated by precision constraints
1374        let d = Decimal::with_precision_scale("-123.456", Some(3), Some(2)).unwrap();
1375        assert_eq!(d.to_string(), "-3.46");
1376    }
1377
1378    #[test]
1379    fn test_invalid_sign_byte() {
1380        // Sign bytes: SIGN_NEGATIVE=0x00, SIGN_ZERO=0x80, SIGN_POSITIVE=0xFF
1381        // Any other sign byte is invalid
1382
1383        // Sign byte 0x01 is invalid
1384        let result = Decimal::from_bytes(&[0x01, 0x40, 0x00, 0x12]);
1385        assert!(result.is_err());
1386
1387        // Sign byte 0x7F is also invalid
1388        let result = Decimal::from_bytes(&[0x7F, 0x40, 0x00, 0x12]);
1389        assert!(result.is_err());
1390
1391        // Sign byte 0xFE is also invalid
1392        let result = Decimal::from_bytes(&[0xFE, 0x40, 0x00, 0x12]);
1393        assert!(result.is_err());
1394    }
1395
1396    #[test]
1397    fn test_invalid_bcd_encoding() {
1398        // Construct bytes that would decode to invalid BCD (digit > 9)
1399        // SIGN_POSITIVE = 0xFF, with valid exponent, but invalid BCD mantissa
1400        // A valid BCD digit must have each nibble 0-9. 0xAB has A=10, B=11 (both > 9)
1401        let invalid_bytes = vec![
1402            0xFF, // SIGN_POSITIVE
1403            0x80, 0x00, // Valid exponent (middle of range)
1404            0xAB, // Invalid BCD: high nibble = 10, low nibble = 11
1405        ];
1406        let result = Decimal::from_bytes(&invalid_bytes);
1407        assert!(result.is_err());
1408
1409        // Also test with just high nibble invalid
1410        let invalid_bytes = vec![
1411            0xFF, // SIGN_POSITIVE
1412            0x80, 0x00, // Valid exponent
1413            0xA1, // Invalid BCD: high nibble = 10, low nibble = 1
1414        ];
1415        let result = Decimal::from_bytes(&invalid_bytes);
1416        assert!(result.is_err());
1417
1418        // Also test with just low nibble invalid
1419        let invalid_bytes = vec![
1420            0xFF, // SIGN_POSITIVE
1421            0x80, 0x00, // Valid exponent
1422            0x1B, // Invalid BCD: high nibble = 1, low nibble = 11
1423        ];
1424        let result = Decimal::from_bytes(&invalid_bytes);
1425        assert!(result.is_err());
1426    }
1427
1428    #[test]
1429    fn test_reserved_exponent_positive() {
1430        // SIGN_POSITIVE = 0xFF
1431        // Reserved exponents: 0xFFFE (Infinity), 0xFFFF (NaN)
1432        // If we have more than 3 bytes, special value check is skipped
1433        // but decode_exponent will catch the reserved value
1434
1435        // Reserved NaN exponent (0xFFFF) with extra mantissa byte
1436        let bytes_with_reserved_exp = vec![
1437            0xFF, // SIGN_POSITIVE
1438            0xFF, 0xFF, // Reserved for NaN
1439            0x12, // Some mantissa (makes it 4 bytes, not 3)
1440        ];
1441        let result = Decimal::from_bytes(&bytes_with_reserved_exp);
1442        assert!(result.is_err());
1443
1444        // Reserved Infinity exponent (0xFFFE) with extra mantissa byte
1445        let bytes_with_reserved_exp = vec![
1446            0xFF, // SIGN_POSITIVE
1447            0xFF, 0xFE, // Reserved for Infinity
1448            0x12, // Some mantissa (makes it 4 bytes, not 3)
1449        ];
1450        let result = Decimal::from_bytes(&bytes_with_reserved_exp);
1451        assert!(result.is_err());
1452    }
1453
1454    #[test]
1455    fn test_reserved_exponent_negative() {
1456        // SIGN_NEGATIVE = 0x00
1457        // Reserved exponent for -Infinity: 0x0000
1458        // If we have more than 3 bytes, special value check is skipped
1459
1460        let bytes_with_reserved_exp = vec![
1461            0x00, // SIGN_NEGATIVE
1462            0x00, 0x00, // Reserved for -Infinity
1463            0x12, // Some mantissa (makes it 4 bytes, not 3)
1464        ];
1465        let result = Decimal::from_bytes(&bytes_with_reserved_exp);
1466        assert!(result.is_err());
1467    }
1468
1469    #[test]
1470    fn test_empty_mantissa_bytes() {
1471        // Construct bytes with valid sign and exponent but no mantissa
1472        // This should decode to 0 via the empty digits path in format_decimal
1473        let bytes_no_mantissa = vec![
1474            0xFF, // SIGN_POSITIVE
1475            0x80, 0x00, // Valid exponent
1476                  // No mantissa bytes
1477        ];
1478        let d = Decimal::from_bytes(&bytes_no_mantissa).unwrap();
1479        assert_eq!(d.to_string(), "0");
1480    }
1481
1482    // ==================== rust_decimal Interop Tests ====================
1483
1484    #[cfg(feature = "rust_decimal")]
1485    mod rust_decimal_tests {
1486        use super::*;
1487
1488        #[test]
1489        fn test_from_rust_decimal() {
1490            use rust_decimal::Decimal as RustDecimal;
1491
1492            let rd = RustDecimal::new(12345, 2); // 123.45
1493            let d: Decimal = rd.try_into().unwrap();
1494            assert_eq!(d.to_string(), "123.45");
1495        }
1496
1497        #[test]
1498        fn test_to_rust_decimal() {
1499            use rust_decimal::Decimal as RustDecimal;
1500
1501            let d = Decimal::from_str("123.45").unwrap();
1502            let rd: RustDecimal = (&d).try_into().unwrap();
1503            assert_eq!(rd.to_string(), "123.45");
1504        }
1505
1506        #[test]
1507        fn test_rust_decimal_roundtrip() {
1508            use rust_decimal::Decimal as RustDecimal;
1509
1510            let values = vec!["0", "1", "-1", "123.456", "-999.999", "0.001"];
1511
1512            for s in values {
1513                let d = Decimal::from_str(s).unwrap();
1514                let rd: RustDecimal = (&d).try_into().unwrap();
1515                let d2: Decimal = rd.try_into().unwrap();
1516                assert_eq!(d, d2, "Roundtrip failed for {}", s);
1517            }
1518        }
1519
1520        #[test]
1521        fn test_rust_decimal_arithmetic() {
1522            use rust_decimal::Decimal as RustDecimal;
1523
1524            // Start with decimal-bytes values
1525            let a = Decimal::from_str("100.50").unwrap();
1526            let b = Decimal::from_str("25.25").unwrap();
1527
1528            // Convert to rust_decimal for arithmetic
1529            let ra: RustDecimal = (&a).try_into().unwrap();
1530            let rb: RustDecimal = (&b).try_into().unwrap();
1531            let sum = ra + rb;
1532
1533            // Convert back to decimal-bytes for storage
1534            let result: Decimal = sum.try_into().unwrap();
1535            assert_eq!(result.to_string(), "125.75");
1536        }
1537
1538        #[test]
1539        fn test_rust_decimal_from_owned() {
1540            use rust_decimal::Decimal as RustDecimal;
1541
1542            // Test TryFrom<Decimal> (owned) for RustDecimal
1543            let d = Decimal::from_str("456.789").unwrap();
1544            let rd: RustDecimal = d.try_into().unwrap();
1545            assert_eq!(rd.to_string(), "456.789");
1546        }
1547
1548        #[test]
1549        fn test_rust_decimal_special_values_fail() {
1550            use rust_decimal::Decimal as RustDecimal;
1551
1552            // Infinity cannot convert to rust_decimal
1553            let inf = Decimal::infinity();
1554            let result: Result<RustDecimal, _> = (&inf).try_into();
1555            assert!(result.is_err());
1556
1557            // NaN cannot convert to rust_decimal
1558            let nan = Decimal::nan();
1559            let result: Result<RustDecimal, _> = (&nan).try_into();
1560            assert!(result.is_err());
1561        }
1562    }
1563
1564    // ==================== bigdecimal Interop Tests ====================
1565
1566    #[cfg(feature = "bigdecimal")]
1567    mod bigdecimal_tests {
1568        use super::*;
1569
1570        #[test]
1571        fn test_from_bigdecimal() {
1572            use bigdecimal::BigDecimal;
1573            use std::str::FromStr;
1574
1575            let bd = BigDecimal::from_str("123.45").unwrap();
1576            let d: Decimal = bd.try_into().unwrap();
1577            assert_eq!(d.to_string(), "123.45");
1578        }
1579
1580        #[test]
1581        fn test_to_bigdecimal() {
1582            use bigdecimal::BigDecimal;
1583
1584            let d = Decimal::from_str("123.45").unwrap();
1585            let bd: BigDecimal = (&d).try_into().unwrap();
1586            assert_eq!(bd.to_string(), "123.45");
1587        }
1588
1589        #[test]
1590        fn test_bigdecimal_roundtrip() {
1591            use bigdecimal::BigDecimal;
1592
1593            let values = vec!["0", "1", "-1", "123.456", "-999.999", "0.001"];
1594
1595            for s in values {
1596                let d = Decimal::from_str(s).unwrap();
1597                let bd: BigDecimal = (&d).try_into().unwrap();
1598                let d2: Decimal = bd.try_into().unwrap();
1599                assert_eq!(d, d2, "Roundtrip failed for {}", s);
1600            }
1601        }
1602
1603        #[test]
1604        fn test_bigdecimal_from_owned() {
1605            use bigdecimal::BigDecimal;
1606
1607            // Test TryFrom<Decimal> (owned) for BigDecimal
1608            let d = Decimal::from_str("456.789").unwrap();
1609            let bd: BigDecimal = d.try_into().unwrap();
1610            assert_eq!(bd.to_string(), "456.789");
1611        }
1612
1613        #[test]
1614        fn test_bigdecimal_from_ref() {
1615            use bigdecimal::BigDecimal;
1616            use std::str::FromStr;
1617
1618            // Test TryFrom<&BigDecimal> for Decimal
1619            let bd = BigDecimal::from_str("789.012").unwrap();
1620            let d: Decimal = (&bd).try_into().unwrap();
1621            assert_eq!(d.to_string(), "789.012");
1622        }
1623
1624        #[test]
1625        fn test_bigdecimal_special_values_fail() {
1626            use bigdecimal::BigDecimal;
1627
1628            // Infinity cannot convert to BigDecimal
1629            let inf = Decimal::infinity();
1630            let result: Result<BigDecimal, _> = (&inf).try_into();
1631            assert!(result.is_err());
1632
1633            // NaN cannot convert to BigDecimal
1634            let nan = Decimal::nan();
1635            let result: Result<BigDecimal, _> = (&nan).try_into();
1636            assert!(result.is_err());
1637        }
1638    }
1639}