1use std::cmp::Ordering;
16use std::error::Error;
17use std::fmt;
18use std::sync::LazyLock;
19
20use anyhow::bail;
21use dec::{Context, Decimal, OrderedDecimal};
22use mz_ore::cast;
23use mz_persist_types::columnar::FixedSizeCodec;
24use mz_proto::{ProtoType, RustType, TryFromProtoError};
25#[cfg(any(test, feature = "proptest"))]
26use proptest::arbitrary::Arbitrary;
27#[cfg(any(test, feature = "proptest"))]
28use proptest::strategy::{BoxedStrategy, Strategy};
29use serde::{Deserialize, Serialize};
30
31include!(concat!(env!("OUT_DIR"), "/mz_repr.adt.numeric.rs"));
32
33pub const NUMERIC_DATUM_WIDTH: u8 = 13;
35
36pub const NUMERIC_DATUM_WIDTH_USIZE: usize = cast::u8_to_usize(NUMERIC_DATUM_WIDTH);
38
39pub const NUMERIC_DATUM_MAX_PRECISION: u8 = NUMERIC_DATUM_WIDTH * 3;
41
42pub type Numeric = Decimal<NUMERIC_DATUM_WIDTH_USIZE>;
44
45pub const NUMERIC_AGG_WIDTH: u8 = 27;
47
48pub const NUMERIC_AGG_WIDTH_USIZE: usize = cast::u8_to_usize(NUMERIC_AGG_WIDTH);
50
51pub const NUMERIC_AGG_MAX_PRECISION: u8 = NUMERIC_AGG_WIDTH * 3;
53
54pub type NumericAgg = Decimal<NUMERIC_AGG_WIDTH_USIZE>;
56
57#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
64pub struct OrderedNumericAgg(pub NumericAgg);
65
66impl PartialEq for OrderedNumericAgg {
67 fn eq(&self, other: &Self) -> bool {
68 OrderedDecimal(self.0) == OrderedDecimal(other.0)
69 }
70}
71
72impl Eq for OrderedNumericAgg {}
73
74impl PartialOrd for OrderedNumericAgg {
75 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
76 Some(self.cmp(other))
77 }
78}
79
80impl Ord for OrderedNumericAgg {
81 fn cmp(&self, other: &Self) -> Ordering {
82 OrderedDecimal(self.0).cmp(&OrderedDecimal(other.0))
83 }
84}
85
86static CX_DATUM: LazyLock<Context<Numeric>> = LazyLock::new(|| {
87 let mut cx = Context::<Numeric>::default();
88 cx.set_max_exponent(isize::from(NUMERIC_DATUM_MAX_PRECISION - 1))
89 .unwrap();
90 cx.set_min_exponent(-isize::from(NUMERIC_DATUM_MAX_PRECISION))
91 .unwrap();
92 cx
93});
94static CX_AGG: LazyLock<Context<NumericAgg>> = LazyLock::new(|| {
95 let mut cx = Context::<NumericAgg>::default();
96 cx.set_max_exponent(isize::from(NUMERIC_AGG_MAX_PRECISION - 1))
97 .unwrap();
98 cx.set_min_exponent(-isize::from(NUMERIC_AGG_MAX_PRECISION))
99 .unwrap();
100 cx
101});
102static U128_SPLITTER_DATUM: LazyLock<Numeric> = LazyLock::new(|| {
103 let mut cx = Numeric::context();
104 cx.parse("340282366920938463463374607431768211456").unwrap()
106});
107static U128_SPLITTER_AGG: LazyLock<NumericAgg> = LazyLock::new(|| {
108 let mut cx = NumericAgg::context();
109 cx.parse("340282366920938463463374607431768211456").unwrap()
111});
112
113pub mod str_serde {
115 use std::str::FromStr;
116
117 use serde::Deserialize;
118
119 use super::Numeric;
120
121 pub fn deserialize<'de, D>(deserializer: D) -> Result<Numeric, D::Error>
123 where
124 D: serde::Deserializer<'de>,
125 {
126 let buf = String::deserialize(deserializer)?;
127 Numeric::from_str(&buf).map_err(serde::de::Error::custom)
128 }
129}
130
131#[derive(
137 Debug,
138 Clone,
139 Copy,
140 Eq,
141 PartialEq,
142 Ord,
143 PartialOrd,
144 Hash,
145 Serialize,
146 Deserialize
147)]
148pub struct NumericMaxScale(pub(crate) u8);
149
150impl NumericMaxScale {
151 pub const ZERO: NumericMaxScale = NumericMaxScale(0);
153
154 pub fn into_u8(self) -> u8 {
156 self.0
157 }
158}
159
160#[cfg(any(test, feature = "proptest"))]
161impl Arbitrary for NumericMaxScale {
162 type Parameters = ();
163 type Strategy = BoxedStrategy<NumericMaxScale>;
164
165 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
166 (0..=NUMERIC_DATUM_MAX_PRECISION)
167 .prop_map(NumericMaxScale)
168 .boxed()
169 }
170}
171
172impl TryFrom<i64> for NumericMaxScale {
173 type Error = InvalidNumericMaxScaleError;
174
175 fn try_from(max_scale: i64) -> Result<Self, Self::Error> {
176 match u8::try_from(max_scale) {
177 Ok(max_scale) if max_scale <= NUMERIC_DATUM_MAX_PRECISION => {
178 Ok(NumericMaxScale(max_scale))
179 }
180 _ => Err(InvalidNumericMaxScaleError),
181 }
182 }
183}
184
185impl TryFrom<usize> for NumericMaxScale {
186 type Error = InvalidNumericMaxScaleError;
187
188 fn try_from(max_scale: usize) -> Result<Self, Self::Error> {
189 Self::try_from(i64::try_from(max_scale).map_err(|_| InvalidNumericMaxScaleError)?)
190 }
191}
192
193impl RustType<ProtoNumericMaxScale> for NumericMaxScale {
194 fn into_proto(&self) -> ProtoNumericMaxScale {
195 ProtoNumericMaxScale {
196 value: self.0.into_proto(),
197 }
198 }
199
200 fn from_proto(max_scale: ProtoNumericMaxScale) -> Result<Self, TryFromProtoError> {
203 NumericMaxScale::try_from(i64::from(max_scale.value)).map_err(|e| {
204 TryFromProtoError::InvalidFieldError(format!(
205 "ProtoNumericMaxScale::value {}: {e}",
206 max_scale.value
207 ))
208 })
209 }
210}
211
212impl RustType<ProtoOptionalNumericMaxScale> for Option<NumericMaxScale> {
213 fn into_proto(&self) -> ProtoOptionalNumericMaxScale {
214 ProtoOptionalNumericMaxScale {
215 value: self.into_proto(),
216 }
217 }
218
219 fn from_proto(max_scale: ProtoOptionalNumericMaxScale) -> Result<Self, TryFromProtoError> {
220 max_scale.value.into_rust()
221 }
222}
223
224#[derive(Debug, Clone)]
227pub struct InvalidNumericMaxScaleError;
228
229impl fmt::Display for InvalidNumericMaxScaleError {
230 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
231 write!(
232 f,
233 "scale for type numeric must be between 0 and {}",
234 NUMERIC_DATUM_MAX_PRECISION
235 )
236 }
237}
238
239impl Error for InvalidNumericMaxScaleError {}
240
241pub trait Dec<const N: usize> {
244 const TWOS_COMPLEMENT_BYTE_WIDTH: usize;
247 fn context() -> Context<Decimal<N>>;
249 fn u128_splitter() -> &'static Decimal<N>;
252}
253
254impl Dec<NUMERIC_DATUM_WIDTH_USIZE> for Numeric {
255 const TWOS_COMPLEMENT_BYTE_WIDTH: usize = 17;
256 fn context() -> Context<Numeric> {
257 CX_DATUM.clone()
258 }
259 fn u128_splitter() -> &'static Numeric {
260 &U128_SPLITTER_DATUM
261 }
262}
263
264impl Dec<NUMERIC_AGG_WIDTH_USIZE> for NumericAgg {
265 const TWOS_COMPLEMENT_BYTE_WIDTH: usize = 33;
266 fn context() -> Context<NumericAgg> {
267 CX_AGG.clone()
268 }
269 fn u128_splitter() -> &'static NumericAgg {
270 &U128_SPLITTER_AGG
271 }
272}
273
274pub fn cx_datum() -> Context<Numeric> {
276 CX_DATUM.clone()
277}
278
279pub fn cx_agg() -> Context<NumericAgg> {
281 CX_AGG.clone()
282}
283
284fn twos_complement_be_to_u128(input: &[u8]) -> u128 {
285 assert!(input.len() <= 16);
286 let mut buf = [0; 16];
287 buf[16 - input.len()..16].copy_from_slice(input);
288 u128::from_be_bytes(buf)
289}
290
291fn negate_twos_complement_le<'a, I>(b: I)
295where
296 I: Iterator<Item = &'a mut u8>,
297{
298 let mut seen_first_one = false;
299 for i in b {
300 if seen_first_one {
301 *i = *i ^ 0xFF;
302 } else if *i > 0 {
303 seen_first_one = true;
304 if i == &0x80 {
305 continue;
306 }
307 let tz = i.trailing_zeros();
308 *i = *i ^ (0xFF << tz + 1);
309 }
310 }
311}
312
313pub fn numeric_to_twos_complement_be(
315 mut numeric: Numeric,
316) -> [u8; Numeric::TWOS_COMPLEMENT_BYTE_WIDTH] {
317 let mut buf = [0; Numeric::TWOS_COMPLEMENT_BYTE_WIDTH];
318 if numeric.is_special() {
322 return buf;
323 }
324
325 let mut cx = Numeric::context();
326
327 if numeric.exponent() < 0 {
329 let s = Numeric::from(-numeric.exponent());
330 cx.scaleb(&mut numeric, &s);
331 }
332
333 numeric_to_twos_complement_inner::<Numeric, NUMERIC_DATUM_WIDTH_USIZE>(
334 numeric, &mut cx, &mut buf,
335 );
336 buf
337}
338
339pub fn numeric_to_twos_complement_wide(
348 numeric: Numeric,
349) -> [u8; NumericAgg::TWOS_COMPLEMENT_BYTE_WIDTH] {
350 let mut buf = [0; NumericAgg::TWOS_COMPLEMENT_BYTE_WIDTH];
351 if numeric.is_special() {
355 return buf;
356 }
357 let mut cx = NumericAgg::context();
358 let mut d = cx.to_width(numeric);
359 let mut scaler = NumericAgg::from(NUMERIC_DATUM_MAX_PRECISION);
360 cx.neg(&mut scaler);
361 cx.rescale(&mut d, &scaler);
363 cx.abs(&mut scaler);
366 cx.scaleb(&mut d, &scaler);
367
368 numeric_to_twos_complement_inner::<NumericAgg, NUMERIC_AGG_WIDTH_USIZE>(d, &mut cx, &mut buf);
369 buf
370}
371
372fn numeric_to_twos_complement_inner<D: Dec<N>, const N: usize>(
373 mut d: Decimal<N>,
374 cx: &mut Context<Decimal<N>>,
375 buf: &mut [u8],
376) {
377 let is_neg = if d.is_negative() {
379 cx.neg(&mut d);
380 true
381 } else {
382 false
383 };
384
385 assert!(d.exponent() >= 0);
387
388 let mut buf_cursor = 0;
389 while !d.is_zero() {
390 let mut w = d.clone();
391 cx.rem(&mut w, D::u128_splitter());
393
394 let c = w.coefficient::<u128>().unwrap();
398
399 let e = std::cmp::min(buf_cursor + 16, D::TWOS_COMPLEMENT_BYTE_WIDTH);
402
403 buf[buf_cursor..e].copy_from_slice(&c.to_le_bytes()[0..e - buf_cursor]);
405 buf_cursor += 16;
407
408 cx.div_integer(&mut d, D::u128_splitter());
410 }
411
412 if is_neg {
413 negate_twos_complement_le(buf.iter_mut());
414 }
415
416 buf.reverse();
418}
419
420pub fn twos_complement_be_to_numeric(
421 input: &mut [u8],
422 scale: u8,
423) -> Result<Numeric, anyhow::Error> {
424 let mut cx = cx_datum();
425 if input.len() <= 17 {
426 if let Ok(mut n) =
427 twos_complement_be_to_numeric_inner::<Numeric, NUMERIC_DATUM_WIDTH_USIZE>(input)
428 {
429 n.set_exponent(-i32::from(scale));
430 return Ok(n);
431 }
432 }
433 let mut n = twos_complement_be_to_numeric_inner::<NumericAgg, NUMERIC_AGG_WIDTH_USIZE>(input)?;
436 n.set_exponent(-i32::from(scale));
438 let d = cx.to_width(n);
439 if cx.status().inexact() {
440 bail!("Value exceeds maximum numeric value")
441 }
442 Ok(d)
443}
444
445pub fn twos_complement_be_to_numeric_inner<D: Dec<N>, const N: usize>(
448 input: &mut [u8],
449) -> Result<Decimal<N>, anyhow::Error> {
450 if input.is_empty() {
451 bail!("cannot parse a numeric value from an empty byte string");
457 }
458 let is_neg = if (input[0] & 0x80) != 0 {
459 negate_twos_complement_le(input.iter_mut().rev());
462 true
463 } else {
464 false
465 };
466
467 let head = input.len() % 16;
468 let i = twos_complement_be_to_u128(&input[0..head]);
469 let mut cx = D::context();
470 let mut d = cx.from_u128(i);
471
472 for c in input[head..].chunks(16) {
473 assert_eq!(c.len(), 16);
474 cx.mul(&mut d, D::u128_splitter());
476 let i = twos_complement_be_to_u128(c);
477 let i = cx.from_u128(i);
478 cx.add(&mut d, &i);
479 }
480
481 if cx.status().inexact() {
482 bail!("Value exceeds maximum numeric value")
483 } else if cx.status().any() {
484 bail!("unexpected status {:?}", cx.status());
485 }
486 if is_neg {
487 cx.neg(&mut d);
488 }
489 Ok(d)
490}
491
492#[mz_ore::test]
493#[cfg_attr(miri, ignore)] fn test_twos_complement_roundtrip() {
495 fn inner(s: &str) {
496 let mut cx = cx_datum();
497 let d = cx.parse(s).unwrap();
498 let scale = std::cmp::min(d.exponent(), 0).abs();
499 let mut b = numeric_to_twos_complement_be(d.clone());
500 let x = twos_complement_be_to_numeric(&mut b, u8::try_from(scale).unwrap()).unwrap();
501 assert_eq!(d, x);
502 }
503 inner("0");
504 inner("0.000000000000000000000000000000000012345");
505 inner("0.123456789012345678901234567890123456789");
506 inner("1.00000000000000000000000000000000000000");
507 inner("1");
508 inner("2");
509 inner("170141183460469231731687303715884105727");
510 inner("170141183460469231731687303715884105728");
511 inner("12345678901234567890.1234567890123456789");
512 inner("999999999999999999999999999999999999999");
513 inner("7e35");
514 inner("7e-35");
515 inner("-0.000000000000000000000000000000000012345");
516 inner("-0.12345678901234567890123456789012345678");
517 inner("-1.00000000000000000000000000000000000000");
518 inner("-1");
519 inner("-2");
520 inner("-170141183460469231731687303715884105727");
521 inner("-170141183460469231731687303715884105728");
522 inner("-12345678901234567890.1234567890123456789");
523 inner("-999999999999999999999999999999999999999");
524 inner("-7.2e35");
525 inner("-7.2e-35");
526}
527
528#[mz_ore::test]
529#[cfg_attr(miri, ignore)] fn test_twos_complement_empty_is_error() {
531 for scale in [0u8, 1, 38] {
536 assert!(twos_complement_be_to_numeric(&mut [], scale).is_err());
537 }
538 assert!(
539 twos_complement_be_to_numeric_inner::<Numeric, NUMERIC_DATUM_WIDTH_USIZE>(&mut []).is_err()
540 );
541}
542
543#[mz_ore::test]
544#[cfg_attr(miri, ignore)] fn test_twos_comp_numeric_primitive() {
546 fn inner_inner<P>(i: P, i_be_bytes: &mut [u8])
547 where
548 P: Into<Numeric> + TryFrom<Numeric> + Eq + PartialEq + std::fmt::Debug + Copy,
549 {
550 let mut e = [0; Numeric::TWOS_COMPLEMENT_BYTE_WIDTH];
551 e[Numeric::TWOS_COMPLEMENT_BYTE_WIDTH - i_be_bytes.len()..].copy_from_slice(i_be_bytes);
552 let mut w = [0; NumericAgg::TWOS_COMPLEMENT_BYTE_WIDTH];
553 w[NumericAgg::TWOS_COMPLEMENT_BYTE_WIDTH - i_be_bytes.len()..].copy_from_slice(i_be_bytes);
554
555 let d: Numeric = i.into();
556
557 if d.is_negative() {
559 for i in e[..Numeric::TWOS_COMPLEMENT_BYTE_WIDTH - i_be_bytes.len()].iter_mut() {
560 *i = 0xFF;
561 }
562 for i in w[..NumericAgg::TWOS_COMPLEMENT_BYTE_WIDTH - i_be_bytes.len()].iter_mut() {
563 *i = 0xFF;
564 }
565 }
566
567 let d_be_bytes = numeric_to_twos_complement_be(d);
570 assert_eq!(
571 e, d_be_bytes,
572 "expected repr of {:?}, got {:?}",
573 e, d_be_bytes
574 );
575
576 let e_numeric = twos_complement_be_to_numeric(&mut e, 0).unwrap();
578 let e_p: P = e_numeric
579 .try_into()
580 .unwrap_or_else(|_e| panic!("try_into failed"));
581 assert_eq!(i, e_p, "expected val of {:?}, got {:?}", i, e_p);
582
583 let w_numeric = twos_complement_be_to_numeric(&mut w, 0).unwrap();
585 let w_p: P = w_numeric
586 .try_into()
587 .unwrap_or_else(|_e| panic!("try_into failed"));
588 assert_eq!(i, w_p, "expected val of {:?}, got {:?}", i, e_p);
589
590 let p_numeric = twos_complement_be_to_numeric(i_be_bytes, 0).unwrap();
592 let p_p: P = p_numeric
593 .try_into()
594 .unwrap_or_else(|_e| panic!("try_into failed"));
595 assert_eq!(i, p_p, "expected val of {:?}, got {:?}", i, p_p);
596 }
597
598 fn inner_i32(i: i32) {
599 inner_inner(i, &mut i.to_be_bytes());
600 }
601
602 fn inner_i64(i: i64) {
603 inner_inner(i, &mut i.to_be_bytes());
604 }
605
606 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
610 struct FromableI128 {
611 i: i128,
612 }
613 impl From<i128> for FromableI128 {
614 fn from(i: i128) -> FromableI128 {
615 FromableI128 { i }
616 }
617 }
618 impl From<FromableI128> for Numeric {
619 fn from(n: FromableI128) -> Numeric {
620 Numeric::try_from(n.i).unwrap()
621 }
622 }
623 impl TryFrom<Numeric> for FromableI128 {
624 type Error = ();
625 fn try_from(n: Numeric) -> Result<FromableI128, Self::Error> {
626 match i128::try_from(n) {
627 Ok(i) => Ok(FromableI128 { i }),
628 Err(_) => Err(()),
629 }
630 }
631 }
632
633 fn inner_i128(i: i128) {
634 inner_inner(FromableI128::from(i), &mut i.to_be_bytes());
635 }
636
637 inner_i32(0);
638 inner_i32(1);
639 inner_i32(2);
640 inner_i32(-1);
641 inner_i32(-2);
642 inner_i32(i32::MAX);
643 inner_i32(i32::MIN);
644 inner_i32(i32::MAX / 7 + 7);
645 inner_i32(i32::MIN / 7 + 7);
646 inner_i64(0);
647 inner_i64(1);
648 inner_i64(2);
649 inner_i64(-1);
650 inner_i64(-2);
651 inner_i64(i64::MAX);
652 inner_i64(i64::MIN);
653 inner_i64(i64::MAX / 7 + 7);
654 inner_i64(i64::MIN / 7 + 7);
655 inner_i128(0);
656 inner_i128(1);
657 inner_i128(2);
658 inner_i128(-1);
659 inner_i128(-2);
660 inner_i128(i128::from(i64::MAX));
661 inner_i128(i128::from(i64::MIN));
662 inner_i128(i128::MAX);
663 inner_i128(i128::MIN);
664 inner_i128(i128::MAX / 7 + 7);
665 inner_i128(i128::MIN / 7 + 7);
666}
667
668#[mz_ore::test]
669#[cfg_attr(miri, ignore)] fn test_twos_complement_to_numeric_fail() {
671 fn inner(b: &mut [u8]) {
672 let r = twos_complement_be_to_numeric(b, 0);
673 mz_ore::assert_err!(r);
674 }
675 let mut e = [0xFF; Numeric::TWOS_COMPLEMENT_BYTE_WIDTH];
677 e[0] -= 0x80;
678 inner(&mut e);
679
680 let mut e = [0; Numeric::TWOS_COMPLEMENT_BYTE_WIDTH + 1];
682 e[0] = 1;
683 inner(&mut e);
684}
685
686#[mz_ore::test]
687#[cfg_attr(miri, ignore)] fn test_wide_twos_complement_roundtrip() {
689 fn inner(s: &str) {
690 let mut cx = cx_datum();
691 let d = cx.parse(s).unwrap();
692 let mut b = numeric_to_twos_complement_wide(d.clone());
693 let x = twos_complement_be_to_numeric(&mut b, NUMERIC_DATUM_MAX_PRECISION).unwrap();
694 assert_eq!(d, x);
695 }
696 inner("0");
697 inner("0.000000000000000000000000000000000012345");
698 inner("0.123456789012345678901234567890123456789");
699 inner("1.00000000000000000000000000000000000000");
700 inner("1");
701 inner("2");
702 inner("170141183460469231731687303715884105727");
703 inner("170141183460469231731687303715884105728");
704 inner("12345678901234567890.1234567890123456789");
705 inner("999999999999999999999999999999999999999");
706 inner("-0.000000000000000000000000000000000012345");
707 inner("-0.123456789012345678901234567890123456789");
708 inner("-1.00000000000000000000000000000000000000");
709 inner("-1");
710 inner("-2");
711 inner("-170141183460469231731687303715884105727");
712 inner("-170141183460469231731687303715884105728");
713 inner("-12345678901234567890.1234567890123456789");
714 inner("-999999999999999999999999999999999999999");
715}
716
717pub fn get_precision<const N: usize>(n: &Decimal<N>) -> u32 {
720 let e = n.exponent();
721 if e >= 0 {
722 n.digits() + u32::try_from(e).unwrap()
724 } else {
725 let d = n.digits();
727 let e = u32::try_from(e.abs()).unwrap();
728 std::cmp::max(d, e)
732 }
733}
734
735pub fn get_scale(n: &Numeric) -> u32 {
737 let exp = n.exponent();
738 if exp >= 0 { 0 } else { exp.unsigned_abs() }
739}
740
741pub fn munge_numeric(n: &mut Numeric) -> Result<(), anyhow::Error> {
748 rescale_within_max_precision(n)?;
749 if (n.is_zero() || n.is_nan()) && n.is_negative() {
750 cx_datum().neg(n);
751 }
752 Ok(())
753}
754
755fn rescale_within_max_precision(n: &mut Numeric) -> Result<(), anyhow::Error> {
758 let current_precision = get_precision(n);
759 if current_precision > u32::from(NUMERIC_DATUM_MAX_PRECISION) {
760 if n.exponent() < 0 {
761 let precision_diff = current_precision - u32::from(NUMERIC_DATUM_MAX_PRECISION);
762 let current_scale = u8::try_from(get_scale(n))?;
763 let scale_diff = current_scale - u8::try_from(precision_diff).unwrap();
764 rescale(n, scale_diff)?;
765 } else {
766 bail!(
767 "numeric value {} exceed maximum precision {}",
768 n,
769 NUMERIC_DATUM_MAX_PRECISION
770 )
771 }
772 }
773 Ok(())
774}
775
776pub fn rescale(n: &mut Numeric, scale: u8) -> Result<(), anyhow::Error> {
781 let mut cx = cx_datum();
782 cx.rescale(n, &Numeric::from(-i32::from(scale)));
783 if cx.status().invalid_operation() || get_precision(n) > u32::from(NUMERIC_DATUM_MAX_PRECISION)
784 {
785 bail!(
786 "numeric value {} exceed maximum precision {}",
787 n,
788 NUMERIC_DATUM_MAX_PRECISION
789 )
790 }
791 munge_numeric(n)?;
792
793 Ok(())
794}
795
796pub trait DecimalLike:
799 From<u8>
800 + From<u16>
801 + From<u32>
802 + From<i8>
803 + From<i16>
804 + From<i32>
805 + From<f32>
806 + From<f64>
807 + std::ops::Add<Output = Self>
808 + std::ops::Sub<Output = Self>
809 + std::ops::Mul<Output = Self>
810 + std::ops::Div<Output = Self>
811{
812 fn lossy_from(i: i64) -> Self;
815}
816
817impl DecimalLike for f64 {
818 #[allow(clippy::as_conversions)]
820 fn lossy_from(i: i64) -> Self {
821 i as f64
822 }
823}
824
825impl DecimalLike for Numeric {
826 fn lossy_from(i: i64) -> Self {
827 Numeric::from(i)
828 }
829}
830
831#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
836pub struct PackedNumeric(pub [u8; 40]);
837
838impl FixedSizeCodec<Numeric> for PackedNumeric {
839 const SIZE: usize = 40;
840
841 fn as_bytes(&self) -> &[u8] {
842 &self.0
843 }
844
845 fn from_bytes(slice: &[u8]) -> Result<Self, String> {
846 let buf: [u8; Self::SIZE] = slice.try_into().map_err(|_| {
847 format!(
848 "size for PackedNumeric is {} bytes, got {}",
849 Self::SIZE,
850 slice.len()
851 )
852 })?;
853 Ok(PackedNumeric(buf))
854 }
855
856 fn from_value(val: Numeric) -> PackedNumeric {
857 let (digits, exponent, bits, lsu) = val.to_raw_parts();
858
859 let mut buf = [0u8; 40];
860
861 buf[0..4].copy_from_slice(&digits.to_le_bytes());
862 buf[4..8].copy_from_slice(&exponent.to_le_bytes());
863
864 for i in 0..13 {
865 buf[(i * 2) + 8..(i * 2) + 10].copy_from_slice(&lsu[i].to_le_bytes());
866 }
867
868 buf[34..35].copy_from_slice(&bits.to_le_bytes());
869
870 PackedNumeric(buf)
871 }
872
873 fn into_value(self) -> Numeric {
874 let digits: [u8; 4] = self.0[0..4].try_into().unwrap();
875 let digits = u32::from_le_bytes(digits);
876
877 let exponent: [u8; 4] = self.0[4..8].try_into().unwrap();
878 let exponent = i32::from_le_bytes(exponent);
879
880 let mut lsu = [0u16; 13];
881 for i in 0..13 {
882 let x: [u8; 2] = self.0[(i * 2) + 8..(i * 2) + 10].try_into().unwrap();
883 let x = u16::from_le_bytes(x);
884 lsu[i] = x;
885 }
886
887 let bits: [u8; 1] = self.0[34..35].try_into().unwrap();
888 let bits = u8::from_le_bytes(bits);
889
890 Numeric::from_raw_parts(digits, exponent, bits, lsu)
891 }
892}
893
894mod columnar_impls {
895 use std::ops::Range;
896
897 use columnar::bytes::indexed::DecodedStore;
898 use columnar::{AsBytes, Borrow, Clear, Columnar, Container, FromBytes, Index, Len, Push};
899
900 use super::{NUMERIC_AGG_WIDTH_USIZE, NumericAgg, OrderedNumericAgg};
901
902 const LSU_COLUMN_WIDTH: usize = NUMERIC_AGG_WIDTH_USIZE + 1;
907 type Parts = (u32, i32, u8, [u16; LSU_COLUMN_WIDTH]);
910 type PartsContainer = (Vec<u32>, Vec<i32>, Vec<u8>, Vec<[u16; LSU_COLUMN_WIDTH]>);
914 type PartsBorrowed<'a> = <PartsContainer as Borrow>::Borrowed<'a>;
915
916 impl Columnar for OrderedNumericAgg {
917 #[inline(always)]
918 fn into_owned(other: columnar::Ref<'_, Self>) -> Self {
919 other
920 }
921 type Container = OrderedNumericAggs;
922 #[inline(always)]
923 fn reborrow<'b, 'a: 'b>(thing: columnar::Ref<'a, Self>) -> columnar::Ref<'b, Self>
924 where
925 Self: 'a,
926 {
927 thing
928 }
929 }
930
931 #[derive(Copy, Clone, Debug, Default)]
936 pub struct OrderedNumericAggs<TC = PartsContainer>(TC);
937
938 impl Borrow for OrderedNumericAggs {
939 type Ref<'a> = OrderedNumericAgg;
940 type Borrowed<'a> = OrderedNumericAggs<PartsBorrowed<'a>>;
941 #[inline(always)]
942 fn borrow<'a>(&'a self) -> Self::Borrowed<'a> {
943 OrderedNumericAggs(self.0.borrow())
944 }
945 #[inline(always)]
946 fn reborrow<'b, 'a: 'b>(item: Self::Borrowed<'a>) -> Self::Borrowed<'b>
947 where
948 Self: 'a,
949 {
950 OrderedNumericAggs(<PartsContainer as Borrow>::reborrow(item.0))
951 }
952 #[inline(always)]
953 fn reborrow_ref<'b, 'a: 'b>(item: Self::Ref<'a>) -> Self::Ref<'b>
954 where
955 Self: 'a,
956 {
957 item
958 }
959 }
960
961 impl Container for OrderedNumericAggs {
962 #[inline(always)]
963 fn extend_from_self(&mut self, other: Self::Borrowed<'_>, range: Range<usize>) {
964 self.0.extend_from_self(other.0, range);
965 }
966 #[inline(always)]
967 fn reserve_for<'a, I>(&mut self, selves: I)
968 where
969 Self: 'a,
970 I: Iterator<Item = Self::Borrowed<'a>> + Clone,
971 {
972 self.0.reserve_for(selves.map(|s| s.0));
973 }
974 }
975
976 impl<TC: Len> Len for OrderedNumericAggs<TC> {
977 #[inline(always)]
978 fn len(&self) -> usize {
979 self.0.len()
980 }
981 }
982
983 impl Clear for OrderedNumericAggs {
984 #[inline(always)]
985 fn clear(&mut self) {
986 self.0.clear();
987 }
988 }
989
990 impl<'a> Index for OrderedNumericAggs<PartsBorrowed<'a>> {
991 type Ref = OrderedNumericAgg;
992 #[inline(always)]
993 fn get(&self, index: usize) -> Self::Ref {
994 let (digits, exponent, bits, lsu) = self.0.get(index);
995 let mut units = [0u16; NUMERIC_AGG_WIDTH_USIZE];
996 units.copy_from_slice(&lsu[..NUMERIC_AGG_WIDTH_USIZE]);
997 OrderedNumericAgg(NumericAgg::from_raw_parts(*digits, *exponent, *bits, units))
998 }
999 }
1000
1001 impl Push<OrderedNumericAgg> for OrderedNumericAggs {
1002 #[inline(always)]
1003 fn push(&mut self, item: OrderedNumericAgg) {
1004 let (digits, exponent, bits, units) = item.0.to_raw_parts();
1005 let mut lsu = [0u16; LSU_COLUMN_WIDTH];
1006 lsu[..NUMERIC_AGG_WIDTH_USIZE].copy_from_slice(&units);
1007 let parts: Parts = (digits, exponent, bits, lsu);
1008 self.0.push(parts);
1009 }
1010 }
1011
1012 impl Push<&OrderedNumericAgg> for OrderedNumericAggs {
1013 #[inline(always)]
1014 fn push(&mut self, item: &OrderedNumericAgg) {
1015 self.push(*item);
1016 }
1017 }
1018
1019 impl<'a, TC: AsBytes<'a>> AsBytes<'a> for OrderedNumericAggs<TC> {
1020 const SLICE_COUNT: usize = TC::SLICE_COUNT;
1021 #[inline(always)]
1022 fn get_byte_slice(&self, index: usize) -> (u64, &'a [u8]) {
1023 self.0.get_byte_slice(index)
1024 }
1025 }
1026
1027 impl<'a, TC: FromBytes<'a>> FromBytes<'a> for OrderedNumericAggs<TC> {
1028 const SLICE_COUNT: usize = TC::SLICE_COUNT;
1029 #[inline(always)]
1030 fn from_bytes(bytes: &mut impl Iterator<Item = &'a [u8]>) -> Self {
1031 OrderedNumericAggs(TC::from_bytes(bytes))
1032 }
1033 #[inline(always)]
1034 fn from_store(store: &DecodedStore<'a>, offset: &mut usize) -> Self {
1035 OrderedNumericAggs(TC::from_store(store, offset))
1036 }
1037 fn element_sizes(sizes: &mut Vec<usize>) -> Result<(), String> {
1038 TC::element_sizes(sizes)
1039 }
1040 }
1041}
1042
1043#[cfg(test)]
1044mod tests {
1045 use mz_ore::assert_ok;
1046 use mz_proto::protobuf_roundtrip;
1047 use proptest::prelude::*;
1048
1049 use crate::scalar::arb_numeric;
1050
1051 use super::*;
1052
1053 proptest! {
1054 #[mz_ore::test]
1055 fn numeric_max_scale_protobuf_roundtrip(expect in any::<NumericMaxScale>()) {
1056 let actual = protobuf_roundtrip::<_, ProtoNumericMaxScale>(&expect);
1057 assert_ok!(actual);
1058 assert_eq!(actual.unwrap(), expect);
1059 }
1060
1061 #[mz_ore::test]
1062 fn optional_numeric_max_scale_protobuf_roundtrip(
1063 expect in any::<Option<NumericMaxScale>>(),
1064 ) {
1065 let actual = protobuf_roundtrip::<_, ProtoOptionalNumericMaxScale>(&expect);
1066 assert_ok!(actual);
1067 assert_eq!(actual.unwrap(), expect);
1068 }
1069 }
1070
1071 #[mz_ore::test]
1072 #[cfg_attr(miri, ignore)] fn smoketest_packed_numeric_roundtrips() {
1074 let og = PackedNumeric::from_value(Numeric::from(-42));
1075 let bytes = og.as_bytes();
1076 let rnd = PackedNumeric::from_bytes(bytes).expect("valid");
1077 assert_eq!(og, rnd);
1078
1079 mz_ore::assert_err!(PackedNumeric::from_bytes(&[0, 0, 0, 0]));
1081 }
1082
1083 #[mz_ore::test]
1084 #[cfg_attr(miri, ignore)] fn proptest_packed_numeric_roundtrip() {
1086 fn test(og: Numeric) {
1087 let packed = PackedNumeric::from_value(og);
1088 let rnd = packed.into_value();
1089
1090 if og.is_nan() && rnd.is_nan() {
1091 return;
1092 }
1093 assert_eq!(og, rnd);
1094 }
1095
1096 proptest!(|(num in arb_numeric())| {
1097 test(num);
1098 });
1099 }
1100
1101 #[mz_ore::test]
1105 #[cfg_attr(miri, ignore)] fn packed_numeric_stability() {
1107 const RNG_SEED: [u8; 32] = [
1110 0xf4, 0x16, 0x16, 0x48, 0xc3, 0xac, 0x77, 0xac, 0x72, 0x20, 0x0b, 0xea, 0x99, 0x67,
1111 0x2d, 0x6d, 0xca, 0x9f, 0x76, 0xaf, 0x1b, 0x09, 0x73, 0xa0, 0x59, 0x22, 0x6d, 0xc5,
1112 0x46, 0x39, 0x1c, 0x4a,
1113 ];
1114
1115 let rng = proptest::test_runner::TestRng::from_seed(
1116 proptest::test_runner::RngAlgorithm::ChaCha,
1117 &RNG_SEED,
1118 );
1119 let config = proptest::test_runner::Config {
1121 cases: u32::MAX,
1123 rng_algorithm: proptest::test_runner::RngAlgorithm::ChaCha,
1124 ..Default::default()
1125 };
1126 let mut runner = proptest::test_runner::TestRunner::new_with_rng(config, rng);
1127
1128 let test_cases = 2_000;
1129 let strat = arb_numeric();
1130
1131 let mut all_numerics = Vec::new();
1132 for _ in 0..test_cases {
1133 let value_tree = strat.new_tree(&mut runner).unwrap();
1134 let numeric = value_tree.current();
1135 let packed = PackedNumeric::from_value(numeric);
1136 let hex_bytes = format!("{:x?}", packed.as_bytes());
1137
1138 all_numerics.push((numeric, hex_bytes));
1139 }
1140
1141 insta::assert_debug_snapshot!(all_numerics);
1142 }
1143
1144 #[mz_ore::test]
1145 fn ordered_numeric_agg_columnar_round_trip() {
1146 use columnar::{AsBytes, Borrow, BorrowedOf, Columnar, FromBytes, Index, Len};
1147
1148 let mut cx = cx_agg();
1149 let values: Vec<OrderedNumericAgg> = [
1150 "0",
1151 "-0",
1152 "1",
1153 "-12345.678",
1154 "9e39",
1155 "9e-39",
1156 "123456789012345678901234567890123456789012345678901234567890",
1157 "NaN",
1158 "Infinity",
1159 "-Infinity",
1160 ]
1161 .into_iter()
1162 .map(|s| OrderedNumericAgg(cx.parse(s).unwrap()))
1163 .collect();
1164
1165 let container = OrderedNumericAgg::as_columns(values.iter());
1166 assert_eq!(container.len(), values.len());
1167 let borrowed = container.borrow();
1168 for (index, value) in values.iter().enumerate() {
1169 assert_eq!(borrowed.get(index), *value);
1170 }
1171
1172 let bytes: Vec<&[u8]> = borrowed.as_bytes().map(|(_align, bytes)| bytes).collect();
1173 let decoded = BorrowedOf::<OrderedNumericAgg>::from_bytes(&mut bytes.into_iter());
1174 assert_eq!(decoded.len(), values.len());
1175 for (index, value) in values.iter().enumerate() {
1176 assert_eq!(decoded.get(index), *value);
1177 }
1178
1179 let mut words = Vec::new();
1182 columnar::bytes::indexed::encode(&mut words, &borrowed);
1183 columnar::bytes::indexed::validate::<BorrowedOf<OrderedNumericAgg>>(&words).unwrap();
1184 let store = columnar::bytes::indexed::DecodedStore::new(&words);
1185 let decoded = BorrowedOf::<OrderedNumericAgg>::from_store(&store, &mut 0);
1186 assert_eq!(decoded.len(), values.len());
1187 for (index, value) in values.iter().enumerate() {
1188 assert_eq!(decoded.get(index), *value);
1189 }
1190 }
1191}