1use std::error::Error;
16use std::fmt;
17use std::sync::LazyLock;
18
19use anyhow::bail;
20use dec::{Context, Decimal};
21use mz_ore::cast;
22use mz_persist_types::columnar::FixedSizeCodec;
23use mz_proto::{ProtoType, RustType, TryFromProtoError};
24#[cfg(any(test, feature = "proptest"))]
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 Debug,
106 Clone,
107 Copy,
108 Eq,
109 PartialEq,
110 Ord,
111 PartialOrd,
112 Hash,
113 Serialize,
114 Deserialize
115)]
116#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))]
117pub struct NumericMaxScale(pub(crate) u8);
118
119impl NumericMaxScale {
120 pub const ZERO: NumericMaxScale = NumericMaxScale(0);
122
123 pub fn into_u8(self) -> u8 {
125 self.0
126 }
127}
128
129impl TryFrom<i64> for NumericMaxScale {
130 type Error = InvalidNumericMaxScaleError;
131
132 fn try_from(max_scale: i64) -> Result<Self, Self::Error> {
133 match u8::try_from(max_scale) {
134 Ok(max_scale) if max_scale <= NUMERIC_DATUM_MAX_PRECISION => {
135 Ok(NumericMaxScale(max_scale))
136 }
137 _ => Err(InvalidNumericMaxScaleError),
138 }
139 }
140}
141
142impl TryFrom<usize> for NumericMaxScale {
143 type Error = InvalidNumericMaxScaleError;
144
145 fn try_from(max_scale: usize) -> Result<Self, Self::Error> {
146 Self::try_from(i64::try_from(max_scale).map_err(|_| InvalidNumericMaxScaleError)?)
147 }
148}
149
150impl RustType<ProtoNumericMaxScale> for NumericMaxScale {
151 fn into_proto(&self) -> ProtoNumericMaxScale {
152 ProtoNumericMaxScale {
153 value: self.0.into_proto(),
154 }
155 }
156
157 fn from_proto(max_scale: ProtoNumericMaxScale) -> Result<Self, TryFromProtoError> {
158 Ok(NumericMaxScale(max_scale.value.into_rust()?))
159 }
160}
161
162impl RustType<ProtoOptionalNumericMaxScale> for Option<NumericMaxScale> {
163 fn into_proto(&self) -> ProtoOptionalNumericMaxScale {
164 ProtoOptionalNumericMaxScale {
165 value: self.into_proto(),
166 }
167 }
168
169 fn from_proto(max_scale: ProtoOptionalNumericMaxScale) -> Result<Self, TryFromProtoError> {
170 max_scale.value.into_rust()
171 }
172}
173
174#[derive(Debug, Clone)]
177pub struct InvalidNumericMaxScaleError;
178
179impl fmt::Display for InvalidNumericMaxScaleError {
180 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
181 write!(
182 f,
183 "scale for type numeric must be between 0 and {}",
184 NUMERIC_DATUM_MAX_PRECISION
185 )
186 }
187}
188
189impl Error for InvalidNumericMaxScaleError {}
190
191pub trait Dec<const N: usize> {
194 const TWOS_COMPLEMENT_BYTE_WIDTH: usize;
197 fn context() -> Context<Decimal<N>>;
199 fn u128_splitter() -> &'static Decimal<N>;
202}
203
204impl Dec<NUMERIC_DATUM_WIDTH_USIZE> for Numeric {
205 const TWOS_COMPLEMENT_BYTE_WIDTH: usize = 17;
206 fn context() -> Context<Numeric> {
207 CX_DATUM.clone()
208 }
209 fn u128_splitter() -> &'static Numeric {
210 &U128_SPLITTER_DATUM
211 }
212}
213
214impl Dec<NUMERIC_AGG_WIDTH_USIZE> for NumericAgg {
215 const TWOS_COMPLEMENT_BYTE_WIDTH: usize = 33;
216 fn context() -> Context<NumericAgg> {
217 CX_AGG.clone()
218 }
219 fn u128_splitter() -> &'static NumericAgg {
220 &U128_SPLITTER_AGG
221 }
222}
223
224pub fn cx_datum() -> Context<Numeric> {
226 CX_DATUM.clone()
227}
228
229pub fn cx_agg() -> Context<NumericAgg> {
231 CX_AGG.clone()
232}
233
234fn twos_complement_be_to_u128(input: &[u8]) -> u128 {
235 assert!(input.len() <= 16);
236 let mut buf = [0; 16];
237 buf[16 - input.len()..16].copy_from_slice(input);
238 u128::from_be_bytes(buf)
239}
240
241fn negate_twos_complement_le<'a, I>(b: I)
245where
246 I: Iterator<Item = &'a mut u8>,
247{
248 let mut seen_first_one = false;
249 for i in b {
250 if seen_first_one {
251 *i = *i ^ 0xFF;
252 } else if *i > 0 {
253 seen_first_one = true;
254 if i == &0x80 {
255 continue;
256 }
257 let tz = i.trailing_zeros();
258 *i = *i ^ (0xFF << tz + 1);
259 }
260 }
261}
262
263pub fn numeric_to_twos_complement_be(
265 mut numeric: Numeric,
266) -> [u8; Numeric::TWOS_COMPLEMENT_BYTE_WIDTH] {
267 let mut buf = [0; Numeric::TWOS_COMPLEMENT_BYTE_WIDTH];
268 if numeric.is_special() {
272 return buf;
273 }
274
275 let mut cx = Numeric::context();
276
277 if numeric.exponent() < 0 {
279 let s = Numeric::from(-numeric.exponent());
280 cx.scaleb(&mut numeric, &s);
281 }
282
283 numeric_to_twos_complement_inner::<Numeric, NUMERIC_DATUM_WIDTH_USIZE>(
284 numeric, &mut cx, &mut buf,
285 );
286 buf
287}
288
289pub fn numeric_to_twos_complement_wide(
298 numeric: Numeric,
299) -> [u8; NumericAgg::TWOS_COMPLEMENT_BYTE_WIDTH] {
300 let mut buf = [0; NumericAgg::TWOS_COMPLEMENT_BYTE_WIDTH];
301 if numeric.is_special() {
305 return buf;
306 }
307 let mut cx = NumericAgg::context();
308 let mut d = cx.to_width(numeric);
309 let mut scaler = NumericAgg::from(NUMERIC_DATUM_MAX_PRECISION);
310 cx.neg(&mut scaler);
311 cx.rescale(&mut d, &scaler);
313 cx.abs(&mut scaler);
316 cx.scaleb(&mut d, &scaler);
317
318 numeric_to_twos_complement_inner::<NumericAgg, NUMERIC_AGG_WIDTH_USIZE>(d, &mut cx, &mut buf);
319 buf
320}
321
322fn numeric_to_twos_complement_inner<D: Dec<N>, const N: usize>(
323 mut d: Decimal<N>,
324 cx: &mut Context<Decimal<N>>,
325 buf: &mut [u8],
326) {
327 let is_neg = if d.is_negative() {
329 cx.neg(&mut d);
330 true
331 } else {
332 false
333 };
334
335 assert!(d.exponent() >= 0);
337
338 let mut buf_cursor = 0;
339 while !d.is_zero() {
340 let mut w = d.clone();
341 cx.rem(&mut w, D::u128_splitter());
343
344 let c = w.coefficient::<u128>().unwrap();
348
349 let e = std::cmp::min(buf_cursor + 16, D::TWOS_COMPLEMENT_BYTE_WIDTH);
352
353 buf[buf_cursor..e].copy_from_slice(&c.to_le_bytes()[0..e - buf_cursor]);
355 buf_cursor += 16;
357
358 cx.div_integer(&mut d, D::u128_splitter());
360 }
361
362 if is_neg {
363 negate_twos_complement_le(buf.iter_mut());
364 }
365
366 buf.reverse();
368}
369
370pub fn twos_complement_be_to_numeric(
371 input: &mut [u8],
372 scale: u8,
373) -> Result<Numeric, anyhow::Error> {
374 let mut cx = cx_datum();
375 if input.len() <= 17 {
376 if let Ok(mut n) =
377 twos_complement_be_to_numeric_inner::<Numeric, NUMERIC_DATUM_WIDTH_USIZE>(input)
378 {
379 n.set_exponent(-i32::from(scale));
380 return Ok(n);
381 }
382 }
383 let mut n = twos_complement_be_to_numeric_inner::<NumericAgg, NUMERIC_AGG_WIDTH_USIZE>(input)?;
386 n.set_exponent(-i32::from(scale));
388 let d = cx.to_width(n);
389 if cx.status().inexact() {
390 bail!("Value exceeds maximum numeric value")
391 }
392 Ok(d)
393}
394
395pub fn twos_complement_be_to_numeric_inner<D: Dec<N>, const N: usize>(
398 input: &mut [u8],
399) -> Result<Decimal<N>, anyhow::Error> {
400 if input.is_empty() {
401 bail!("cannot parse a numeric value from an empty byte string");
407 }
408 let is_neg = if (input[0] & 0x80) != 0 {
409 negate_twos_complement_le(input.iter_mut().rev());
412 true
413 } else {
414 false
415 };
416
417 let head = input.len() % 16;
418 let i = twos_complement_be_to_u128(&input[0..head]);
419 let mut cx = D::context();
420 let mut d = cx.from_u128(i);
421
422 for c in input[head..].chunks(16) {
423 assert_eq!(c.len(), 16);
424 cx.mul(&mut d, D::u128_splitter());
426 let i = twos_complement_be_to_u128(c);
427 let i = cx.from_u128(i);
428 cx.add(&mut d, &i);
429 }
430
431 if cx.status().inexact() {
432 bail!("Value exceeds maximum numeric value")
433 } else if cx.status().any() {
434 bail!("unexpected status {:?}", cx.status());
435 }
436 if is_neg {
437 cx.neg(&mut d);
438 }
439 Ok(d)
440}
441
442#[mz_ore::test]
443#[cfg_attr(miri, ignore)] fn test_twos_complement_roundtrip() {
445 fn inner(s: &str) {
446 let mut cx = cx_datum();
447 let d = cx.parse(s).unwrap();
448 let scale = std::cmp::min(d.exponent(), 0).abs();
449 let mut b = numeric_to_twos_complement_be(d.clone());
450 let x = twos_complement_be_to_numeric(&mut b, u8::try_from(scale).unwrap()).unwrap();
451 assert_eq!(d, x);
452 }
453 inner("0");
454 inner("0.000000000000000000000000000000000012345");
455 inner("0.123456789012345678901234567890123456789");
456 inner("1.00000000000000000000000000000000000000");
457 inner("1");
458 inner("2");
459 inner("170141183460469231731687303715884105727");
460 inner("170141183460469231731687303715884105728");
461 inner("12345678901234567890.1234567890123456789");
462 inner("999999999999999999999999999999999999999");
463 inner("7e35");
464 inner("7e-35");
465 inner("-0.000000000000000000000000000000000012345");
466 inner("-0.12345678901234567890123456789012345678");
467 inner("-1.00000000000000000000000000000000000000");
468 inner("-1");
469 inner("-2");
470 inner("-170141183460469231731687303715884105727");
471 inner("-170141183460469231731687303715884105728");
472 inner("-12345678901234567890.1234567890123456789");
473 inner("-999999999999999999999999999999999999999");
474 inner("-7.2e35");
475 inner("-7.2e-35");
476}
477
478#[mz_ore::test]
479#[cfg_attr(miri, ignore)] fn test_twos_complement_empty_is_error() {
481 for scale in [0u8, 1, 38] {
486 assert!(twos_complement_be_to_numeric(&mut [], scale).is_err());
487 }
488 assert!(
489 twos_complement_be_to_numeric_inner::<Numeric, NUMERIC_DATUM_WIDTH_USIZE>(&mut []).is_err()
490 );
491}
492
493#[mz_ore::test]
494#[cfg_attr(miri, ignore)] fn test_twos_comp_numeric_primitive() {
496 fn inner_inner<P>(i: P, i_be_bytes: &mut [u8])
497 where
498 P: Into<Numeric> + TryFrom<Numeric> + Eq + PartialEq + std::fmt::Debug + Copy,
499 {
500 let mut e = [0; Numeric::TWOS_COMPLEMENT_BYTE_WIDTH];
501 e[Numeric::TWOS_COMPLEMENT_BYTE_WIDTH - i_be_bytes.len()..].copy_from_slice(i_be_bytes);
502 let mut w = [0; NumericAgg::TWOS_COMPLEMENT_BYTE_WIDTH];
503 w[NumericAgg::TWOS_COMPLEMENT_BYTE_WIDTH - i_be_bytes.len()..].copy_from_slice(i_be_bytes);
504
505 let d: Numeric = i.into();
506
507 if d.is_negative() {
509 for i in e[..Numeric::TWOS_COMPLEMENT_BYTE_WIDTH - i_be_bytes.len()].iter_mut() {
510 *i = 0xFF;
511 }
512 for i in w[..NumericAgg::TWOS_COMPLEMENT_BYTE_WIDTH - i_be_bytes.len()].iter_mut() {
513 *i = 0xFF;
514 }
515 }
516
517 let d_be_bytes = numeric_to_twos_complement_be(d);
520 assert_eq!(
521 e, d_be_bytes,
522 "expected repr of {:?}, got {:?}",
523 e, d_be_bytes
524 );
525
526 let e_numeric = twos_complement_be_to_numeric(&mut e, 0).unwrap();
528 let e_p: P = e_numeric
529 .try_into()
530 .unwrap_or_else(|_e| panic!("try_into failed"));
531 assert_eq!(i, e_p, "expected val of {:?}, got {:?}", i, e_p);
532
533 let w_numeric = twos_complement_be_to_numeric(&mut w, 0).unwrap();
535 let w_p: P = w_numeric
536 .try_into()
537 .unwrap_or_else(|_e| panic!("try_into failed"));
538 assert_eq!(i, w_p, "expected val of {:?}, got {:?}", i, e_p);
539
540 let p_numeric = twos_complement_be_to_numeric(i_be_bytes, 0).unwrap();
542 let p_p: P = p_numeric
543 .try_into()
544 .unwrap_or_else(|_e| panic!("try_into failed"));
545 assert_eq!(i, p_p, "expected val of {:?}, got {:?}", i, p_p);
546 }
547
548 fn inner_i32(i: i32) {
549 inner_inner(i, &mut i.to_be_bytes());
550 }
551
552 fn inner_i64(i: i64) {
553 inner_inner(i, &mut i.to_be_bytes());
554 }
555
556 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
560 struct FromableI128 {
561 i: i128,
562 }
563 impl From<i128> for FromableI128 {
564 fn from(i: i128) -> FromableI128 {
565 FromableI128 { i }
566 }
567 }
568 impl From<FromableI128> for Numeric {
569 fn from(n: FromableI128) -> Numeric {
570 Numeric::try_from(n.i).unwrap()
571 }
572 }
573 impl TryFrom<Numeric> for FromableI128 {
574 type Error = ();
575 fn try_from(n: Numeric) -> Result<FromableI128, Self::Error> {
576 match i128::try_from(n) {
577 Ok(i) => Ok(FromableI128 { i }),
578 Err(_) => Err(()),
579 }
580 }
581 }
582
583 fn inner_i128(i: i128) {
584 inner_inner(FromableI128::from(i), &mut i.to_be_bytes());
585 }
586
587 inner_i32(0);
588 inner_i32(1);
589 inner_i32(2);
590 inner_i32(-1);
591 inner_i32(-2);
592 inner_i32(i32::MAX);
593 inner_i32(i32::MIN);
594 inner_i32(i32::MAX / 7 + 7);
595 inner_i32(i32::MIN / 7 + 7);
596 inner_i64(0);
597 inner_i64(1);
598 inner_i64(2);
599 inner_i64(-1);
600 inner_i64(-2);
601 inner_i64(i64::MAX);
602 inner_i64(i64::MIN);
603 inner_i64(i64::MAX / 7 + 7);
604 inner_i64(i64::MIN / 7 + 7);
605 inner_i128(0);
606 inner_i128(1);
607 inner_i128(2);
608 inner_i128(-1);
609 inner_i128(-2);
610 inner_i128(i128::from(i64::MAX));
611 inner_i128(i128::from(i64::MIN));
612 inner_i128(i128::MAX);
613 inner_i128(i128::MIN);
614 inner_i128(i128::MAX / 7 + 7);
615 inner_i128(i128::MIN / 7 + 7);
616}
617
618#[mz_ore::test]
619#[cfg_attr(miri, ignore)] fn test_twos_complement_to_numeric_fail() {
621 fn inner(b: &mut [u8]) {
622 let r = twos_complement_be_to_numeric(b, 0);
623 mz_ore::assert_err!(r);
624 }
625 let mut e = [0xFF; Numeric::TWOS_COMPLEMENT_BYTE_WIDTH];
627 e[0] -= 0x80;
628 inner(&mut e);
629
630 let mut e = [0; Numeric::TWOS_COMPLEMENT_BYTE_WIDTH + 1];
632 e[0] = 1;
633 inner(&mut e);
634}
635
636#[mz_ore::test]
637#[cfg_attr(miri, ignore)] fn test_wide_twos_complement_roundtrip() {
639 fn inner(s: &str) {
640 let mut cx = cx_datum();
641 let d = cx.parse(s).unwrap();
642 let mut b = numeric_to_twos_complement_wide(d.clone());
643 let x = twos_complement_be_to_numeric(&mut b, NUMERIC_DATUM_MAX_PRECISION).unwrap();
644 assert_eq!(d, x);
645 }
646 inner("0");
647 inner("0.000000000000000000000000000000000012345");
648 inner("0.123456789012345678901234567890123456789");
649 inner("1.00000000000000000000000000000000000000");
650 inner("1");
651 inner("2");
652 inner("170141183460469231731687303715884105727");
653 inner("170141183460469231731687303715884105728");
654 inner("12345678901234567890.1234567890123456789");
655 inner("999999999999999999999999999999999999999");
656 inner("-0.000000000000000000000000000000000012345");
657 inner("-0.123456789012345678901234567890123456789");
658 inner("-1.00000000000000000000000000000000000000");
659 inner("-1");
660 inner("-2");
661 inner("-170141183460469231731687303715884105727");
662 inner("-170141183460469231731687303715884105728");
663 inner("-12345678901234567890.1234567890123456789");
664 inner("-999999999999999999999999999999999999999");
665}
666
667pub fn get_precision<const N: usize>(n: &Decimal<N>) -> u32 {
670 let e = n.exponent();
671 if e >= 0 {
672 n.digits() + u32::try_from(e).unwrap()
674 } else {
675 let d = n.digits();
677 let e = u32::try_from(e.abs()).unwrap();
678 std::cmp::max(d, e)
682 }
683}
684
685pub fn get_scale(n: &Numeric) -> u32 {
687 let exp = n.exponent();
688 if exp >= 0 { 0 } else { exp.unsigned_abs() }
689}
690
691pub fn munge_numeric(n: &mut Numeric) -> Result<(), anyhow::Error> {
698 rescale_within_max_precision(n)?;
699 if (n.is_zero() || n.is_nan()) && n.is_negative() {
700 cx_datum().neg(n);
701 }
702 Ok(())
703}
704
705fn rescale_within_max_precision(n: &mut Numeric) -> Result<(), anyhow::Error> {
708 let current_precision = get_precision(n);
709 if current_precision > u32::from(NUMERIC_DATUM_MAX_PRECISION) {
710 if n.exponent() < 0 {
711 let precision_diff = current_precision - u32::from(NUMERIC_DATUM_MAX_PRECISION);
712 let current_scale = u8::try_from(get_scale(n))?;
713 let scale_diff = current_scale - u8::try_from(precision_diff).unwrap();
714 rescale(n, scale_diff)?;
715 } else {
716 bail!(
717 "numeric value {} exceed maximum precision {}",
718 n,
719 NUMERIC_DATUM_MAX_PRECISION
720 )
721 }
722 }
723 Ok(())
724}
725
726pub fn rescale(n: &mut Numeric, scale: u8) -> Result<(), anyhow::Error> {
731 let mut cx = cx_datum();
732 cx.rescale(n, &Numeric::from(-i32::from(scale)));
733 if cx.status().invalid_operation() || get_precision(n) > u32::from(NUMERIC_DATUM_MAX_PRECISION)
734 {
735 bail!(
736 "numeric value {} exceed maximum precision {}",
737 n,
738 NUMERIC_DATUM_MAX_PRECISION
739 )
740 }
741 munge_numeric(n)?;
742
743 Ok(())
744}
745
746pub trait DecimalLike:
749 From<u8>
750 + From<u16>
751 + From<u32>
752 + From<i8>
753 + From<i16>
754 + From<i32>
755 + From<f32>
756 + From<f64>
757 + std::ops::Add<Output = Self>
758 + std::ops::Sub<Output = Self>
759 + std::ops::Mul<Output = Self>
760 + std::ops::Div<Output = Self>
761{
762 fn lossy_from(i: i64) -> Self;
765}
766
767impl DecimalLike for f64 {
768 #[allow(clippy::as_conversions)]
770 fn lossy_from(i: i64) -> Self {
771 i as f64
772 }
773}
774
775impl DecimalLike for Numeric {
776 fn lossy_from(i: i64) -> Self {
777 Numeric::from(i)
778 }
779}
780
781#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
786pub struct PackedNumeric(pub [u8; 40]);
787
788impl FixedSizeCodec<Numeric> for PackedNumeric {
789 const SIZE: usize = 40;
790
791 fn as_bytes(&self) -> &[u8] {
792 &self.0
793 }
794
795 fn from_bytes(slice: &[u8]) -> Result<Self, String> {
796 let buf: [u8; Self::SIZE] = slice.try_into().map_err(|_| {
797 format!(
798 "size for PackedNumeric is {} bytes, got {}",
799 Self::SIZE,
800 slice.len()
801 )
802 })?;
803 Ok(PackedNumeric(buf))
804 }
805
806 fn from_value(val: Numeric) -> PackedNumeric {
807 let (digits, exponent, bits, lsu) = val.to_raw_parts();
808
809 let mut buf = [0u8; 40];
810
811 buf[0..4].copy_from_slice(&digits.to_le_bytes());
812 buf[4..8].copy_from_slice(&exponent.to_le_bytes());
813
814 for i in 0..13 {
815 buf[(i * 2) + 8..(i * 2) + 10].copy_from_slice(&lsu[i].to_le_bytes());
816 }
817
818 buf[34..35].copy_from_slice(&bits.to_le_bytes());
819
820 PackedNumeric(buf)
821 }
822
823 fn into_value(self) -> Numeric {
824 let digits: [u8; 4] = self.0[0..4].try_into().unwrap();
825 let digits = u32::from_le_bytes(digits);
826
827 let exponent: [u8; 4] = self.0[4..8].try_into().unwrap();
828 let exponent = i32::from_le_bytes(exponent);
829
830 let mut lsu = [0u16; 13];
831 for i in 0..13 {
832 let x: [u8; 2] = self.0[(i * 2) + 8..(i * 2) + 10].try_into().unwrap();
833 let x = u16::from_le_bytes(x);
834 lsu[i] = x;
835 }
836
837 let bits: [u8; 1] = self.0[34..35].try_into().unwrap();
838 let bits = u8::from_le_bytes(bits);
839
840 Numeric::from_raw_parts(digits, exponent, bits, lsu)
841 }
842}
843
844#[cfg(test)]
845mod tests {
846 use mz_ore::assert_ok;
847 use mz_proto::protobuf_roundtrip;
848 use proptest::prelude::*;
849
850 use crate::scalar::arb_numeric;
851
852 use super::*;
853
854 proptest! {
855 #[mz_ore::test]
856 fn numeric_max_scale_protobuf_roundtrip(expect in any::<NumericMaxScale>()) {
857 let actual = protobuf_roundtrip::<_, ProtoNumericMaxScale>(&expect);
858 assert_ok!(actual);
859 assert_eq!(actual.unwrap(), expect);
860 }
861
862 #[mz_ore::test]
863 fn optional_numeric_max_scale_protobuf_roundtrip(
864 expect in any::<Option<NumericMaxScale>>(),
865 ) {
866 let actual = protobuf_roundtrip::<_, ProtoOptionalNumericMaxScale>(&expect);
867 assert_ok!(actual);
868 assert_eq!(actual.unwrap(), expect);
869 }
870 }
871
872 #[mz_ore::test]
873 #[cfg_attr(miri, ignore)] fn smoketest_packed_numeric_roundtrips() {
875 let og = PackedNumeric::from_value(Numeric::from(-42));
876 let bytes = og.as_bytes();
877 let rnd = PackedNumeric::from_bytes(bytes).expect("valid");
878 assert_eq!(og, rnd);
879
880 mz_ore::assert_err!(PackedNumeric::from_bytes(&[0, 0, 0, 0]));
882 }
883
884 #[mz_ore::test]
885 #[cfg_attr(miri, ignore)] fn proptest_packed_numeric_roundtrip() {
887 fn test(og: Numeric) {
888 let packed = PackedNumeric::from_value(og);
889 let rnd = packed.into_value();
890
891 if og.is_nan() && rnd.is_nan() {
892 return;
893 }
894 assert_eq!(og, rnd);
895 }
896
897 proptest!(|(num in arb_numeric())| {
898 test(num);
899 });
900 }
901
902 #[mz_ore::test]
906 #[cfg_attr(miri, ignore)] fn packed_numeric_stability() {
908 const RNG_SEED: [u8; 32] = [
911 0xf4, 0x16, 0x16, 0x48, 0xc3, 0xac, 0x77, 0xac, 0x72, 0x20, 0x0b, 0xea, 0x99, 0x67,
912 0x2d, 0x6d, 0xca, 0x9f, 0x76, 0xaf, 0x1b, 0x09, 0x73, 0xa0, 0x59, 0x22, 0x6d, 0xc5,
913 0x46, 0x39, 0x1c, 0x4a,
914 ];
915
916 let rng = proptest::test_runner::TestRng::from_seed(
917 proptest::test_runner::RngAlgorithm::ChaCha,
918 &RNG_SEED,
919 );
920 let config = proptest::test_runner::Config {
922 cases: u32::MAX,
924 rng_algorithm: proptest::test_runner::RngAlgorithm::ChaCha,
925 ..Default::default()
926 };
927 let mut runner = proptest::test_runner::TestRunner::new_with_rng(config, rng);
928
929 let test_cases = 2_000;
930 let strat = arb_numeric();
931
932 let mut all_numerics = Vec::new();
933 for _ in 0..test_cases {
934 let value_tree = strat.new_tree(&mut runner).unwrap();
935 let numeric = value_tree.current();
936 let packed = PackedNumeric::from_value(numeric);
937 let hex_bytes = format!("{:x?}", packed.as_bytes());
938
939 all_numerics.push((numeric, hex_bytes));
940 }
941
942 insta::assert_debug_snapshot!(all_numerics);
943 }
944}