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