1use std::error::Error;
16use std::fmt;
17use std::sync::LazyLock;
18
19use anyhow::bail;
20use dec::{Context, Decimal};
21use mz_lowertest::MzReflect;
22use mz_ore::cast;
23use mz_persist_types::columnar::FixedSizeCodec;
24use mz_proto::{ProtoType, RustType, TryFromProtoError};
25use proptest_derive::Arbitrary;
26use serde::{Deserialize, Serialize};
27
28include!(concat!(env!("OUT_DIR"), "/mz_repr.adt.numeric.rs"));
29
30pub const NUMERIC_DATUM_WIDTH: u8 = 13;
32
33pub const NUMERIC_DATUM_WIDTH_USIZE: usize = cast::u8_to_usize(NUMERIC_DATUM_WIDTH);
35
36pub const NUMERIC_DATUM_MAX_PRECISION: u8 = NUMERIC_DATUM_WIDTH * 3;
38
39pub type Numeric = Decimal<NUMERIC_DATUM_WIDTH_USIZE>;
41
42pub const NUMERIC_AGG_WIDTH: u8 = 27;
44
45pub const NUMERIC_AGG_WIDTH_USIZE: usize = cast::u8_to_usize(NUMERIC_AGG_WIDTH);
47
48pub const NUMERIC_AGG_MAX_PRECISION: u8 = NUMERIC_AGG_WIDTH * 3;
50
51pub type NumericAgg = Decimal<NUMERIC_AGG_WIDTH_USIZE>;
53
54static CX_DATUM: LazyLock<Context<Numeric>> = LazyLock::new(|| {
55 let mut cx = Context::<Numeric>::default();
56 cx.set_max_exponent(isize::from(NUMERIC_DATUM_MAX_PRECISION - 1))
57 .unwrap();
58 cx.set_min_exponent(-isize::from(NUMERIC_DATUM_MAX_PRECISION))
59 .unwrap();
60 cx
61});
62static CX_AGG: LazyLock<Context<NumericAgg>> = LazyLock::new(|| {
63 let mut cx = Context::<NumericAgg>::default();
64 cx.set_max_exponent(isize::from(NUMERIC_AGG_MAX_PRECISION - 1))
65 .unwrap();
66 cx.set_min_exponent(-isize::from(NUMERIC_AGG_MAX_PRECISION))
67 .unwrap();
68 cx
69});
70static U128_SPLITTER_DATUM: LazyLock<Numeric> = LazyLock::new(|| {
71 let mut cx = Numeric::context();
72 cx.parse("340282366920938463463374607431768211456").unwrap()
74});
75static U128_SPLITTER_AGG: LazyLock<NumericAgg> = LazyLock::new(|| {
76 let mut cx = NumericAgg::context();
77 cx.parse("340282366920938463463374607431768211456").unwrap()
79});
80
81pub mod str_serde {
83 use std::str::FromStr;
84
85 use serde::Deserialize;
86
87 use super::Numeric;
88
89 pub fn deserialize<'de, D>(deserializer: D) -> Result<Numeric, D::Error>
91 where
92 D: serde::Deserializer<'de>,
93 {
94 let buf = String::deserialize(deserializer)?;
95 Numeric::from_str(&buf).map_err(serde::de::Error::custom)
96 }
97}
98
99#[derive(
105 Arbitrary,
106 Debug,
107 Clone,
108 Copy,
109 Eq,
110 PartialEq,
111 Ord,
112 PartialOrd,
113 Hash,
114 Serialize,
115 Deserialize,
116 MzReflect
117)]
118pub struct NumericMaxScale(pub(crate) u8);
119
120impl NumericMaxScale {
121 pub const ZERO: NumericMaxScale = NumericMaxScale(0);
123
124 pub fn into_u8(self) -> u8 {
126 self.0
127 }
128}
129
130impl TryFrom<i64> for NumericMaxScale {
131 type Error = InvalidNumericMaxScaleError;
132
133 fn try_from(max_scale: i64) -> Result<Self, Self::Error> {
134 match u8::try_from(max_scale) {
135 Ok(max_scale) if max_scale <= NUMERIC_DATUM_MAX_PRECISION => {
136 Ok(NumericMaxScale(max_scale))
137 }
138 _ => Err(InvalidNumericMaxScaleError),
139 }
140 }
141}
142
143impl TryFrom<usize> for NumericMaxScale {
144 type Error = InvalidNumericMaxScaleError;
145
146 fn try_from(max_scale: usize) -> Result<Self, Self::Error> {
147 Self::try_from(i64::try_from(max_scale).map_err(|_| InvalidNumericMaxScaleError)?)
148 }
149}
150
151impl RustType<ProtoNumericMaxScale> for NumericMaxScale {
152 fn into_proto(&self) -> ProtoNumericMaxScale {
153 ProtoNumericMaxScale {
154 value: self.0.into_proto(),
155 }
156 }
157
158 fn from_proto(max_scale: ProtoNumericMaxScale) -> Result<Self, TryFromProtoError> {
159 Ok(NumericMaxScale(max_scale.value.into_rust()?))
160 }
161}
162
163impl RustType<ProtoOptionalNumericMaxScale> for Option<NumericMaxScale> {
164 fn into_proto(&self) -> ProtoOptionalNumericMaxScale {
165 ProtoOptionalNumericMaxScale {
166 value: self.into_proto(),
167 }
168 }
169
170 fn from_proto(max_scale: ProtoOptionalNumericMaxScale) -> Result<Self, TryFromProtoError> {
171 max_scale.value.into_rust()
172 }
173}
174
175#[derive(Debug, Clone)]
178pub struct InvalidNumericMaxScaleError;
179
180impl fmt::Display for InvalidNumericMaxScaleError {
181 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
182 write!(
183 f,
184 "scale for type numeric must be between 0 and {}",
185 NUMERIC_DATUM_MAX_PRECISION
186 )
187 }
188}
189
190impl Error for InvalidNumericMaxScaleError {}
191
192pub trait Dec<const N: usize> {
195 const TWOS_COMPLEMENT_BYTE_WIDTH: usize;
198 fn context() -> Context<Decimal<N>>;
200 fn u128_splitter() -> &'static Decimal<N>;
203}
204
205impl Dec<NUMERIC_DATUM_WIDTH_USIZE> for Numeric {
206 const TWOS_COMPLEMENT_BYTE_WIDTH: usize = 17;
207 fn context() -> Context<Numeric> {
208 CX_DATUM.clone()
209 }
210 fn u128_splitter() -> &'static Numeric {
211 &U128_SPLITTER_DATUM
212 }
213}
214
215impl Dec<NUMERIC_AGG_WIDTH_USIZE> for NumericAgg {
216 const TWOS_COMPLEMENT_BYTE_WIDTH: usize = 33;
217 fn context() -> Context<NumericAgg> {
218 CX_AGG.clone()
219 }
220 fn u128_splitter() -> &'static NumericAgg {
221 &U128_SPLITTER_AGG
222 }
223}
224
225pub fn cx_datum() -> Context<Numeric> {
227 CX_DATUM.clone()
228}
229
230pub fn cx_agg() -> Context<NumericAgg> {
232 CX_AGG.clone()
233}
234
235fn twos_complement_be_to_u128(input: &[u8]) -> u128 {
236 assert!(input.len() <= 16);
237 let mut buf = [0; 16];
238 buf[16 - input.len()..16].copy_from_slice(input);
239 u128::from_be_bytes(buf)
240}
241
242fn negate_twos_complement_le<'a, I>(b: I)
246where
247 I: Iterator<Item = &'a mut u8>,
248{
249 let mut seen_first_one = false;
250 for i in b {
251 if seen_first_one {
252 *i = *i ^ 0xFF;
253 } else if *i > 0 {
254 seen_first_one = true;
255 if i == &0x80 {
256 continue;
257 }
258 let tz = i.trailing_zeros();
259 *i = *i ^ (0xFF << tz + 1);
260 }
261 }
262}
263
264pub fn numeric_to_twos_complement_be(
266 mut numeric: Numeric,
267) -> [u8; Numeric::TWOS_COMPLEMENT_BYTE_WIDTH] {
268 let mut buf = [0; Numeric::TWOS_COMPLEMENT_BYTE_WIDTH];
269 if numeric.is_special() {
273 return buf;
274 }
275
276 let mut cx = Numeric::context();
277
278 if numeric.exponent() < 0 {
280 let s = Numeric::from(-numeric.exponent());
281 cx.scaleb(&mut numeric, &s);
282 }
283
284 numeric_to_twos_complement_inner::<Numeric, NUMERIC_DATUM_WIDTH_USIZE>(
285 numeric, &mut cx, &mut buf,
286 );
287 buf
288}
289
290pub fn numeric_to_twos_complement_wide(
299 numeric: Numeric,
300) -> [u8; NumericAgg::TWOS_COMPLEMENT_BYTE_WIDTH] {
301 let mut buf = [0; NumericAgg::TWOS_COMPLEMENT_BYTE_WIDTH];
302 if numeric.is_special() {
306 return buf;
307 }
308 let mut cx = NumericAgg::context();
309 let mut d = cx.to_width(numeric);
310 let mut scaler = NumericAgg::from(NUMERIC_DATUM_MAX_PRECISION);
311 cx.neg(&mut scaler);
312 cx.rescale(&mut d, &scaler);
314 cx.abs(&mut scaler);
317 cx.scaleb(&mut d, &scaler);
318
319 numeric_to_twos_complement_inner::<NumericAgg, NUMERIC_AGG_WIDTH_USIZE>(d, &mut cx, &mut buf);
320 buf
321}
322
323fn numeric_to_twos_complement_inner<D: Dec<N>, const N: usize>(
324 mut d: Decimal<N>,
325 cx: &mut Context<Decimal<N>>,
326 buf: &mut [u8],
327) {
328 let is_neg = if d.is_negative() {
330 cx.neg(&mut d);
331 true
332 } else {
333 false
334 };
335
336 assert!(d.exponent() >= 0);
338
339 let mut buf_cursor = 0;
340 while !d.is_zero() {
341 let mut w = d.clone();
342 cx.rem(&mut w, D::u128_splitter());
344
345 let c = w.coefficient::<u128>().unwrap();
349
350 let e = std::cmp::min(buf_cursor + 16, D::TWOS_COMPLEMENT_BYTE_WIDTH);
353
354 buf[buf_cursor..e].copy_from_slice(&c.to_le_bytes()[0..e - buf_cursor]);
356 buf_cursor += 16;
358
359 cx.div_integer(&mut d, D::u128_splitter());
361 }
362
363 if is_neg {
364 negate_twos_complement_le(buf.iter_mut());
365 }
366
367 buf.reverse();
369}
370
371pub fn twos_complement_be_to_numeric(
372 input: &mut [u8],
373 scale: u8,
374) -> Result<Numeric, anyhow::Error> {
375 let mut cx = cx_datum();
376 if input.len() <= 17 {
377 if let Ok(mut n) =
378 twos_complement_be_to_numeric_inner::<Numeric, NUMERIC_DATUM_WIDTH_USIZE>(input)
379 {
380 n.set_exponent(-i32::from(scale));
381 return Ok(n);
382 }
383 }
384 let mut n = twos_complement_be_to_numeric_inner::<NumericAgg, NUMERIC_AGG_WIDTH_USIZE>(input)?;
387 n.set_exponent(-i32::from(scale));
389 let d = cx.to_width(n);
390 if cx.status().inexact() {
391 bail!("Value exceeds maximum numeric value")
392 }
393 Ok(d)
394}
395
396pub fn twos_complement_be_to_numeric_inner<D: Dec<N>, const N: usize>(
399 input: &mut [u8],
400) -> Result<Decimal<N>, anyhow::Error> {
401 let is_neg = if (input[0] & 0x80) != 0 {
402 negate_twos_complement_le(input.iter_mut().rev());
405 true
406 } else {
407 false
408 };
409
410 let head = input.len() % 16;
411 let i = twos_complement_be_to_u128(&input[0..head]);
412 let mut cx = D::context();
413 let mut d = cx.from_u128(i);
414
415 for c in input[head..].chunks(16) {
416 assert_eq!(c.len(), 16);
417 cx.mul(&mut d, D::u128_splitter());
419 let i = twos_complement_be_to_u128(c);
420 let i = cx.from_u128(i);
421 cx.add(&mut d, &i);
422 }
423
424 if cx.status().inexact() {
425 bail!("Value exceeds maximum numeric value")
426 } else if cx.status().any() {
427 bail!("unexpected status {:?}", cx.status());
428 }
429 if is_neg {
430 cx.neg(&mut d);
431 }
432 Ok(d)
433}
434
435#[mz_ore::test]
436#[cfg_attr(miri, ignore)] fn test_twos_complement_roundtrip() {
438 fn inner(s: &str) {
439 let mut cx = cx_datum();
440 let d = cx.parse(s).unwrap();
441 let scale = std::cmp::min(d.exponent(), 0).abs();
442 let mut b = numeric_to_twos_complement_be(d.clone());
443 let x = twos_complement_be_to_numeric(&mut b, u8::try_from(scale).unwrap()).unwrap();
444 assert_eq!(d, x);
445 }
446 inner("0");
447 inner("0.000000000000000000000000000000000012345");
448 inner("0.123456789012345678901234567890123456789");
449 inner("1.00000000000000000000000000000000000000");
450 inner("1");
451 inner("2");
452 inner("170141183460469231731687303715884105727");
453 inner("170141183460469231731687303715884105728");
454 inner("12345678901234567890.1234567890123456789");
455 inner("999999999999999999999999999999999999999");
456 inner("7e35");
457 inner("7e-35");
458 inner("-0.000000000000000000000000000000000012345");
459 inner("-0.12345678901234567890123456789012345678");
460 inner("-1.00000000000000000000000000000000000000");
461 inner("-1");
462 inner("-2");
463 inner("-170141183460469231731687303715884105727");
464 inner("-170141183460469231731687303715884105728");
465 inner("-12345678901234567890.1234567890123456789");
466 inner("-999999999999999999999999999999999999999");
467 inner("-7.2e35");
468 inner("-7.2e-35");
469}
470
471#[mz_ore::test]
472#[cfg_attr(miri, ignore)] fn test_twos_comp_numeric_primitive() {
474 fn inner_inner<P>(i: P, i_be_bytes: &mut [u8])
475 where
476 P: Into<Numeric> + TryFrom<Numeric> + Eq + PartialEq + std::fmt::Debug + Copy,
477 {
478 let mut e = [0; Numeric::TWOS_COMPLEMENT_BYTE_WIDTH];
479 e[Numeric::TWOS_COMPLEMENT_BYTE_WIDTH - i_be_bytes.len()..].copy_from_slice(i_be_bytes);
480 let mut w = [0; NumericAgg::TWOS_COMPLEMENT_BYTE_WIDTH];
481 w[NumericAgg::TWOS_COMPLEMENT_BYTE_WIDTH - i_be_bytes.len()..].copy_from_slice(i_be_bytes);
482
483 let d: Numeric = i.into();
484
485 if d.is_negative() {
487 for i in e[..Numeric::TWOS_COMPLEMENT_BYTE_WIDTH - i_be_bytes.len()].iter_mut() {
488 *i = 0xFF;
489 }
490 for i in w[..NumericAgg::TWOS_COMPLEMENT_BYTE_WIDTH - i_be_bytes.len()].iter_mut() {
491 *i = 0xFF;
492 }
493 }
494
495 let d_be_bytes = numeric_to_twos_complement_be(d);
498 assert_eq!(
499 e, d_be_bytes,
500 "expected repr of {:?}, got {:?}",
501 e, d_be_bytes
502 );
503
504 let e_numeric = twos_complement_be_to_numeric(&mut e, 0).unwrap();
506 let e_p: P = e_numeric
507 .try_into()
508 .unwrap_or_else(|_e| panic!("try_into failed"));
509 assert_eq!(i, e_p, "expected val of {:?}, got {:?}", i, e_p);
510
511 let w_numeric = twos_complement_be_to_numeric(&mut w, 0).unwrap();
513 let w_p: P = w_numeric
514 .try_into()
515 .unwrap_or_else(|_e| panic!("try_into failed"));
516 assert_eq!(i, w_p, "expected val of {:?}, got {:?}", i, e_p);
517
518 let p_numeric = twos_complement_be_to_numeric(i_be_bytes, 0).unwrap();
520 let p_p: P = p_numeric
521 .try_into()
522 .unwrap_or_else(|_e| panic!("try_into failed"));
523 assert_eq!(i, p_p, "expected val of {:?}, got {:?}", i, p_p);
524 }
525
526 fn inner_i32(i: i32) {
527 inner_inner(i, &mut i.to_be_bytes());
528 }
529
530 fn inner_i64(i: i64) {
531 inner_inner(i, &mut i.to_be_bytes());
532 }
533
534 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
538 struct FromableI128 {
539 i: i128,
540 }
541 impl From<i128> for FromableI128 {
542 fn from(i: i128) -> FromableI128 {
543 FromableI128 { i }
544 }
545 }
546 impl From<FromableI128> for Numeric {
547 fn from(n: FromableI128) -> Numeric {
548 Numeric::try_from(n.i).unwrap()
549 }
550 }
551 impl TryFrom<Numeric> for FromableI128 {
552 type Error = ();
553 fn try_from(n: Numeric) -> Result<FromableI128, Self::Error> {
554 match i128::try_from(n) {
555 Ok(i) => Ok(FromableI128 { i }),
556 Err(_) => Err(()),
557 }
558 }
559 }
560
561 fn inner_i128(i: i128) {
562 inner_inner(FromableI128::from(i), &mut i.to_be_bytes());
563 }
564
565 inner_i32(0);
566 inner_i32(1);
567 inner_i32(2);
568 inner_i32(-1);
569 inner_i32(-2);
570 inner_i32(i32::MAX);
571 inner_i32(i32::MIN);
572 inner_i32(i32::MAX / 7 + 7);
573 inner_i32(i32::MIN / 7 + 7);
574 inner_i64(0);
575 inner_i64(1);
576 inner_i64(2);
577 inner_i64(-1);
578 inner_i64(-2);
579 inner_i64(i64::MAX);
580 inner_i64(i64::MIN);
581 inner_i64(i64::MAX / 7 + 7);
582 inner_i64(i64::MIN / 7 + 7);
583 inner_i128(0);
584 inner_i128(1);
585 inner_i128(2);
586 inner_i128(-1);
587 inner_i128(-2);
588 inner_i128(i128::from(i64::MAX));
589 inner_i128(i128::from(i64::MIN));
590 inner_i128(i128::MAX);
591 inner_i128(i128::MIN);
592 inner_i128(i128::MAX / 7 + 7);
593 inner_i128(i128::MIN / 7 + 7);
594}
595
596#[mz_ore::test]
597#[cfg_attr(miri, ignore)] fn test_twos_complement_to_numeric_fail() {
599 fn inner(b: &mut [u8]) {
600 let r = twos_complement_be_to_numeric(b, 0);
601 mz_ore::assert_err!(r);
602 }
603 let mut e = [0xFF; Numeric::TWOS_COMPLEMENT_BYTE_WIDTH];
605 e[0] -= 0x80;
606 inner(&mut e);
607
608 let mut e = [0; Numeric::TWOS_COMPLEMENT_BYTE_WIDTH + 1];
610 e[0] = 1;
611 inner(&mut e);
612}
613
614#[mz_ore::test]
615#[cfg_attr(miri, ignore)] fn test_wide_twos_complement_roundtrip() {
617 fn inner(s: &str) {
618 let mut cx = cx_datum();
619 let d = cx.parse(s).unwrap();
620 let mut b = numeric_to_twos_complement_wide(d.clone());
621 let x = twos_complement_be_to_numeric(&mut b, NUMERIC_DATUM_MAX_PRECISION).unwrap();
622 assert_eq!(d, x);
623 }
624 inner("0");
625 inner("0.000000000000000000000000000000000012345");
626 inner("0.123456789012345678901234567890123456789");
627 inner("1.00000000000000000000000000000000000000");
628 inner("1");
629 inner("2");
630 inner("170141183460469231731687303715884105727");
631 inner("170141183460469231731687303715884105728");
632 inner("12345678901234567890.1234567890123456789");
633 inner("999999999999999999999999999999999999999");
634 inner("-0.000000000000000000000000000000000012345");
635 inner("-0.123456789012345678901234567890123456789");
636 inner("-1.00000000000000000000000000000000000000");
637 inner("-1");
638 inner("-2");
639 inner("-170141183460469231731687303715884105727");
640 inner("-170141183460469231731687303715884105728");
641 inner("-12345678901234567890.1234567890123456789");
642 inner("-999999999999999999999999999999999999999");
643}
644
645pub fn get_precision<const N: usize>(n: &Decimal<N>) -> u32 {
648 let e = n.exponent();
649 if e >= 0 {
650 n.digits() + u32::try_from(e).unwrap()
652 } else {
653 let d = n.digits();
655 let e = u32::try_from(e.abs()).unwrap();
656 std::cmp::max(d, e)
660 }
661}
662
663pub fn get_scale(n: &Numeric) -> u32 {
665 let exp = n.exponent();
666 if exp >= 0 { 0 } else { exp.unsigned_abs() }
667}
668
669pub fn munge_numeric(n: &mut Numeric) -> Result<(), anyhow::Error> {
676 rescale_within_max_precision(n)?;
677 if (n.is_zero() || n.is_nan()) && n.is_negative() {
678 cx_datum().neg(n);
679 }
680 Ok(())
681}
682
683fn rescale_within_max_precision(n: &mut Numeric) -> Result<(), anyhow::Error> {
686 let current_precision = get_precision(n);
687 if current_precision > u32::from(NUMERIC_DATUM_MAX_PRECISION) {
688 if n.exponent() < 0 {
689 let precision_diff = current_precision - u32::from(NUMERIC_DATUM_MAX_PRECISION);
690 let current_scale = u8::try_from(get_scale(n))?;
691 let scale_diff = current_scale - u8::try_from(precision_diff).unwrap();
692 rescale(n, scale_diff)?;
693 } else {
694 bail!(
695 "numeric value {} exceed maximum precision {}",
696 n,
697 NUMERIC_DATUM_MAX_PRECISION
698 )
699 }
700 }
701 Ok(())
702}
703
704pub fn rescale(n: &mut Numeric, scale: u8) -> Result<(), anyhow::Error> {
709 let mut cx = cx_datum();
710 cx.rescale(n, &Numeric::from(-i32::from(scale)));
711 if cx.status().invalid_operation() || get_precision(n) > u32::from(NUMERIC_DATUM_MAX_PRECISION)
712 {
713 bail!(
714 "numeric value {} exceed maximum precision {}",
715 n,
716 NUMERIC_DATUM_MAX_PRECISION
717 )
718 }
719 munge_numeric(n)?;
720
721 Ok(())
722}
723
724pub trait DecimalLike:
727 From<u8>
728 + From<u16>
729 + From<u32>
730 + From<i8>
731 + From<i16>
732 + From<i32>
733 + From<f32>
734 + From<f64>
735 + std::ops::Add<Output = Self>
736 + std::ops::Sub<Output = Self>
737 + std::ops::Mul<Output = Self>
738 + std::ops::Div<Output = Self>
739{
740 fn lossy_from(i: i64) -> Self;
743}
744
745impl DecimalLike for f64 {
746 #[allow(clippy::as_conversions)]
748 fn lossy_from(i: i64) -> Self {
749 i as f64
750 }
751}
752
753impl DecimalLike for Numeric {
754 fn lossy_from(i: i64) -> Self {
755 Numeric::from(i)
756 }
757}
758
759#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
764pub struct PackedNumeric(pub [u8; 40]);
765
766impl FixedSizeCodec<Numeric> for PackedNumeric {
767 const SIZE: usize = 40;
768
769 fn as_bytes(&self) -> &[u8] {
770 &self.0
771 }
772
773 fn from_bytes(slice: &[u8]) -> Result<Self, String> {
774 let buf: [u8; Self::SIZE] = slice.try_into().map_err(|_| {
775 format!(
776 "size for PackedNumeric is {} bytes, got {}",
777 Self::SIZE,
778 slice.len()
779 )
780 })?;
781 Ok(PackedNumeric(buf))
782 }
783
784 fn from_value(val: Numeric) -> PackedNumeric {
785 let (digits, exponent, bits, lsu) = val.to_raw_parts();
786
787 let mut buf = [0u8; 40];
788
789 buf[0..4].copy_from_slice(&digits.to_le_bytes());
790 buf[4..8].copy_from_slice(&exponent.to_le_bytes());
791
792 for i in 0..13 {
793 buf[(i * 2) + 8..(i * 2) + 10].copy_from_slice(&lsu[i].to_le_bytes());
794 }
795
796 buf[34..35].copy_from_slice(&bits.to_le_bytes());
797
798 PackedNumeric(buf)
799 }
800
801 fn into_value(self) -> Numeric {
802 let digits: [u8; 4] = self.0[0..4].try_into().unwrap();
803 let digits = u32::from_le_bytes(digits);
804
805 let exponent: [u8; 4] = self.0[4..8].try_into().unwrap();
806 let exponent = i32::from_le_bytes(exponent);
807
808 let mut lsu = [0u16; 13];
809 for i in 0..13 {
810 let x: [u8; 2] = self.0[(i * 2) + 8..(i * 2) + 10].try_into().unwrap();
811 let x = u16::from_le_bytes(x);
812 lsu[i] = x;
813 }
814
815 let bits: [u8; 1] = self.0[34..35].try_into().unwrap();
816 let bits = u8::from_le_bytes(bits);
817
818 Numeric::from_raw_parts(digits, exponent, bits, lsu)
819 }
820}
821
822#[cfg(test)]
823mod tests {
824 use mz_ore::assert_ok;
825 use mz_proto::protobuf_roundtrip;
826 use proptest::prelude::*;
827
828 use crate::scalar::arb_numeric;
829
830 use super::*;
831
832 proptest! {
833 #[mz_ore::test]
834 fn numeric_max_scale_protobuf_roundtrip(expect in any::<NumericMaxScale>()) {
835 let actual = protobuf_roundtrip::<_, ProtoNumericMaxScale>(&expect);
836 assert_ok!(actual);
837 assert_eq!(actual.unwrap(), expect);
838 }
839
840 #[mz_ore::test]
841 fn optional_numeric_max_scale_protobuf_roundtrip(
842 expect in any::<Option<NumericMaxScale>>(),
843 ) {
844 let actual = protobuf_roundtrip::<_, ProtoOptionalNumericMaxScale>(&expect);
845 assert_ok!(actual);
846 assert_eq!(actual.unwrap(), expect);
847 }
848 }
849
850 #[mz_ore::test]
851 #[cfg_attr(miri, ignore)] fn smoketest_packed_numeric_roundtrips() {
853 let og = PackedNumeric::from_value(Numeric::from(-42));
854 let bytes = og.as_bytes();
855 let rnd = PackedNumeric::from_bytes(bytes).expect("valid");
856 assert_eq!(og, rnd);
857
858 mz_ore::assert_err!(PackedNumeric::from_bytes(&[0, 0, 0, 0]));
860 }
861
862 #[mz_ore::test]
863 #[cfg_attr(miri, ignore)] fn proptest_packed_numeric_roundtrip() {
865 fn test(og: Numeric) {
866 let packed = PackedNumeric::from_value(og);
867 let rnd = packed.into_value();
868
869 if og.is_nan() && rnd.is_nan() {
870 return;
871 }
872 assert_eq!(og, rnd);
873 }
874
875 proptest!(|(num in arb_numeric())| {
876 test(num);
877 });
878 }
879
880 #[mz_ore::test]
884 #[cfg_attr(miri, ignore)] fn packed_numeric_stability() {
886 const RNG_SEED: [u8; 32] = [
889 0xf4, 0x16, 0x16, 0x48, 0xc3, 0xac, 0x77, 0xac, 0x72, 0x20, 0x0b, 0xea, 0x99, 0x67,
890 0x2d, 0x6d, 0xca, 0x9f, 0x76, 0xaf, 0x1b, 0x09, 0x73, 0xa0, 0x59, 0x22, 0x6d, 0xc5,
891 0x46, 0x39, 0x1c, 0x4a,
892 ];
893
894 let rng = proptest::test_runner::TestRng::from_seed(
895 proptest::test_runner::RngAlgorithm::ChaCha,
896 &RNG_SEED,
897 );
898 let config = proptest::test_runner::Config {
900 cases: u32::MAX,
902 rng_algorithm: proptest::test_runner::RngAlgorithm::ChaCha,
903 ..Default::default()
904 };
905 let mut runner = proptest::test_runner::TestRunner::new_with_rng(config, rng);
906
907 let test_cases = 2_000;
908 let strat = arb_numeric();
909
910 let mut all_numerics = Vec::new();
911 for _ in 0..test_cases {
912 let value_tree = strat.new_tree(&mut runner).unwrap();
913 let numeric = value_tree.current();
914 let packed = PackedNumeric::from_value(numeric);
915 let hex_bytes = format!("{:x?}", packed.as_bytes());
916
917 all_numerics.push((numeric, hex_bytes));
918 }
919
920 insta::assert_debug_snapshot!(all_numerics);
921 }
922}