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