1use 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
77pub const MAX_DECIMAL64_PRECISION: u32 = 16;
79
80pub const MAX_DECIMAL64_SCALE: u8 = 18;
82
83const VALUE_BITS: u32 = 56;
85const MAX_VALUE: i64 = (1i64 << (VALUE_BITS - 1)) - 1; const MIN_VALUE: i64 = -(1i64 << (VALUE_BITS - 1)); const VALUE_MASK: i64 = (1i64 << VALUE_BITS) - 1;
88const SIGN_BIT: i64 = 1i64 << (VALUE_BITS - 1); const SENTINEL_NEG_INFINITY: i64 = i64::MIN; const SENTINEL_POS_INFINITY: i64 = i64::MAX - 1; const SENTINEL_NAN: i64 = i64::MAX; #[derive(Clone, Copy)]
146pub struct Decimal64 {
147 packed: i64,
150}
151
152impl Decimal64 {
153 pub fn new(s: &str, scale: u8) -> Result<Self, DecimalError> {
173 Self::with_precision_scale(s, None, Some(scale as i32))
174 }
175
176 pub fn with_precision_scale(
195 s: &str,
196 precision: Option<u32>,
197 scale: Option<i32>,
198 ) -> Result<Self, DecimalError> {
199 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 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 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 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 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 let int_part = int_part.trim_start_matches('0');
253 let int_part = if int_part.is_empty() { "0" } else { int_part };
254
255 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 let (int_part, frac_digits) = Self::apply_scale(int_part, frac_part, scale_u8 as usize);
264
265 let (final_int, final_frac) =
267 Self::apply_precision(&int_part, &frac_digits, precision, scale_u8 as usize);
268
269 Self::parts_to_packed(&final_int, &final_frac, is_negative, scale_u8)
271 }
272
273 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 #[inline]
300 pub const fn from_raw(packed: i64) -> Self {
301 Self { packed }
302 }
303
304 #[inline]
308 pub const fn infinity() -> Self {
309 Self {
310 packed: SENTINEL_POS_INFINITY,
311 }
312 }
313
314 #[inline]
316 pub const fn neg_infinity() -> Self {
317 Self {
318 packed: SENTINEL_NEG_INFINITY,
319 }
320 }
321
322 #[inline]
326 pub const fn nan() -> Self {
327 Self {
328 packed: SENTINEL_NAN,
329 }
330 }
331
332 #[inline]
336 pub const fn raw(&self) -> i64 {
337 self.packed
338 }
339
340 #[inline]
344 pub fn scale(&self) -> u8 {
345 if self.is_special() {
346 0
347 } else {
348 self.scale_byte()
349 }
350 }
351
352 #[inline]
356 pub fn value(&self) -> i64 {
357 if self.is_special() {
358 0
359 } else {
360 self.unpack_value()
361 }
362 }
363
364 #[inline]
366 pub fn is_zero(&self) -> bool {
367 !self.is_special() && self.unpack_value() == 0
368 }
369
370 #[inline]
372 pub fn is_negative(&self) -> bool {
373 !self.is_special() && self.unpack_value() < 0
374 }
375
376 #[inline]
378 pub fn is_positive(&self) -> bool {
379 !self.is_special() && self.unpack_value() > 0
380 }
381
382 #[inline]
384 pub fn is_pos_infinity(&self) -> bool {
385 self.packed == SENTINEL_POS_INFINITY
386 }
387
388 #[inline]
390 pub fn is_neg_infinity(&self) -> bool {
391 self.packed == SENTINEL_NEG_INFINITY
392 }
393
394 #[inline]
396 pub fn is_infinity(&self) -> bool {
397 self.is_pos_infinity() || self.is_neg_infinity()
398 }
399
400 #[inline]
402 pub fn is_nan(&self) -> bool {
403 self.packed == SENTINEL_NAN
404 }
405
406 #[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 #[inline]
416 pub fn is_finite(&self) -> bool {
417 !self.is_special()
418 }
419
420 fn format_decimal(&self) -> String {
424 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 #[inline]
474 pub fn to_be_bytes(&self) -> [u8; 8] {
475 self.packed.to_be_bytes()
476 }
477
478 #[inline]
480 pub fn from_be_bytes(bytes: [u8; 8]) -> Self {
481 Self {
482 packed: i64::from_be_bytes(bytes),
483 }
484 }
485
486 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 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 #[inline]
518 pub const fn min_value() -> Self {
519 Self::pack(MIN_VALUE, 0)
520 }
521
522 #[inline]
524 pub const fn max_value() -> Self {
525 Self::pack(MAX_VALUE, 0)
526 }
527
528 #[inline]
531 const fn pack(value: i64, scale: u8) -> Self {
532 let scale_part = (scale as i64) << VALUE_BITS;
536 let value_part = value & VALUE_MASK;
537 let biased_value = value_part ^ SIGN_BIT; 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 let biased = self.packed & VALUE_MASK;
552 let raw = biased ^ SIGN_BIT; if raw & SIGN_BIT != 0 {
556 raw | (!0i64 << VALUE_BITS)
558 } else {
559 raw
560 }
561 }
562
563 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 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 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 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 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 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 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 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
740fn 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
764impl PartialEq for Decimal64 {
767 fn eq(&self, other: &Self) -> bool {
768 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 match (self.is_special(), other.is_special()) {
785 (true, true) => {
786 self.packed.cmp(&other.packed)
789 }
790 (true, false) => {
791 if self.is_neg_infinity() {
793 Ordering::Less
794 } else {
795 Ordering::Greater }
797 }
798 (false, true) => {
799 if other.is_neg_infinity() {
801 Ordering::Greater
802 } else {
803 Ordering::Less }
805 }
806 (false, false) => {
807 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 self_value.cmp(&other_value)
816 } else {
817 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 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
888impl 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 fn from_str(s: &str) -> Result<Self, Self::Err> {
913 let s = s.trim();
914
915 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 let scale = if let Some(dot_pos) = s.find('.') {
926 let after_dot = &s[dot_pos + 1..];
927 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 #[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 #[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 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 #[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(); let b = Decimal64::new("1.50", 2).unwrap(); 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 #[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 #[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 #[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 #[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 assert!(Decimal64::from_parts(i64::MAX, 0).is_err());
1145 assert!(Decimal64::from_parts(MIN_VALUE - 1, 0).is_err());
1146 }
1147
1148 #[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()); set.insert(Decimal64::new("200", 0).unwrap());
1170 set.insert(Decimal64::nan());
1171
1172 assert_eq!(set.len(), 3);
1173 }
1174
1175 #[test]
1178 fn test_raw_comparison_same_scale() {
1179 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 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 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 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 let ten_scale0 = Decimal64::new("10", 0).unwrap(); let ten_scale2 = Decimal64::new("10.00", 2).unwrap(); assert_eq!(
1229 ten_scale0.cmp(&ten_scale2),
1230 Ordering::Equal,
1231 "Ord trait should compare equal"
1232 );
1233
1234 assert_ne!(
1237 ten_scale0.raw(),
1238 ten_scale2.raw(),
1239 "Raw values differ due to different scales"
1240 );
1241 }
1242}