Skip to main content

mz_repr/adt/
numeric.rs

1// Copyright Materialize, Inc. and contributors. All rights reserved.
2//
3// Use of this software is governed by the Business Source License
4// included in the LICENSE file.
5//
6// As of the Change Date specified in that file, in accordance with
7// the Business Source License, use of this software will be governed
8// by the Apache License, Version 2.0.
9
10//! Functions related to Materialize's numeric type, which is largely a wrapper
11//! around [`rust-dec`].
12//!
13//! [`rust-dec`]: https://github.com/MaterializeInc/rust-dec/
14
15use 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
33/// The number of internal decimal units in a [`Numeric`] value.
34pub const NUMERIC_DATUM_WIDTH: u8 = 13;
35
36/// The value of [`NUMERIC_DATUM_WIDTH`] as a [`u8`].
37pub const NUMERIC_DATUM_WIDTH_USIZE: usize = cast::u8_to_usize(NUMERIC_DATUM_WIDTH);
38
39/// The maximum number of digits expressable in a [`Numeric`] value.
40pub const NUMERIC_DATUM_MAX_PRECISION: u8 = NUMERIC_DATUM_WIDTH * 3;
41
42/// A numeric value.
43pub type Numeric = Decimal<NUMERIC_DATUM_WIDTH_USIZE>;
44
45/// The number of internal decimal units in a [`NumericAgg`] value.
46pub const NUMERIC_AGG_WIDTH: u8 = 27;
47
48/// The value of [`NUMERIC_AGG_WIDTH`] as a [`u8`].
49pub const NUMERIC_AGG_WIDTH_USIZE: usize = cast::u8_to_usize(NUMERIC_AGG_WIDTH);
50
51/// The maximum number of digits expressable in a [`NumericAgg`] value.
52pub const NUMERIC_AGG_MAX_PRECISION: u8 = NUMERIC_AGG_WIDTH * 3;
53
54/// A double-width version of [`Numeric`] for use in aggregations.
55pub type NumericAgg = Decimal<NUMERIC_AGG_WIDTH_USIZE>;
56
57/// A [`NumericAgg`] with the total order of [`OrderedDecimal`], storable in columnar form.
58///
59/// Equality and ordering are those of `OrderedDecimal`, so NaN equals NaN and every
60/// value has a defined position. This crate cannot implement `Columnar` for
61/// `OrderedDecimal<NumericAgg>`, as both the trait and the type are foreign, so the
62/// newtype hosts that impl.
63#[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    // 1 << 128
105    cx.parse("340282366920938463463374607431768211456").unwrap()
106});
107static U128_SPLITTER_AGG: LazyLock<NumericAgg> = LazyLock::new(|| {
108    let mut cx = NumericAgg::context();
109    // 1 << 128
110    cx.parse("340282366920938463463374607431768211456").unwrap()
111});
112
113/// Module to simplify serde'ing a `Numeric` through its string representation.
114pub mod str_serde {
115    use std::str::FromStr;
116
117    use serde::Deserialize;
118
119    use super::Numeric;
120
121    /// Deserializing a [`Numeric`] value from its `String` representation.
122    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/// The `max_scale` of a [`SqlScalarType::Numeric`].
132///
133/// This newtype wrapper ensures that the scale is within the valid range.
134///
135/// [`SqlScalarType::Numeric`]: crate::SqlScalarType::Numeric
136#[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    /// A max scale of zero.
152    pub const ZERO: NumericMaxScale = NumericMaxScale(0);
153
154    /// Consumes the newtype wrapper, returning the inner `u8`.
155    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    // NOTE: `from_proto` is a trust boundary for durable and protocol state, so it
201    // enforces the same domain as `TryFrom<i64>` rather than trusting the wire.
202    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/// The error returned when constructing a [`NumericMaxScale`] from an invalid
225/// value.
226#[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
241/// Traits to generalize converting [`Decimal`] values to and from their
242/// coefficients' two's complements.
243pub trait Dec<const N: usize> {
244    // The number of bytes required to represent the min/max value of a decimal
245    // using two's complement.
246    const TWOS_COMPLEMENT_BYTE_WIDTH: usize;
247    // Convenience method for generating appropriate default contexts.
248    fn context() -> Context<Decimal<N>>;
249    // Provides value to break decimal into units of `u128`s for binary
250    // encoding/decoding.
251    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
274/// Returns a new context appropriate for operating on numeric datums.
275pub fn cx_datum() -> Context<Numeric> {
276    CX_DATUM.clone()
277}
278
279/// Returns a new context appropriate for operating on numeric aggregates.
280pub 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
291/// Using negative binary numbers can require more digits of precision than
292/// [`Numeric`] offers, so we need to have the option to swap bytes' signs at the
293/// byte- rather than the library-level.
294fn 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
313/// Converts an [`Numeric`] into its big endian two's complement representation.
314pub 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    // Avro doesn't specify how to handle NaN/infinity, so we simply treat them
319    // as zeroes so as to avoid erroring (encoding values is meant to be
320    // infallible) and retain downstream associativity/commutativity.
321    if numeric.is_special() {
322        return buf;
323    }
324
325    let mut cx = Numeric::context();
326
327    // Ensure `numeric` is a canonical coefficient.
328    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
339/// Converts an [`Numeric`] into a big endian two's complement representation where
340/// the encoded value has [`NUMERIC_AGG_MAX_PRECISION`] digits and a scale of
341/// [`NUMERIC_DATUM_MAX_PRECISION`].
342///
343/// This representation is appropriate to use in
344/// contexts requiring two's complement representation but `Numeric` values' scale
345/// isn't known, e.g. when working with columns with an explicitly defined
346/// scale.
347pub 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    // Avro doesn't specify how to handle NaN/infinity, so we simply treat them
352    // as zeroes so as to avoid erroring (encoding values is meant to be
353    // infallible) and retain downstream associativity/commutativity.
354    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    // Shape `d` so that its exponent is -NUMERIC_DATUM_MAX_PRECISION
362    cx.rescale(&mut d, &scaler);
363    // Adjust `d` so it is a canonical coefficient, i.e. its exact value can be
364    // recovered by setting its exponent to -39.
365    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    // Adjust negative values to be writable as series of `u128`.
378    let is_neg = if d.is_negative() {
379        cx.neg(&mut d);
380        true
381    } else {
382        false
383    };
384
385    // Values have all been made into canonical coefficients.
386    assert!(d.exponent() >= 0);
387
388    let mut buf_cursor = 0;
389    while !d.is_zero() {
390        let mut w = d.clone();
391        // Take the remainder; this represents one of our "units" to take the coefficient of, i.e. d & u128::MAX
392        cx.rem(&mut w, D::u128_splitter());
393
394        // Take the `u128` version of the coefficient, which will always be what
395        // we want given that we adjusted negative values to have an unsigned
396        // integer representation.
397        let c = w.coefficient::<u128>().unwrap();
398
399        // Determine the width of the coefficient we want to take, i.e. the full
400        // coefficient or a part of it to fill the buffer.
401        let e = std::cmp::min(buf_cursor + 16, D::TWOS_COMPLEMENT_BYTE_WIDTH);
402
403        // We're putting less significant bytes at index 0, which is little endian.
404        buf[buf_cursor..e].copy_from_slice(&c.to_le_bytes()[0..e - buf_cursor]);
405        // Advance cursor; ok that it will go past buffer on final + 1th iteration.
406        buf_cursor += 16;
407
408        // Take the quotient to represent the next unit, i.e. d >> 128
409        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    // Convert from little endian to big endian.
417    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    // If bytes were invalid for narrower representation, try to use wider
434    // representation in case e.g. simply has more trailing zeroes.
435    let mut n = twos_complement_be_to_numeric_inner::<NumericAgg, NUMERIC_AGG_WIDTH_USIZE>(input)?;
436    // Exponent must be set before converting to `Numeric` width, otherwise values can overflow 39 dop.
437    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
445/// Parses a buffer of two's complement digits in big-endian order and converts
446/// them to [`Decimal<N>`].
447pub 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        // An empty byte string is not a valid two's-complement integer. Our own
452        // encoder never emits one (zero is the single byte `0x00`), so this only
453        // arises from untrusted input. One example is an Avro `decimal` field
454        // whose unscaled value was encoded as zero-length `bytes`. Reject it
455        // rather than indexing `input[0]` below and panicking.
456        bail!("cannot parse a numeric value from an empty byte string");
457    }
458    let is_neg = if (input[0] & 0x80) != 0 {
459        // byte-level negate all negative values, guaranteeing all bytes are
460        // readable as unsigned.
461        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        // essentially d << 128
475        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)] // unsupported operation: can't call foreign function `decNumberFromInt32` on OS `linux`
494fn 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)] // unsupported operation: can't call foreign function `decNumberFromInt32` on OS `linux`
530fn test_twos_complement_empty_is_error() {
531    // An empty byte string is not a valid two's-complement integer and used to
532    // panic with an out-of-bounds index. It only arrives from untrusted input
533    // (e.g. an Avro `decimal` encoded as zero-length `bytes`), so it must be a
534    // clean error, not a panic. Regression test for that fix.
535    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)] // unsupported operation: can't call foreign function `decNumberFromInt32` on OS `linux`
545fn 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        // Extend negative sign into most-significant bits
558        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        // Ensure decimal value's two's complement representation matches an
568        // extended version of `to_be_bytes`.
569        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        // Ensure extended version of `to_be_bytes` generates same `i128`.
577        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        // Wide representation produces same result.
584        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        // Bytes do not need to be in `Numeric`-specific format
591        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    // We need a wrapper around i128 to implement the same traits as the other
607    // primitive types. This is less code than a second implementation of the
608    // same test that takes unwrapped i128s.
609    #[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)] // unsupported operation: can't call foreign function `decNumberFromInt32` on OS `linux`
670fn 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    // 17-byte signed digit's max value exceeds 39 digits of precision
676    let mut e = [0xFF; Numeric::TWOS_COMPLEMENT_BYTE_WIDTH];
677    e[0] -= 0x80;
678    inner(&mut e);
679
680    // 1 << 17 * 8 exceeds exceeds 39 digits of precision
681    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)] // unsupported operation: can't call foreign function `decNumberFromInt32` on OS `linux`
688fn 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
717/// Returns `n`'s precision, i.e. the total number of digits represented by `n`
718/// in standard notation not including a zero in the "one's place" in (-1,1).
719pub fn get_precision<const N: usize>(n: &Decimal<N>) -> u32 {
720    let e = n.exponent();
721    if e >= 0 {
722        // Positive exponent
723        n.digits() + u32::try_from(e).unwrap()
724    } else {
725        // Negative exponent
726        let d = n.digits();
727        let e = u32::try_from(e.abs()).unwrap();
728        // Precision is...
729        // - d if decimal point splits numbers
730        // - e if e dominates number of digits
731        std::cmp::max(d, e)
732    }
733}
734
735/// Returns `n`'s scale, i.e. the number of digits used after the decimal point.
736pub fn get_scale(n: &Numeric) -> u32 {
737    let exp = n.exponent();
738    if exp >= 0 { 0 } else { exp.unsigned_abs() }
739}
740
741/// Ensures [`Numeric`] values are:
742/// - Within `Numeric`'s max precision ([`NUMERIC_DATUM_MAX_PRECISION`]), or errors if not.
743/// - Never possible but invalid representations (i.e. never -Nan or -0).
744///
745/// Should be called after any operation that can change an [`Numeric`]'s scale or
746/// generate negative values (except addition and subtraction).
747pub 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
755/// Rescale's `n` to fit within [`Numeric`]'s max precision or error if not
756/// possible.
757fn 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
776/// Rescale `n` as an `OrderedDecimal` with the described scale, or error if:
777/// - Rescaling exceeds max precision
778/// - `n` requires > [`NUMERIC_DATUM_MAX_PRECISION`] - `scale` digits of precision
779///   left of the decimal point
780pub 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
796/// A type that can represent Real Numbers. Useful for interoperability between Numeric and
797/// floating point.
798pub 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    /// Used to do value-to-value conversions while consuming the input value. Depending on the
813    /// implementation it may be potentially lossy.
814    fn lossy_from(i: i64) -> Self;
815}
816
817impl DecimalLike for f64 {
818    // No other known way to convert `i64` to `f64`.
819    #[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/// An encoded packed variant of [`Numeric`].
832///
833/// Unlike other "Packed" types we _DO NOT_ uphold the invariant that
834/// [`PackedNumeric`] sorts the same as [`Numeric`].
835#[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    /// Width of the coefficient column. One unit wider than the decimal's coefficient so
903    /// that each element is 56 bytes, a whole number of `u64` words. The indexed byte
904    /// store pads every column to whole words and cannot decode an element width that
905    /// does not divide that padding.
906    const LSU_COLUMN_WIDTH: usize = NUMERIC_AGG_WIDTH_USIZE + 1;
907    /// The raw parts of a [`NumericAgg`], in the order `Decimal::to_raw_parts` returns
908    /// them, with the coefficient units zero-padded to [`LSU_COLUMN_WIDTH`].
909    type Parts = (u32, i32, u8, [u16; LSU_COLUMN_WIDTH]);
910    /// One column per raw part. The coefficient units use `Vec<[u16; N]>` directly rather
911    /// than the array's own columnar container, which would add per-element offsets to a
912    /// fixed-width value.
913    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    /// Columnar container for [`OrderedNumericAgg`].
932    ///
933    /// References are owned values rebuilt from the part columns, so comparisons on
934    /// references use the decimal order rather than the raw parts' lexicographic order.
935    #[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)] // error: unsupported operation: can't call foreign function `decNumberFromInt32` on OS `linux`
1073    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        // Returns an error if the size of the slice is invalid.
1080        mz_ore::assert_err!(PackedNumeric::from_bytes(&[0, 0, 0, 0]));
1081    }
1082
1083    #[mz_ore::test]
1084    #[cfg_attr(miri, ignore)] // error: unsupported operation: can't call foreign function `decNumberCopyNegate` on OS `linux`
1085    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    // Note: It's expected that this test will fail if you update the strategy
1102    // for generating an arbitrary Numeric. In that case feel free to
1103    // regenerate the snapshot.
1104    #[mz_ore::test]
1105    #[cfg_attr(miri, ignore)] // error: unsupported operation: can't call foreign function `decNumberCopyNegate` on OS `linux`
1106    fn packed_numeric_stability() {
1107        /// This is the seed [`proptest`] uses for their deterministic RNG. We
1108        /// copy it here to prevent breaking this test if [`proptest`] changes.
1109        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        // Generate a collection of Rows.
1120        let config = proptest::test_runner::Config {
1121            // We let the loop below drive how much data we generate.
1122            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        // The indexed store pads each column to whole words, so decoding through it
1180        // also checks that the column layout tolerates that padding.
1181        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}