Skip to main content

decimal_bytes/
decimal64.rs

1//! Fixed-precision 64-bit decimal type with embedded scale.
2//!
3//! `Decimal64` provides an efficient fixed-size representation for decimal numbers
4//! that fit within 64 bits. Both the value and scale are packed into a single i64.
5//!
6//! ## When to Use
7//!
8//! - **Decimal64**: For decimals with precision ≤ 16 and scale 0-18 (most financial data)
9//! - **Decimal**: For arbitrary precision or when precision/scale exceed Decimal64 limits
10//!
11//! ## Storage Layout
12//!
13//! ```text
14//! 64-bit packed representation:
15//! ┌──────────────────┬─────────────────────────────────────────────────────┐
16//! │ Scale (8 bits)   │ Value (56 bits, signed)                             │
17//! │ Byte 0           │ Bytes 1-7                                           │
18//! └──────────────────┴─────────────────────────────────────────────────────┘
19//! ```
20//!
21//! - **Scale byte**: 0-18 for normal values, 253/254/255 for special values
22//! - **Value**: 56-bit signed integer (-2^55 to 2^55-1)
23//!
24//! ## PostgreSQL Compatibility
25//!
26//! `Decimal64` supports PostgreSQL NUMERIC semantics including:
27//! - Precision up to 16 significant digits
28//! - Scale from 0 to 18
29//! - Special values: `Infinity`, `-Infinity`, and `NaN`
30//! - Sort order: `-Infinity < numbers < +Infinity < NaN`
31//! - `NaN == NaN` (PostgreSQL semantics, not IEEE 754)
32//!
33//! ## Example
34//!
35//! ```
36//! use decimal_bytes::Decimal64;
37//!
38//! // Create with precision and scale
39//! let price = Decimal64::new("123.45", 2).unwrap();
40//! assert_eq!(price.to_string(), "123.45");
41//! assert_eq!(price.scale(), 2);
42//!
43//! // Efficient 8-byte representation
44//! assert_eq!(std::mem::size_of_val(&price), 8);
45//!
46//! // Special values
47//! let inf = Decimal64::infinity();
48//! let nan = Decimal64::nan();
49//! assert!(price < inf);
50//! assert!(inf < nan);
51//! ```
52
53use std::cmp::Ordering;
54use std::fmt;
55use std::hash::{Hash, Hasher};
56use std::str::FromStr;
57
58use serde::{Deserialize, Deserializer, Serialize, Serializer};
59
60use crate::encoding::DecimalError;
61use crate::Decimal;
62
63/// Maximum precision that fits in 56-bit value (16 digits).
64pub const MAX_DECIMAL64_PRECISION: u32 = 16;
65
66/// Maximum scale supported (0-18).
67pub const MAX_DECIMAL64_SCALE: u8 = 18;
68
69// Scale byte values for special values
70const SCALE_NEG_INFINITY: u8 = 253;
71const SCALE_POS_INFINITY: u8 = 254;
72const SCALE_NAN: u8 = 255;
73
74// 56-bit value limits
75const VALUE_BITS: u32 = 56;
76const MAX_VALUE: i64 = (1i64 << (VALUE_BITS - 1)) - 1; // 2^55 - 1
77const MIN_VALUE: i64 = -(1i64 << (VALUE_BITS - 1)); // -2^55
78
79/// A fixed-precision decimal stored as a 64-bit integer with embedded scale.
80///
81/// ## Storage Design
82///
83/// Unlike designs where scale is external, `Decimal64` packs both value and scale
84/// into a single 64-bit integer:
85///
86/// ```text
87/// Byte:    [0]      [1]      [2]      [3]      [4]      [5]      [6]      [7]
88///          Scale    |<---------------- 56-bit signed value ---------------->|
89/// ```
90///
91/// - **Scale (byte 0)**: 0-18 for normal values, special markers for Infinity/NaN
92/// - **Value (bytes 1-7)**: 56-bit signed integer representing `actual_value * 10^scale`
93///
94/// ## Trade-offs
95///
96/// | Aspect | Decimal64 (embedded scale) | External scale design |
97/// |--------|---------------------------|----------------------|
98/// | Precision | 16 digits | 18 digits |
99/// | Self-contained | Yes (scale included) | No (need metadata) |
100/// | API simplicity | `to_string()` works | Need `to_string_with_scale(s)` |
101/// | Columnar storage | Scale repeated per value | Scale in column metadata |
102///
103/// ## Special Values
104///
105/// Special values are encoded using reserved scale bytes:
106/// - Scale = 253: -Infinity
107/// - Scale = 254: +Infinity
108/// - Scale = 255: NaN
109///
110/// ## Ordering
111///
112/// Within the same scale, values are ordered correctly. Across different scales,
113/// ordering requires conversion to a common scale or use of `cmp()`.
114#[derive(Clone, Copy)]
115pub struct Decimal64 {
116    /// Packed representation: scale in high byte, 56-bit value in low 7 bytes
117    packed: i64,
118}
119
120impl Decimal64 {
121    // ==================== Constructors ====================
122
123    /// Creates a Decimal64 from a string with automatic scale detection.
124    ///
125    /// The scale is determined from the number of digits after the decimal point.
126    ///
127    /// # Examples
128    ///
129    /// ```
130    /// use decimal_bytes::Decimal64;
131    ///
132    /// let d = Decimal64::new("123.45", 2).unwrap();
133    /// assert_eq!(d.to_string(), "123.45");
134    /// assert_eq!(d.scale(), 2);
135    ///
136    /// let d = Decimal64::new("100", 0).unwrap();
137    /// assert_eq!(d.to_string(), "100");
138    /// assert_eq!(d.scale(), 0);
139    /// ```
140    pub fn new(s: &str, scale: u8) -> Result<Self, DecimalError> {
141        Self::with_precision_scale(s, None, Some(scale as i32))
142    }
143
144    /// Creates a Decimal64 with precision and scale constraints (PostgreSQL NUMERIC semantics).
145    ///
146    /// - `precision`: Maximum total significant digits (1-16, or None for no limit)
147    /// - `scale`: Digits after decimal point (0-18, or negative for rounding left of decimal)
148    ///
149    /// # Examples
150    ///
151    /// ```
152    /// use decimal_bytes::Decimal64;
153    ///
154    /// // NUMERIC(5, 2) - up to 5 digits total, 2 after decimal
155    /// let d = Decimal64::with_precision_scale("123.456", Some(5), Some(2)).unwrap();
156    /// assert_eq!(d.to_string(), "123.46");
157    ///
158    /// // NUMERIC(2, -3) - rounds to nearest 1000
159    /// let d = Decimal64::with_precision_scale("12345", Some(2), Some(-3)).unwrap();
160    /// assert_eq!(d.to_string(), "12000");
161    /// ```
162    pub fn with_precision_scale(
163        s: &str,
164        precision: Option<u32>,
165        scale: Option<i32>,
166    ) -> Result<Self, DecimalError> {
167        // Validate precision
168        if let Some(p) = precision {
169            if p > MAX_DECIMAL64_PRECISION {
170                return Err(DecimalError::InvalidFormat(format!(
171                    "Precision {} exceeds maximum {} for Decimal64",
172                    p, MAX_DECIMAL64_PRECISION
173                )));
174            }
175            if p == 0 {
176                return Err(DecimalError::InvalidFormat(
177                    "Precision must be at least 1".to_string(),
178                ));
179            }
180        }
181
182        let scale_val = scale.unwrap_or(0);
183
184        // For positive scale, validate it doesn't exceed max
185        if scale_val > 0 && scale_val as u8 > MAX_DECIMAL64_SCALE {
186            return Err(DecimalError::InvalidFormat(format!(
187                "Scale {} exceeds maximum {} for Decimal64",
188                scale_val, MAX_DECIMAL64_SCALE
189            )));
190        }
191
192        let s = s.trim();
193
194        // Handle special values (case-insensitive)
195        let lower = s.to_lowercase();
196        match lower.as_str() {
197            "nan" | "-nan" | "+nan" => return Ok(Self::nan()),
198            "infinity" | "inf" | "+infinity" | "+inf" => return Ok(Self::infinity()),
199            "-infinity" | "-inf" => return Ok(Self::neg_infinity()),
200            _ => {}
201        }
202
203        // Parse sign
204        let (is_negative, s) = if let Some(rest) = s.strip_prefix('-') {
205            (true, rest)
206        } else if let Some(rest) = s.strip_prefix('+') {
207            (false, rest)
208        } else {
209            (false, s)
210        };
211
212        // Split into integer and fractional parts
213        let (int_part, frac_part) = if let Some(dot_pos) = s.find('.') {
214            (&s[..dot_pos], &s[dot_pos + 1..])
215        } else {
216            (s, "")
217        };
218
219        // Trim leading zeros from integer part
220        let int_part = int_part.trim_start_matches('0');
221        let int_part = if int_part.is_empty() { "0" } else { int_part };
222
223        // Handle negative scale (round to left of decimal point)
224        if scale_val < 0 {
225            return Self::parse_negative_scale(int_part, is_negative, precision, scale_val);
226        }
227
228        let scale_u8 = scale_val as u8;
229
230        // Apply scale to fractional part (truncate/round)
231        let (int_part, frac_digits) = Self::apply_scale(int_part, frac_part, scale_u8 as usize);
232
233        // Apply precision constraint
234        let (final_int, final_frac) =
235            Self::apply_precision(&int_part, &frac_digits, precision, scale_u8 as usize);
236
237        // Convert to packed representation
238        Self::parts_to_packed(&final_int, &final_frac, is_negative, scale_u8)
239    }
240
241    /// Creates a Decimal64 from raw value and scale components.
242    ///
243    /// # Arguments
244    /// * `value` - The scaled integer value (must fit in 56 bits)
245    /// * `scale` - The scale (0-18)
246    ///
247    /// # Errors
248    /// Returns an error if value doesn't fit in 56 bits or scale > 18.
249    pub fn from_parts(value: i64, scale: u8) -> Result<Self, DecimalError> {
250        if scale > MAX_DECIMAL64_SCALE {
251            return Err(DecimalError::InvalidFormat(format!(
252                "Scale {} exceeds maximum {}",
253                scale, MAX_DECIMAL64_SCALE
254            )));
255        }
256        if !(MIN_VALUE..=MAX_VALUE).contains(&value) {
257            return Err(DecimalError::InvalidFormat(format!(
258                "Value {} doesn't fit in 56 bits (range {} to {})",
259                value, MIN_VALUE, MAX_VALUE
260            )));
261        }
262
263        Ok(Self::pack(value, scale))
264    }
265
266    /// Creates a Decimal64 from a raw packed i64 (for deserialization).
267    #[inline]
268    pub const fn from_raw(packed: i64) -> Self {
269        Self { packed }
270    }
271
272    // ==================== Special Value Constructors ====================
273
274    /// Creates positive infinity.
275    #[inline]
276    pub const fn infinity() -> Self {
277        Self::pack_special(SCALE_POS_INFINITY)
278    }
279
280    /// Creates negative infinity.
281    #[inline]
282    pub const fn neg_infinity() -> Self {
283        Self::pack_special(SCALE_NEG_INFINITY)
284    }
285
286    /// Creates NaN (Not a Number).
287    ///
288    /// Follows PostgreSQL semantics: `NaN == NaN` is `true`.
289    #[inline]
290    pub const fn nan() -> Self {
291        Self::pack_special(SCALE_NAN)
292    }
293
294    // ==================== Accessors ====================
295
296    /// Returns the raw packed i64 value.
297    #[inline]
298    pub const fn raw(&self) -> i64 {
299        self.packed
300    }
301
302    /// Returns the scale (digits after decimal point).
303    ///
304    /// Returns 0 for special values (NaN, Infinity).
305    #[inline]
306    pub fn scale(&self) -> u8 {
307        let scale_byte = self.scale_byte();
308        if scale_byte > MAX_DECIMAL64_SCALE {
309            0 // Special values
310        } else {
311            scale_byte
312        }
313    }
314
315    /// Returns the 56-bit value component.
316    ///
317    /// For special values, returns 0.
318    #[inline]
319    pub fn value(&self) -> i64 {
320        if self.is_special() {
321            0
322        } else {
323            self.unpack_value()
324        }
325    }
326
327    /// Returns true if this value is zero.
328    #[inline]
329    pub fn is_zero(&self) -> bool {
330        !self.is_special() && self.unpack_value() == 0
331    }
332
333    /// Returns true if this value is negative (excluding -Infinity).
334    #[inline]
335    pub fn is_negative(&self) -> bool {
336        !self.is_special() && self.unpack_value() < 0
337    }
338
339    /// Returns true if this value is positive (excluding +Infinity and NaN).
340    #[inline]
341    pub fn is_positive(&self) -> bool {
342        !self.is_special() && self.unpack_value() > 0
343    }
344
345    /// Returns true if this value is positive infinity.
346    #[inline]
347    pub fn is_pos_infinity(&self) -> bool {
348        self.scale_byte() == SCALE_POS_INFINITY
349    }
350
351    /// Returns true if this value is negative infinity.
352    #[inline]
353    pub fn is_neg_infinity(&self) -> bool {
354        self.scale_byte() == SCALE_NEG_INFINITY
355    }
356
357    /// Returns true if this value is positive or negative infinity.
358    #[inline]
359    pub fn is_infinity(&self) -> bool {
360        self.is_pos_infinity() || self.is_neg_infinity()
361    }
362
363    /// Returns true if this value is NaN (Not a Number).
364    #[inline]
365    pub fn is_nan(&self) -> bool {
366        self.scale_byte() == SCALE_NAN
367    }
368
369    /// Returns true if this is a special value (Infinity or NaN).
370    #[inline]
371    pub fn is_special(&self) -> bool {
372        self.scale_byte() > MAX_DECIMAL64_SCALE
373    }
374
375    /// Returns true if this is a finite number (not Infinity or NaN).
376    #[inline]
377    pub fn is_finite(&self) -> bool {
378        !self.is_special()
379    }
380
381    // ==================== Conversions ====================
382
383    /// Formats the decimal as a string (internal helper for Display).
384    fn format_decimal(&self) -> String {
385        // Handle special values
386        if self.is_neg_infinity() {
387            return "-Infinity".to_string();
388        }
389        if self.is_pos_infinity() {
390            return "Infinity".to_string();
391        }
392        if self.is_nan() {
393            return "NaN".to_string();
394        }
395
396        let value = self.unpack_value();
397        let scale = self.scale_byte() as u32;
398
399        if value == 0 {
400            return "0".to_string();
401        }
402
403        let is_negative = value < 0;
404        let abs_value = value.unsigned_abs();
405
406        if scale == 0 {
407            return if is_negative {
408                format!("-{}", abs_value)
409            } else {
410                abs_value.to_string()
411            };
412        }
413
414        let scale_factor = 10u64.pow(scale);
415        let int_part = abs_value / scale_factor;
416        let frac_part = abs_value % scale_factor;
417
418        let result = if frac_part == 0 {
419            int_part.to_string()
420        } else {
421            let frac_str = format!("{:0>width$}", frac_part, width = scale as usize);
422            let frac_str = frac_str.trim_end_matches('0');
423            format!("{}.{}", int_part, frac_str)
424        };
425
426        if is_negative {
427            format!("-{}", result)
428        } else {
429            result
430        }
431    }
432
433    /// Returns the 8-byte big-endian representation.
434    #[inline]
435    pub fn to_be_bytes(&self) -> [u8; 8] {
436        self.packed.to_be_bytes()
437    }
438
439    /// Creates a Decimal64 from big-endian bytes.
440    #[inline]
441    pub fn from_be_bytes(bytes: [u8; 8]) -> Self {
442        Self {
443            packed: i64::from_be_bytes(bytes),
444        }
445    }
446
447    /// Converts to the variable-length `Decimal` type.
448    pub fn to_decimal(&self) -> Decimal {
449        if self.is_neg_infinity() {
450            return Decimal::neg_infinity();
451        }
452        if self.is_pos_infinity() {
453            return Decimal::infinity();
454        }
455        if self.is_nan() {
456            return Decimal::nan();
457        }
458
459        Decimal::from_str(&self.to_string()).expect("Decimal64 string is always valid")
460    }
461
462    /// Creates a Decimal64 from a Decimal with the specified scale.
463    pub fn from_decimal(decimal: &Decimal, scale: u8) -> Result<Self, DecimalError> {
464        if decimal.is_nan() {
465            return Ok(Self::nan());
466        }
467        if decimal.is_pos_infinity() {
468            return Ok(Self::infinity());
469        }
470        if decimal.is_neg_infinity() {
471            return Ok(Self::neg_infinity());
472        }
473
474        Self::new(&decimal.to_string(), scale)
475    }
476
477    /// Returns the minimum finite value that can be stored (at scale 0).
478    #[inline]
479    pub const fn min_value() -> Self {
480        Self::pack(MIN_VALUE, 0)
481    }
482
483    /// Returns the maximum finite value that can be stored (at scale 0).
484    #[inline]
485    pub const fn max_value() -> Self {
486        Self::pack(MAX_VALUE, 0)
487    }
488
489    // ==================== Internal Helpers ====================
490
491    #[inline]
492    const fn pack(value: i64, scale: u8) -> Self {
493        // Pack: scale in high byte, value in low 56 bits
494        let scale_part = (scale as i64) << VALUE_BITS;
495        let value_part = value & ((1i64 << VALUE_BITS) - 1);
496        Self {
497            packed: scale_part | value_part,
498        }
499    }
500
501    #[inline]
502    const fn pack_special(scale: u8) -> Self {
503        // Special values have scale marker and zero value
504        Self {
505            packed: (scale as i64) << VALUE_BITS,
506        }
507    }
508
509    #[inline]
510    fn scale_byte(&self) -> u8 {
511        ((self.packed >> VALUE_BITS) & 0xFF) as u8
512    }
513
514    #[inline]
515    fn unpack_value(&self) -> i64 {
516        // Sign-extend the 56-bit value
517        let raw = self.packed & ((1i64 << VALUE_BITS) - 1);
518        // Check if sign bit (bit 55) is set
519        if raw & (1i64 << (VALUE_BITS - 1)) != 0 {
520            // Negative: extend sign bits
521            raw | (!0i64 << VALUE_BITS)
522        } else {
523            raw
524        }
525    }
526
527    /// Helper: Parse with negative scale (rounds to powers of 10).
528    fn parse_negative_scale(
529        int_part: &str,
530        is_negative: bool,
531        precision: Option<u32>,
532        scale: i32,
533    ) -> Result<Self, DecimalError> {
534        let round_digits = (-scale) as usize;
535
536        if int_part == "0" {
537            return Ok(Self::pack(0, 0));
538        }
539
540        let int_len = int_part.len();
541
542        if int_len <= round_digits {
543            // Number is smaller than rounding unit
544            let num_val: u64 = int_part.parse().unwrap_or(0);
545            let rounding_unit = 10u64.pow(round_digits as u32);
546            let half_unit = rounding_unit / 2;
547
548            let result = if num_val >= half_unit {
549                rounding_unit as i64
550            } else {
551                0
552            };
553
554            let value = if is_negative && result != 0 {
555                -result
556            } else {
557                result
558            };
559
560            if !(MIN_VALUE..=MAX_VALUE).contains(&value) {
561                return Err(DecimalError::InvalidFormat(
562                    "Value too large for Decimal64".to_string(),
563                ));
564            }
565
566            return Ok(Self::pack(value, 0));
567        }
568
569        // Round the integer part
570        let keep_len = int_len - round_digits;
571        let keep_part = &int_part[..keep_len];
572        let round_part = &int_part[keep_len..];
573
574        let first_rounded_digit = round_part.chars().next().unwrap_or('0');
575        let mut result_int: String = keep_part.to_string();
576
577        if first_rounded_digit >= '5' {
578            result_int = add_one_to_string(&result_int);
579        }
580
581        // Apply precision constraint
582        if let Some(p) = precision {
583            let sig_len = result_int.trim_start_matches('0').len();
584            if sig_len > p as usize && p > 0 {
585                let start = result_int.len().saturating_sub(p as usize);
586                result_int = result_int[start..].to_string();
587            }
588        }
589
590        // Parse the significant part and scale back
591        let significant: i64 = result_int.parse().unwrap_or(0);
592        let value = significant
593            .checked_mul(10i64.pow(round_digits as u32))
594            .ok_or_else(|| {
595                DecimalError::InvalidFormat("Value too large for Decimal64".to_string())
596            })?;
597
598        let value = if is_negative { -value } else { value };
599
600        if !(MIN_VALUE..=MAX_VALUE).contains(&value) {
601            return Err(DecimalError::InvalidFormat(
602                "Value too large for Decimal64".to_string(),
603            ));
604        }
605
606        Ok(Self::pack(value, 0))
607    }
608
609    /// Helper: Apply scale constraint to fractional part.
610    fn apply_scale(int_part: &str, frac_part: &str, scale: usize) -> (String, String) {
611        if scale == 0 {
612            let first_frac = frac_part.chars().next().unwrap_or('0');
613            if first_frac >= '5' {
614                return (add_one_to_string(int_part), String::new());
615            }
616            return (int_part.to_string(), String::new());
617        }
618
619        if frac_part.len() <= scale {
620            let padded = format!("{:0<width$}", frac_part, width = scale);
621            return (int_part.to_string(), padded);
622        }
623
624        let truncated = &frac_part[..scale];
625        let next_digit = frac_part.chars().nth(scale).unwrap_or('0');
626
627        if next_digit >= '5' {
628            let rounded = add_one_to_string(truncated);
629            if rounded.len() > scale {
630                return (add_one_to_string(int_part), "0".repeat(scale));
631            }
632            let padded = format!("{:0>width$}", rounded, width = scale);
633            return (int_part.to_string(), padded);
634        }
635
636        (int_part.to_string(), truncated.to_string())
637    }
638
639    /// Helper: Apply precision constraint.
640    fn apply_precision(
641        int_part: &str,
642        frac_part: &str,
643        precision: Option<u32>,
644        scale: usize,
645    ) -> (String, String) {
646        let Some(p) = precision else {
647            return (int_part.to_string(), frac_part.to_string());
648        };
649
650        let p = p as usize;
651        let max_int_digits = p.saturating_sub(scale);
652
653        let int_part = int_part.trim_start_matches('0');
654        let int_part = if int_part.is_empty() { "0" } else { int_part };
655
656        if int_part.len() > max_int_digits && max_int_digits > 0 {
657            let start = int_part.len() - max_int_digits;
658            return (int_part[start..].to_string(), frac_part.to_string());
659        } else if max_int_digits == 0 && int_part != "0" {
660            return ("0".to_string(), frac_part.to_string());
661        }
662
663        (int_part.to_string(), frac_part.to_string())
664    }
665
666    /// Helper: Convert parsed parts to packed representation.
667    fn parts_to_packed(
668        int_part: &str,
669        frac_part: &str,
670        is_negative: bool,
671        scale: u8,
672    ) -> Result<Self, DecimalError> {
673        let int_value: i64 = int_part.parse().unwrap_or(0);
674
675        let scale_factor = 10i64.pow(scale as u32);
676
677        let scaled_int = int_value.checked_mul(scale_factor).ok_or_else(|| {
678            DecimalError::InvalidFormat("Value too large for Decimal64".to_string())
679        })?;
680
681        let frac_value: i64 = if frac_part.is_empty() {
682            0
683        } else {
684            frac_part.parse().unwrap_or(0)
685        };
686
687        let value = scaled_int + frac_value;
688        let value = if is_negative && value != 0 {
689            -value
690        } else {
691            value
692        };
693
694        if !(MIN_VALUE..=MAX_VALUE).contains(&value) {
695            return Err(DecimalError::InvalidFormat(
696                "Value too large for Decimal64".to_string(),
697            ));
698        }
699
700        Ok(Self::pack(value, scale))
701    }
702}
703
704// ==================== Helper Functions ====================
705
706fn add_one_to_string(s: &str) -> String {
707    let mut chars: Vec<char> = s.chars().collect();
708    let mut carry = true;
709
710    for c in chars.iter_mut().rev() {
711        if carry {
712            if *c == '9' {
713                *c = '0';
714            } else {
715                *c = char::from_digit(c.to_digit(10).unwrap() + 1, 10).unwrap();
716                carry = false;
717            }
718        }
719    }
720
721    if carry {
722        format!("1{}", chars.iter().collect::<String>())
723    } else {
724        chars.iter().collect()
725    }
726}
727
728// ==================== Trait Implementations ====================
729
730impl PartialEq for Decimal64 {
731    fn eq(&self, other: &Self) -> bool {
732        // Same scale and same value
733        self.packed == other.packed
734    }
735}
736
737impl Eq for Decimal64 {}
738
739impl PartialOrd for Decimal64 {
740    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
741        Some(self.cmp(other))
742    }
743}
744
745impl Ord for Decimal64 {
746    fn cmp(&self, other: &Self) -> Ordering {
747        // Handle special values
748        match (self.is_special(), other.is_special()) {
749            (true, true) => {
750                // Both special: compare scale bytes
751                // Order: -Infinity (253) < +Infinity (254) < NaN (255)
752                self.scale_byte().cmp(&other.scale_byte())
753            }
754            (true, false) => {
755                // self is special
756                if self.is_neg_infinity() {
757                    Ordering::Less
758                } else {
759                    Ordering::Greater // +Infinity or NaN
760                }
761            }
762            (false, true) => {
763                // other is special
764                if other.is_neg_infinity() {
765                    Ordering::Greater
766                } else {
767                    Ordering::Less // +Infinity or NaN
768                }
769            }
770            (false, false) => {
771                // Both normal: compare actual decimal values
772                let self_scale = self.scale_byte();
773                let other_scale = other.scale_byte();
774                let self_value = self.unpack_value();
775                let other_value = other.unpack_value();
776
777                if self_scale == other_scale {
778                    // Same scale: direct comparison
779                    self_value.cmp(&other_value)
780                } else {
781                    // Different scales: normalize to common scale
782                    let max_scale = self_scale.max(other_scale);
783
784                    let self_normalized = if self_scale < max_scale {
785                        self_value.saturating_mul(10i64.pow((max_scale - self_scale) as u32))
786                    } else {
787                        self_value
788                    };
789
790                    let other_normalized = if other_scale < max_scale {
791                        other_value.saturating_mul(10i64.pow((max_scale - other_scale) as u32))
792                    } else {
793                        other_value
794                    };
795
796                    self_normalized.cmp(&other_normalized)
797                }
798            }
799        }
800    }
801}
802
803impl Hash for Decimal64 {
804    fn hash<H: Hasher>(&self, state: &mut H) {
805        self.packed.hash(state);
806    }
807}
808
809impl fmt::Debug for Decimal64 {
810    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
811        if self.is_nan() {
812            write!(f, "Decimal64(NaN)")
813        } else if self.is_pos_infinity() {
814            write!(f, "Decimal64(Infinity)")
815        } else if self.is_neg_infinity() {
816            write!(f, "Decimal64(-Infinity)")
817        } else {
818            f.debug_struct("Decimal64")
819                .field("value", &self.to_string())
820                .field("scale", &self.scale())
821                .finish()
822        }
823    }
824}
825
826impl fmt::Display for Decimal64 {
827    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
828        write!(f, "{}", self.format_decimal())
829    }
830}
831
832impl Default for Decimal64 {
833    fn default() -> Self {
834        Self::pack(0, 0)
835    }
836}
837
838impl From<i64> for Decimal64 {
839    fn from(value: i64) -> Self {
840        // Clamp to 56-bit range
841        let clamped = value.clamp(MIN_VALUE, MAX_VALUE);
842        Self::pack(clamped, 0)
843    }
844}
845
846impl From<i32> for Decimal64 {
847    fn from(value: i32) -> Self {
848        Self::pack(value as i64, 0)
849    }
850}
851
852// Serde support
853impl Serialize for Decimal64 {
854    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
855    where
856        S: Serializer,
857    {
858        serializer.serialize_i64(self.packed)
859    }
860}
861
862impl<'de> Deserialize<'de> for Decimal64 {
863    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
864    where
865        D: Deserializer<'de>,
866    {
867        let packed = i64::deserialize(deserializer)?;
868        Ok(Self::from_raw(packed))
869    }
870}
871
872impl FromStr for Decimal64 {
873    type Err = DecimalError;
874
875    /// Parses with automatic scale detection from the string.
876    fn from_str(s: &str) -> Result<Self, Self::Err> {
877        let s = s.trim();
878
879        // Handle special values
880        let lower = s.to_lowercase();
881        match lower.as_str() {
882            "nan" | "-nan" | "+nan" => return Ok(Self::nan()),
883            "infinity" | "inf" | "+infinity" | "+inf" => return Ok(Self::infinity()),
884            "-infinity" | "-inf" => return Ok(Self::neg_infinity()),
885            _ => {}
886        }
887
888        // Detect scale from decimal point position
889        let scale = if let Some(dot_pos) = s.find('.') {
890            let after_dot = &s[dot_pos + 1..];
891            // Count digits after decimal (excluding trailing non-digits)
892            after_dot.chars().take_while(|c| c.is_ascii_digit()).count() as u8
893        } else {
894            0
895        };
896
897        Self::new(s, scale.min(MAX_DECIMAL64_SCALE))
898    }
899}
900
901#[cfg(test)]
902mod tests {
903    use super::*;
904
905    // ==================== Basic Tests ====================
906
907    #[test]
908    fn test_new_basic() {
909        let d = Decimal64::new("123.45", 2).unwrap();
910        assert_eq!(d.to_string(), "123.45");
911        assert_eq!(d.scale(), 2);
912        assert_eq!(d.value(), 12345);
913
914        let d = Decimal64::new("100", 0).unwrap();
915        assert_eq!(d.to_string(), "100");
916        assert_eq!(d.scale(), 0);
917
918        let d = Decimal64::new("-50.5", 1).unwrap();
919        assert_eq!(d.to_string(), "-50.5");
920        assert_eq!(d.scale(), 1);
921        assert_eq!(d.value(), -505);
922    }
923
924    #[test]
925    fn test_zero() {
926        let d = Decimal64::new("0", 0).unwrap();
927        assert!(d.is_zero());
928        assert!(!d.is_negative());
929        assert!(!d.is_positive());
930        assert!(d.is_finite());
931
932        let d = Decimal64::new("0.00", 2).unwrap();
933        assert!(d.is_zero());
934        assert_eq!(d.scale(), 2);
935    }
936
937    #[test]
938    fn test_from_str() {
939        let d: Decimal64 = "123.456".parse().unwrap();
940        assert_eq!(d.to_string(), "123.456");
941        assert_eq!(d.scale(), 3);
942
943        let d: Decimal64 = "100".parse().unwrap();
944        assert_eq!(d.to_string(), "100");
945        assert_eq!(d.scale(), 0);
946    }
947
948    // ==================== Special Values ====================
949
950    #[test]
951    fn test_infinity() {
952        let inf = Decimal64::infinity();
953        assert!(inf.is_pos_infinity());
954        assert!(inf.is_infinity());
955        assert!(inf.is_special());
956        assert!(!inf.is_finite());
957        assert_eq!(inf.to_string(), "Infinity");
958
959        let neg_inf = Decimal64::neg_infinity();
960        assert!(neg_inf.is_neg_infinity());
961        assert!(neg_inf.is_infinity());
962        assert_eq!(neg_inf.to_string(), "-Infinity");
963    }
964
965    #[test]
966    fn test_nan() {
967        let nan = Decimal64::nan();
968        assert!(nan.is_nan());
969        assert!(nan.is_special());
970        assert!(!nan.is_finite());
971        assert_eq!(nan.to_string(), "NaN");
972
973        // PostgreSQL semantics: NaN == NaN
974        assert_eq!(nan, Decimal64::nan());
975    }
976
977    #[test]
978    fn test_special_from_str() {
979        assert!(Decimal64::from_str("Infinity").unwrap().is_pos_infinity());
980        assert!(Decimal64::from_str("-Infinity").unwrap().is_neg_infinity());
981        assert!(Decimal64::from_str("NaN").unwrap().is_nan());
982        assert!(Decimal64::from_str("inf").unwrap().is_pos_infinity());
983        assert!(Decimal64::from_str("-inf").unwrap().is_neg_infinity());
984    }
985
986    // ==================== Ordering ====================
987
988    #[test]
989    fn test_ordering_same_scale() {
990        let a = Decimal64::new("100", 0).unwrap();
991        let b = Decimal64::new("200", 0).unwrap();
992        let c = Decimal64::new("-50", 0).unwrap();
993
994        assert!(c < a);
995        assert!(a < b);
996    }
997
998    #[test]
999    fn test_ordering_different_scale() {
1000        let a = Decimal64::new("1.5", 1).unwrap(); // 15 at scale 1
1001        let b = Decimal64::new("1.50", 2).unwrap(); // 150 at scale 2
1002
1003        // 1.5 == 1.50 when normalized
1004        assert_eq!(a.cmp(&b), Ordering::Equal);
1005
1006        let c = Decimal64::new("1.51", 2).unwrap();
1007        assert!(a < c);
1008    }
1009
1010    #[test]
1011    fn test_ordering_with_special() {
1012        let neg_inf = Decimal64::neg_infinity();
1013        let neg = Decimal64::new("-1000", 0).unwrap();
1014        let zero = Decimal64::new("0", 0).unwrap();
1015        let pos = Decimal64::new("1000", 0).unwrap();
1016        let inf = Decimal64::infinity();
1017        let nan = Decimal64::nan();
1018
1019        assert!(neg_inf < neg);
1020        assert!(neg < zero);
1021        assert!(zero < pos);
1022        assert!(pos < inf);
1023        assert!(inf < nan);
1024    }
1025
1026    // ==================== Precision/Scale ====================
1027
1028    #[test]
1029    fn test_precision_scale() {
1030        let d = Decimal64::with_precision_scale("123.456", Some(5), Some(2)).unwrap();
1031        assert_eq!(d.to_string(), "123.46");
1032
1033        let d = Decimal64::with_precision_scale("12345.67", Some(5), Some(2)).unwrap();
1034        assert_eq!(d.to_string(), "345.67");
1035    }
1036
1037    #[test]
1038    fn test_negative_scale() {
1039        let d = Decimal64::with_precision_scale("12345", None, Some(-2)).unwrap();
1040        assert_eq!(d.to_string(), "12300");
1041
1042        let d = Decimal64::with_precision_scale("12350", None, Some(-2)).unwrap();
1043        assert_eq!(d.to_string(), "12400");
1044    }
1045
1046    // ==================== Roundtrip ====================
1047
1048    #[test]
1049    fn test_roundtrip() {
1050        let values = ["0", "123.45", "-99.99", "1000000", "-1"];
1051
1052        for s in values {
1053            let d: Decimal64 = s.parse().unwrap();
1054            let packed = d.raw();
1055            let restored = Decimal64::from_raw(packed);
1056            assert_eq!(
1057                d.to_string(),
1058                restored.to_string(),
1059                "Roundtrip failed for {}",
1060                s
1061            );
1062        }
1063    }
1064
1065    #[test]
1066    fn test_byte_roundtrip() {
1067        let d = Decimal64::new("123.45", 2).unwrap();
1068        let bytes = d.to_be_bytes();
1069        let restored = Decimal64::from_be_bytes(bytes);
1070        assert_eq!(d, restored);
1071    }
1072
1073    // ==================== Conversion ====================
1074
1075    #[test]
1076    fn test_decimal_conversion() {
1077        let d64 = Decimal64::new("123.456", 3).unwrap();
1078        let decimal = d64.to_decimal();
1079        assert_eq!(decimal.to_string(), "123.456");
1080
1081        let d64_back = Decimal64::from_decimal(&decimal, 3).unwrap();
1082        assert_eq!(d64.to_string(), d64_back.to_string());
1083    }
1084
1085    #[test]
1086    fn test_from_parts() {
1087        let d = Decimal64::from_parts(12345, 2).unwrap();
1088        assert_eq!(d.to_string(), "123.45");
1089        assert_eq!(d.value(), 12345);
1090        assert_eq!(d.scale(), 2);
1091    }
1092
1093    // ==================== Error Cases ====================
1094
1095    #[test]
1096    fn test_precision_too_large() {
1097        assert!(Decimal64::with_precision_scale("1", Some(17), Some(0)).is_err());
1098    }
1099
1100    #[test]
1101    fn test_scale_too_large() {
1102        assert!(Decimal64::new("1", 19).is_err());
1103    }
1104
1105    #[test]
1106    fn test_value_too_large() {
1107        // Value that exceeds 56 bits
1108        assert!(Decimal64::from_parts(i64::MAX, 0).is_err());
1109        assert!(Decimal64::from_parts(MIN_VALUE - 1, 0).is_err());
1110    }
1111
1112    // ==================== Edge Cases ====================
1113
1114    #[test]
1115    fn test_min_max_values() {
1116        let min = Decimal64::min_value();
1117        let max = Decimal64::max_value();
1118
1119        assert!(min.is_finite());
1120        assert!(max.is_finite());
1121        assert!(min < max);
1122        assert!(Decimal64::neg_infinity() < min);
1123        assert!(max < Decimal64::infinity());
1124    }
1125
1126    #[test]
1127    fn test_hash() {
1128        use std::collections::HashSet;
1129
1130        let mut set = HashSet::new();
1131        set.insert(Decimal64::new("100", 0).unwrap());
1132        set.insert(Decimal64::new("100", 0).unwrap()); // Duplicate
1133        set.insert(Decimal64::new("200", 0).unwrap());
1134        set.insert(Decimal64::nan());
1135
1136        assert_eq!(set.len(), 3);
1137    }
1138}