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::arbitrary::Arbitrary;
26#[cfg(any(test, feature = "proptest"))]
27use proptest::strategy::{BoxedStrategy, Strategy};
28use serde::{Deserialize, Serialize};
29
30include!(concat!(env!("OUT_DIR"), "/mz_repr.adt.numeric.rs"));
31
32pub const NUMERIC_DATUM_WIDTH: u8 = 13;
34
35pub const NUMERIC_DATUM_WIDTH_USIZE: usize = cast::u8_to_usize(NUMERIC_DATUM_WIDTH);
37
38pub const NUMERIC_DATUM_MAX_PRECISION: u8 = NUMERIC_DATUM_WIDTH * 3;
40
41pub type Numeric = Decimal<NUMERIC_DATUM_WIDTH_USIZE>;
43
44pub const NUMERIC_AGG_WIDTH: u8 = 27;
46
47pub const NUMERIC_AGG_WIDTH_USIZE: usize = cast::u8_to_usize(NUMERIC_AGG_WIDTH);
49
50pub const NUMERIC_AGG_MAX_PRECISION: u8 = NUMERIC_AGG_WIDTH * 3;
52
53pub type NumericAgg = Decimal<NUMERIC_AGG_WIDTH_USIZE>;
55
56static CX_DATUM: LazyLock<Context<Numeric>> = LazyLock::new(|| {
57 let mut cx = Context::<Numeric>::default();
58 cx.set_max_exponent(isize::from(NUMERIC_DATUM_MAX_PRECISION - 1))
59 .unwrap();
60 cx.set_min_exponent(-isize::from(NUMERIC_DATUM_MAX_PRECISION))
61 .unwrap();
62 cx
63});
64static CX_AGG: LazyLock<Context<NumericAgg>> = LazyLock::new(|| {
65 let mut cx = Context::<NumericAgg>::default();
66 cx.set_max_exponent(isize::from(NUMERIC_AGG_MAX_PRECISION - 1))
67 .unwrap();
68 cx.set_min_exponent(-isize::from(NUMERIC_AGG_MAX_PRECISION))
69 .unwrap();
70 cx
71});
72static U128_SPLITTER_DATUM: LazyLock<Numeric> = LazyLock::new(|| {
73 let mut cx = Numeric::context();
74 cx.parse("340282366920938463463374607431768211456").unwrap()
76});
77static U128_SPLITTER_AGG: LazyLock<NumericAgg> = LazyLock::new(|| {
78 let mut cx = NumericAgg::context();
79 cx.parse("340282366920938463463374607431768211456").unwrap()
81});
82
83pub mod str_serde {
85 use std::str::FromStr;
86
87 use serde::Deserialize;
88
89 use super::Numeric;
90
91 pub fn deserialize<'de, D>(deserializer: D) -> Result<Numeric, D::Error>
93 where
94 D: serde::Deserializer<'de>,
95 {
96 let buf = String::deserialize(deserializer)?;
97 Numeric::from_str(&buf).map_err(serde::de::Error::custom)
98 }
99}
100
101#[derive(
107 Debug,
108 Clone,
109 Copy,
110 Eq,
111 PartialEq,
112 Ord,
113 PartialOrd,
114 Hash,
115 Serialize,
116 Deserialize
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
130#[cfg(any(test, feature = "proptest"))]
131impl Arbitrary for NumericMaxScale {
132 type Parameters = ();
133 type Strategy = BoxedStrategy<NumericMaxScale>;
134
135 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
136 (0..=NUMERIC_DATUM_MAX_PRECISION)
137 .prop_map(NumericMaxScale)
138 .boxed()
139 }
140}
141
142impl TryFrom<i64> for NumericMaxScale {
143 type Error = InvalidNumericMaxScaleError;
144
145 fn try_from(max_scale: i64) -> Result<Self, Self::Error> {
146 match u8::try_from(max_scale) {
147 Ok(max_scale) if max_scale <= NUMERIC_DATUM_MAX_PRECISION => {
148 Ok(NumericMaxScale(max_scale))
149 }
150 _ => Err(InvalidNumericMaxScaleError),
151 }
152 }
153}
154
155impl TryFrom<usize> for NumericMaxScale {
156 type Error = InvalidNumericMaxScaleError;
157
158 fn try_from(max_scale: usize) -> Result<Self, Self::Error> {
159 Self::try_from(i64::try_from(max_scale).map_err(|_| InvalidNumericMaxScaleError)?)
160 }
161}
162
163impl RustType<ProtoNumericMaxScale> for NumericMaxScale {
164 fn into_proto(&self) -> ProtoNumericMaxScale {
165 ProtoNumericMaxScale {
166 value: self.0.into_proto(),
167 }
168 }
169
170 fn from_proto(max_scale: ProtoNumericMaxScale) -> Result<Self, TryFromProtoError> {
173 NumericMaxScale::try_from(i64::from(max_scale.value)).map_err(|e| {
174 TryFromProtoError::InvalidFieldError(format!(
175 "ProtoNumericMaxScale::value {}: {e}",
176 max_scale.value
177 ))
178 })
179 }
180}
181
182impl RustType<ProtoOptionalNumericMaxScale> for Option<NumericMaxScale> {
183 fn into_proto(&self) -> ProtoOptionalNumericMaxScale {
184 ProtoOptionalNumericMaxScale {
185 value: self.into_proto(),
186 }
187 }
188
189 fn from_proto(max_scale: ProtoOptionalNumericMaxScale) -> Result<Self, TryFromProtoError> {
190 max_scale.value.into_rust()
191 }
192}
193
194#[derive(Debug, Clone)]
197pub struct InvalidNumericMaxScaleError;
198
199impl fmt::Display for InvalidNumericMaxScaleError {
200 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
201 write!(
202 f,
203 "scale for type numeric must be between 0 and {}",
204 NUMERIC_DATUM_MAX_PRECISION
205 )
206 }
207}
208
209impl Error for InvalidNumericMaxScaleError {}
210
211pub trait Dec<const N: usize> {
214 const TWOS_COMPLEMENT_BYTE_WIDTH: usize;
217 fn context() -> Context<Decimal<N>>;
219 fn u128_splitter() -> &'static Decimal<N>;
222}
223
224impl Dec<NUMERIC_DATUM_WIDTH_USIZE> for Numeric {
225 const TWOS_COMPLEMENT_BYTE_WIDTH: usize = 17;
226 fn context() -> Context<Numeric> {
227 CX_DATUM.clone()
228 }
229 fn u128_splitter() -> &'static Numeric {
230 &U128_SPLITTER_DATUM
231 }
232}
233
234impl Dec<NUMERIC_AGG_WIDTH_USIZE> for NumericAgg {
235 const TWOS_COMPLEMENT_BYTE_WIDTH: usize = 33;
236 fn context() -> Context<NumericAgg> {
237 CX_AGG.clone()
238 }
239 fn u128_splitter() -> &'static NumericAgg {
240 &U128_SPLITTER_AGG
241 }
242}
243
244pub fn cx_datum() -> Context<Numeric> {
246 CX_DATUM.clone()
247}
248
249pub fn cx_agg() -> Context<NumericAgg> {
251 CX_AGG.clone()
252}
253
254fn twos_complement_be_to_u128(input: &[u8]) -> u128 {
255 assert!(input.len() <= 16);
256 let mut buf = [0; 16];
257 buf[16 - input.len()..16].copy_from_slice(input);
258 u128::from_be_bytes(buf)
259}
260
261fn negate_twos_complement_le<'a, I>(b: I)
265where
266 I: Iterator<Item = &'a mut u8>,
267{
268 let mut seen_first_one = false;
269 for i in b {
270 if seen_first_one {
271 *i = *i ^ 0xFF;
272 } else if *i > 0 {
273 seen_first_one = true;
274 if i == &0x80 {
275 continue;
276 }
277 let tz = i.trailing_zeros();
278 *i = *i ^ (0xFF << tz + 1);
279 }
280 }
281}
282
283pub fn numeric_to_twos_complement_be(
285 mut numeric: Numeric,
286) -> [u8; Numeric::TWOS_COMPLEMENT_BYTE_WIDTH] {
287 let mut buf = [0; Numeric::TWOS_COMPLEMENT_BYTE_WIDTH];
288 if numeric.is_special() {
292 return buf;
293 }
294
295 let mut cx = Numeric::context();
296
297 if numeric.exponent() < 0 {
299 let s = Numeric::from(-numeric.exponent());
300 cx.scaleb(&mut numeric, &s);
301 }
302
303 numeric_to_twos_complement_inner::<Numeric, NUMERIC_DATUM_WIDTH_USIZE>(
304 numeric, &mut cx, &mut buf,
305 );
306 buf
307}
308
309pub fn numeric_to_twos_complement_wide(
318 numeric: Numeric,
319) -> [u8; NumericAgg::TWOS_COMPLEMENT_BYTE_WIDTH] {
320 let mut buf = [0; NumericAgg::TWOS_COMPLEMENT_BYTE_WIDTH];
321 if numeric.is_special() {
325 return buf;
326 }
327 let mut cx = NumericAgg::context();
328 let mut d = cx.to_width(numeric);
329 let mut scaler = NumericAgg::from(NUMERIC_DATUM_MAX_PRECISION);
330 cx.neg(&mut scaler);
331 cx.rescale(&mut d, &scaler);
333 cx.abs(&mut scaler);
336 cx.scaleb(&mut d, &scaler);
337
338 numeric_to_twos_complement_inner::<NumericAgg, NUMERIC_AGG_WIDTH_USIZE>(d, &mut cx, &mut buf);
339 buf
340}
341
342fn numeric_to_twos_complement_inner<D: Dec<N>, const N: usize>(
343 mut d: Decimal<N>,
344 cx: &mut Context<Decimal<N>>,
345 buf: &mut [u8],
346) {
347 let is_neg = if d.is_negative() {
349 cx.neg(&mut d);
350 true
351 } else {
352 false
353 };
354
355 assert!(d.exponent() >= 0);
357
358 let mut buf_cursor = 0;
359 while !d.is_zero() {
360 let mut w = d.clone();
361 cx.rem(&mut w, D::u128_splitter());
363
364 let c = w.coefficient::<u128>().unwrap();
368
369 let e = std::cmp::min(buf_cursor + 16, D::TWOS_COMPLEMENT_BYTE_WIDTH);
372
373 buf[buf_cursor..e].copy_from_slice(&c.to_le_bytes()[0..e - buf_cursor]);
375 buf_cursor += 16;
377
378 cx.div_integer(&mut d, D::u128_splitter());
380 }
381
382 if is_neg {
383 negate_twos_complement_le(buf.iter_mut());
384 }
385
386 buf.reverse();
388}
389
390pub fn twos_complement_be_to_numeric(
391 input: &mut [u8],
392 scale: u8,
393) -> Result<Numeric, anyhow::Error> {
394 let mut cx = cx_datum();
395 if input.len() <= 17 {
396 if let Ok(mut n) =
397 twos_complement_be_to_numeric_inner::<Numeric, NUMERIC_DATUM_WIDTH_USIZE>(input)
398 {
399 n.set_exponent(-i32::from(scale));
400 return Ok(n);
401 }
402 }
403 let mut n = twos_complement_be_to_numeric_inner::<NumericAgg, NUMERIC_AGG_WIDTH_USIZE>(input)?;
406 n.set_exponent(-i32::from(scale));
408 let d = cx.to_width(n);
409 if cx.status().inexact() {
410 bail!("Value exceeds maximum numeric value")
411 }
412 Ok(d)
413}
414
415pub fn twos_complement_be_to_numeric_inner<D: Dec<N>, const N: usize>(
418 input: &mut [u8],
419) -> Result<Decimal<N>, anyhow::Error> {
420 if input.is_empty() {
421 bail!("cannot parse a numeric value from an empty byte string");
427 }
428 let is_neg = if (input[0] & 0x80) != 0 {
429 negate_twos_complement_le(input.iter_mut().rev());
432 true
433 } else {
434 false
435 };
436
437 let head = input.len() % 16;
438 let i = twos_complement_be_to_u128(&input[0..head]);
439 let mut cx = D::context();
440 let mut d = cx.from_u128(i);
441
442 for c in input[head..].chunks(16) {
443 assert_eq!(c.len(), 16);
444 cx.mul(&mut d, D::u128_splitter());
446 let i = twos_complement_be_to_u128(c);
447 let i = cx.from_u128(i);
448 cx.add(&mut d, &i);
449 }
450
451 if cx.status().inexact() {
452 bail!("Value exceeds maximum numeric value")
453 } else if cx.status().any() {
454 bail!("unexpected status {:?}", cx.status());
455 }
456 if is_neg {
457 cx.neg(&mut d);
458 }
459 Ok(d)
460}
461
462#[mz_ore::test]
463#[cfg_attr(miri, ignore)] fn test_twos_complement_roundtrip() {
465 fn inner(s: &str) {
466 let mut cx = cx_datum();
467 let d = cx.parse(s).unwrap();
468 let scale = std::cmp::min(d.exponent(), 0).abs();
469 let mut b = numeric_to_twos_complement_be(d.clone());
470 let x = twos_complement_be_to_numeric(&mut b, u8::try_from(scale).unwrap()).unwrap();
471 assert_eq!(d, x);
472 }
473 inner("0");
474 inner("0.000000000000000000000000000000000012345");
475 inner("0.123456789012345678901234567890123456789");
476 inner("1.00000000000000000000000000000000000000");
477 inner("1");
478 inner("2");
479 inner("170141183460469231731687303715884105727");
480 inner("170141183460469231731687303715884105728");
481 inner("12345678901234567890.1234567890123456789");
482 inner("999999999999999999999999999999999999999");
483 inner("7e35");
484 inner("7e-35");
485 inner("-0.000000000000000000000000000000000012345");
486 inner("-0.12345678901234567890123456789012345678");
487 inner("-1.00000000000000000000000000000000000000");
488 inner("-1");
489 inner("-2");
490 inner("-170141183460469231731687303715884105727");
491 inner("-170141183460469231731687303715884105728");
492 inner("-12345678901234567890.1234567890123456789");
493 inner("-999999999999999999999999999999999999999");
494 inner("-7.2e35");
495 inner("-7.2e-35");
496}
497
498#[mz_ore::test]
499#[cfg_attr(miri, ignore)] fn test_twos_complement_empty_is_error() {
501 for scale in [0u8, 1, 38] {
506 assert!(twos_complement_be_to_numeric(&mut [], scale).is_err());
507 }
508 assert!(
509 twos_complement_be_to_numeric_inner::<Numeric, NUMERIC_DATUM_WIDTH_USIZE>(&mut []).is_err()
510 );
511}
512
513#[mz_ore::test]
514#[cfg_attr(miri, ignore)] fn test_twos_comp_numeric_primitive() {
516 fn inner_inner<P>(i: P, i_be_bytes: &mut [u8])
517 where
518 P: Into<Numeric> + TryFrom<Numeric> + Eq + PartialEq + std::fmt::Debug + Copy,
519 {
520 let mut e = [0; Numeric::TWOS_COMPLEMENT_BYTE_WIDTH];
521 e[Numeric::TWOS_COMPLEMENT_BYTE_WIDTH - i_be_bytes.len()..].copy_from_slice(i_be_bytes);
522 let mut w = [0; NumericAgg::TWOS_COMPLEMENT_BYTE_WIDTH];
523 w[NumericAgg::TWOS_COMPLEMENT_BYTE_WIDTH - i_be_bytes.len()..].copy_from_slice(i_be_bytes);
524
525 let d: Numeric = i.into();
526
527 if d.is_negative() {
529 for i in e[..Numeric::TWOS_COMPLEMENT_BYTE_WIDTH - i_be_bytes.len()].iter_mut() {
530 *i = 0xFF;
531 }
532 for i in w[..NumericAgg::TWOS_COMPLEMENT_BYTE_WIDTH - i_be_bytes.len()].iter_mut() {
533 *i = 0xFF;
534 }
535 }
536
537 let d_be_bytes = numeric_to_twos_complement_be(d);
540 assert_eq!(
541 e, d_be_bytes,
542 "expected repr of {:?}, got {:?}",
543 e, d_be_bytes
544 );
545
546 let e_numeric = twos_complement_be_to_numeric(&mut e, 0).unwrap();
548 let e_p: P = e_numeric
549 .try_into()
550 .unwrap_or_else(|_e| panic!("try_into failed"));
551 assert_eq!(i, e_p, "expected val of {:?}, got {:?}", i, e_p);
552
553 let w_numeric = twos_complement_be_to_numeric(&mut w, 0).unwrap();
555 let w_p: P = w_numeric
556 .try_into()
557 .unwrap_or_else(|_e| panic!("try_into failed"));
558 assert_eq!(i, w_p, "expected val of {:?}, got {:?}", i, e_p);
559
560 let p_numeric = twos_complement_be_to_numeric(i_be_bytes, 0).unwrap();
562 let p_p: P = p_numeric
563 .try_into()
564 .unwrap_or_else(|_e| panic!("try_into failed"));
565 assert_eq!(i, p_p, "expected val of {:?}, got {:?}", i, p_p);
566 }
567
568 fn inner_i32(i: i32) {
569 inner_inner(i, &mut i.to_be_bytes());
570 }
571
572 fn inner_i64(i: i64) {
573 inner_inner(i, &mut i.to_be_bytes());
574 }
575
576 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
580 struct FromableI128 {
581 i: i128,
582 }
583 impl From<i128> for FromableI128 {
584 fn from(i: i128) -> FromableI128 {
585 FromableI128 { i }
586 }
587 }
588 impl From<FromableI128> for Numeric {
589 fn from(n: FromableI128) -> Numeric {
590 Numeric::try_from(n.i).unwrap()
591 }
592 }
593 impl TryFrom<Numeric> for FromableI128 {
594 type Error = ();
595 fn try_from(n: Numeric) -> Result<FromableI128, Self::Error> {
596 match i128::try_from(n) {
597 Ok(i) => Ok(FromableI128 { i }),
598 Err(_) => Err(()),
599 }
600 }
601 }
602
603 fn inner_i128(i: i128) {
604 inner_inner(FromableI128::from(i), &mut i.to_be_bytes());
605 }
606
607 inner_i32(0);
608 inner_i32(1);
609 inner_i32(2);
610 inner_i32(-1);
611 inner_i32(-2);
612 inner_i32(i32::MAX);
613 inner_i32(i32::MIN);
614 inner_i32(i32::MAX / 7 + 7);
615 inner_i32(i32::MIN / 7 + 7);
616 inner_i64(0);
617 inner_i64(1);
618 inner_i64(2);
619 inner_i64(-1);
620 inner_i64(-2);
621 inner_i64(i64::MAX);
622 inner_i64(i64::MIN);
623 inner_i64(i64::MAX / 7 + 7);
624 inner_i64(i64::MIN / 7 + 7);
625 inner_i128(0);
626 inner_i128(1);
627 inner_i128(2);
628 inner_i128(-1);
629 inner_i128(-2);
630 inner_i128(i128::from(i64::MAX));
631 inner_i128(i128::from(i64::MIN));
632 inner_i128(i128::MAX);
633 inner_i128(i128::MIN);
634 inner_i128(i128::MAX / 7 + 7);
635 inner_i128(i128::MIN / 7 + 7);
636}
637
638#[mz_ore::test]
639#[cfg_attr(miri, ignore)] fn test_twos_complement_to_numeric_fail() {
641 fn inner(b: &mut [u8]) {
642 let r = twos_complement_be_to_numeric(b, 0);
643 mz_ore::assert_err!(r);
644 }
645 let mut e = [0xFF; Numeric::TWOS_COMPLEMENT_BYTE_WIDTH];
647 e[0] -= 0x80;
648 inner(&mut e);
649
650 let mut e = [0; Numeric::TWOS_COMPLEMENT_BYTE_WIDTH + 1];
652 e[0] = 1;
653 inner(&mut e);
654}
655
656#[mz_ore::test]
657#[cfg_attr(miri, ignore)] fn test_wide_twos_complement_roundtrip() {
659 fn inner(s: &str) {
660 let mut cx = cx_datum();
661 let d = cx.parse(s).unwrap();
662 let mut b = numeric_to_twos_complement_wide(d.clone());
663 let x = twos_complement_be_to_numeric(&mut b, NUMERIC_DATUM_MAX_PRECISION).unwrap();
664 assert_eq!(d, x);
665 }
666 inner("0");
667 inner("0.000000000000000000000000000000000012345");
668 inner("0.123456789012345678901234567890123456789");
669 inner("1.00000000000000000000000000000000000000");
670 inner("1");
671 inner("2");
672 inner("170141183460469231731687303715884105727");
673 inner("170141183460469231731687303715884105728");
674 inner("12345678901234567890.1234567890123456789");
675 inner("999999999999999999999999999999999999999");
676 inner("-0.000000000000000000000000000000000012345");
677 inner("-0.123456789012345678901234567890123456789");
678 inner("-1.00000000000000000000000000000000000000");
679 inner("-1");
680 inner("-2");
681 inner("-170141183460469231731687303715884105727");
682 inner("-170141183460469231731687303715884105728");
683 inner("-12345678901234567890.1234567890123456789");
684 inner("-999999999999999999999999999999999999999");
685}
686
687pub fn get_precision<const N: usize>(n: &Decimal<N>) -> u32 {
690 let e = n.exponent();
691 if e >= 0 {
692 n.digits() + u32::try_from(e).unwrap()
694 } else {
695 let d = n.digits();
697 let e = u32::try_from(e.abs()).unwrap();
698 std::cmp::max(d, e)
702 }
703}
704
705pub fn get_scale(n: &Numeric) -> u32 {
707 let exp = n.exponent();
708 if exp >= 0 { 0 } else { exp.unsigned_abs() }
709}
710
711pub fn munge_numeric(n: &mut Numeric) -> Result<(), anyhow::Error> {
718 rescale_within_max_precision(n)?;
719 if (n.is_zero() || n.is_nan()) && n.is_negative() {
720 cx_datum().neg(n);
721 }
722 Ok(())
723}
724
725fn rescale_within_max_precision(n: &mut Numeric) -> Result<(), anyhow::Error> {
728 let current_precision = get_precision(n);
729 if current_precision > u32::from(NUMERIC_DATUM_MAX_PRECISION) {
730 if n.exponent() < 0 {
731 let precision_diff = current_precision - u32::from(NUMERIC_DATUM_MAX_PRECISION);
732 let current_scale = u8::try_from(get_scale(n))?;
733 let scale_diff = current_scale - u8::try_from(precision_diff).unwrap();
734 rescale(n, scale_diff)?;
735 } else {
736 bail!(
737 "numeric value {} exceed maximum precision {}",
738 n,
739 NUMERIC_DATUM_MAX_PRECISION
740 )
741 }
742 }
743 Ok(())
744}
745
746pub fn rescale(n: &mut Numeric, scale: u8) -> Result<(), anyhow::Error> {
751 let mut cx = cx_datum();
752 cx.rescale(n, &Numeric::from(-i32::from(scale)));
753 if cx.status().invalid_operation() || get_precision(n) > u32::from(NUMERIC_DATUM_MAX_PRECISION)
754 {
755 bail!(
756 "numeric value {} exceed maximum precision {}",
757 n,
758 NUMERIC_DATUM_MAX_PRECISION
759 )
760 }
761 munge_numeric(n)?;
762
763 Ok(())
764}
765
766pub trait DecimalLike:
769 From<u8>
770 + From<u16>
771 + From<u32>
772 + From<i8>
773 + From<i16>
774 + From<i32>
775 + From<f32>
776 + From<f64>
777 + std::ops::Add<Output = Self>
778 + std::ops::Sub<Output = Self>
779 + std::ops::Mul<Output = Self>
780 + std::ops::Div<Output = Self>
781{
782 fn lossy_from(i: i64) -> Self;
785}
786
787impl DecimalLike for f64 {
788 #[allow(clippy::as_conversions)]
790 fn lossy_from(i: i64) -> Self {
791 i as f64
792 }
793}
794
795impl DecimalLike for Numeric {
796 fn lossy_from(i: i64) -> Self {
797 Numeric::from(i)
798 }
799}
800
801#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
806pub struct PackedNumeric(pub [u8; 40]);
807
808impl FixedSizeCodec<Numeric> for PackedNumeric {
809 const SIZE: usize = 40;
810
811 fn as_bytes(&self) -> &[u8] {
812 &self.0
813 }
814
815 fn from_bytes(slice: &[u8]) -> Result<Self, String> {
816 let buf: [u8; Self::SIZE] = slice.try_into().map_err(|_| {
817 format!(
818 "size for PackedNumeric is {} bytes, got {}",
819 Self::SIZE,
820 slice.len()
821 )
822 })?;
823 Ok(PackedNumeric(buf))
824 }
825
826 fn from_value(val: Numeric) -> PackedNumeric {
827 let (digits, exponent, bits, lsu) = val.to_raw_parts();
828
829 let mut buf = [0u8; 40];
830
831 buf[0..4].copy_from_slice(&digits.to_le_bytes());
832 buf[4..8].copy_from_slice(&exponent.to_le_bytes());
833
834 for i in 0..13 {
835 buf[(i * 2) + 8..(i * 2) + 10].copy_from_slice(&lsu[i].to_le_bytes());
836 }
837
838 buf[34..35].copy_from_slice(&bits.to_le_bytes());
839
840 PackedNumeric(buf)
841 }
842
843 fn into_value(self) -> Numeric {
844 let digits: [u8; 4] = self.0[0..4].try_into().unwrap();
845 let digits = u32::from_le_bytes(digits);
846
847 let exponent: [u8; 4] = self.0[4..8].try_into().unwrap();
848 let exponent = i32::from_le_bytes(exponent);
849
850 let mut lsu = [0u16; 13];
851 for i in 0..13 {
852 let x: [u8; 2] = self.0[(i * 2) + 8..(i * 2) + 10].try_into().unwrap();
853 let x = u16::from_le_bytes(x);
854 lsu[i] = x;
855 }
856
857 let bits: [u8; 1] = self.0[34..35].try_into().unwrap();
858 let bits = u8::from_le_bytes(bits);
859
860 Numeric::from_raw_parts(digits, exponent, bits, lsu)
861 }
862}
863
864#[cfg(test)]
865mod tests {
866 use mz_ore::assert_ok;
867 use mz_proto::protobuf_roundtrip;
868 use proptest::prelude::*;
869
870 use crate::scalar::arb_numeric;
871
872 use super::*;
873
874 proptest! {
875 #[mz_ore::test]
876 fn numeric_max_scale_protobuf_roundtrip(expect in any::<NumericMaxScale>()) {
877 let actual = protobuf_roundtrip::<_, ProtoNumericMaxScale>(&expect);
878 assert_ok!(actual);
879 assert_eq!(actual.unwrap(), expect);
880 }
881
882 #[mz_ore::test]
883 fn optional_numeric_max_scale_protobuf_roundtrip(
884 expect in any::<Option<NumericMaxScale>>(),
885 ) {
886 let actual = protobuf_roundtrip::<_, ProtoOptionalNumericMaxScale>(&expect);
887 assert_ok!(actual);
888 assert_eq!(actual.unwrap(), expect);
889 }
890 }
891
892 #[mz_ore::test]
893 #[cfg_attr(miri, ignore)] fn smoketest_packed_numeric_roundtrips() {
895 let og = PackedNumeric::from_value(Numeric::from(-42));
896 let bytes = og.as_bytes();
897 let rnd = PackedNumeric::from_bytes(bytes).expect("valid");
898 assert_eq!(og, rnd);
899
900 mz_ore::assert_err!(PackedNumeric::from_bytes(&[0, 0, 0, 0]));
902 }
903
904 #[mz_ore::test]
905 #[cfg_attr(miri, ignore)] fn proptest_packed_numeric_roundtrip() {
907 fn test(og: Numeric) {
908 let packed = PackedNumeric::from_value(og);
909 let rnd = packed.into_value();
910
911 if og.is_nan() && rnd.is_nan() {
912 return;
913 }
914 assert_eq!(og, rnd);
915 }
916
917 proptest!(|(num in arb_numeric())| {
918 test(num);
919 });
920 }
921
922 #[mz_ore::test]
926 #[cfg_attr(miri, ignore)] fn packed_numeric_stability() {
928 const RNG_SEED: [u8; 32] = [
931 0xf4, 0x16, 0x16, 0x48, 0xc3, 0xac, 0x77, 0xac, 0x72, 0x20, 0x0b, 0xea, 0x99, 0x67,
932 0x2d, 0x6d, 0xca, 0x9f, 0x76, 0xaf, 0x1b, 0x09, 0x73, 0xa0, 0x59, 0x22, 0x6d, 0xc5,
933 0x46, 0x39, 0x1c, 0x4a,
934 ];
935
936 let rng = proptest::test_runner::TestRng::from_seed(
937 proptest::test_runner::RngAlgorithm::ChaCha,
938 &RNG_SEED,
939 );
940 let config = proptest::test_runner::Config {
942 cases: u32::MAX,
944 rng_algorithm: proptest::test_runner::RngAlgorithm::ChaCha,
945 ..Default::default()
946 };
947 let mut runner = proptest::test_runner::TestRunner::new_with_rng(config, rng);
948
949 let test_cases = 2_000;
950 let strat = arb_numeric();
951
952 let mut all_numerics = Vec::new();
953 for _ in 0..test_cases {
954 let value_tree = strat.new_tree(&mut runner).unwrap();
955 let numeric = value_tree.current();
956 let packed = PackedNumeric::from_value(numeric);
957 let hex_bytes = format!("{:x?}", packed.as_bytes());
958
959 all_numerics.push((numeric, hex_bytes));
960 }
961
962 insta::assert_debug_snapshot!(all_numerics);
963 }
964}