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::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
32/// The number of internal decimal units in a [`Numeric`] value.
33pub const NUMERIC_DATUM_WIDTH: u8 = 13;
34
35/// The value of [`NUMERIC_DATUM_WIDTH`] as a [`u8`].
36pub const NUMERIC_DATUM_WIDTH_USIZE: usize = cast::u8_to_usize(NUMERIC_DATUM_WIDTH);
37
38/// The maximum number of digits expressable in a [`Numeric`] value.
39pub const NUMERIC_DATUM_MAX_PRECISION: u8 = NUMERIC_DATUM_WIDTH * 3;
40
41/// A numeric value.
42pub type Numeric = Decimal<NUMERIC_DATUM_WIDTH_USIZE>;
43
44/// The number of internal decimal units in a [`NumericAgg`] value.
45pub const NUMERIC_AGG_WIDTH: u8 = 27;
46
47/// The value of [`NUMERIC_AGG_WIDTH`] as a [`u8`].
48pub const NUMERIC_AGG_WIDTH_USIZE: usize = cast::u8_to_usize(NUMERIC_AGG_WIDTH);
49
50/// The maximum number of digits expressable in a [`NumericAgg`] value.
51pub const NUMERIC_AGG_MAX_PRECISION: u8 = NUMERIC_AGG_WIDTH * 3;
52
53/// A double-width version of [`Numeric`] for use in aggregations.
54pub 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    // 1 << 128
75    cx.parse("340282366920938463463374607431768211456").unwrap()
76});
77static U128_SPLITTER_AGG: LazyLock<NumericAgg> = LazyLock::new(|| {
78    let mut cx = NumericAgg::context();
79    // 1 << 128
80    cx.parse("340282366920938463463374607431768211456").unwrap()
81});
82
83/// Module to simplify serde'ing a `Numeric` through its string representation.
84pub mod str_serde {
85    use std::str::FromStr;
86
87    use serde::Deserialize;
88
89    use super::Numeric;
90
91    /// Deserializing a [`Numeric`] value from its `String` representation.
92    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/// The `max_scale` of a [`SqlScalarType::Numeric`].
102///
103/// This newtype wrapper ensures that the scale is within the valid range.
104///
105/// [`SqlScalarType::Numeric`]: crate::SqlScalarType::Numeric
106#[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    /// A max scale of zero.
122    pub const ZERO: NumericMaxScale = NumericMaxScale(0);
123
124    /// Consumes the newtype wrapper, returning the inner `u8`.
125    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    // NOTE: `from_proto` is a trust boundary for durable and protocol state, so it
171    // enforces the same domain as `TryFrom<i64>` rather than trusting the wire.
172    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/// The error returned when constructing a [`NumericMaxScale`] from an invalid
195/// value.
196#[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
211/// Traits to generalize converting [`Decimal`] values to and from their
212/// coefficients' two's complements.
213pub trait Dec<const N: usize> {
214    // The number of bytes required to represent the min/max value of a decimal
215    // using two's complement.
216    const TWOS_COMPLEMENT_BYTE_WIDTH: usize;
217    // Convenience method for generating appropriate default contexts.
218    fn context() -> Context<Decimal<N>>;
219    // Provides value to break decimal into units of `u128`s for binary
220    // encoding/decoding.
221    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
244/// Returns a new context appropriate for operating on numeric datums.
245pub fn cx_datum() -> Context<Numeric> {
246    CX_DATUM.clone()
247}
248
249/// Returns a new context appropriate for operating on numeric aggregates.
250pub 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
261/// Using negative binary numbers can require more digits of precision than
262/// [`Numeric`] offers, so we need to have the option to swap bytes' signs at the
263/// byte- rather than the library-level.
264fn 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
283/// Converts an [`Numeric`] into its big endian two's complement representation.
284pub 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    // Avro doesn't specify how to handle NaN/infinity, so we simply treat them
289    // as zeroes so as to avoid erroring (encoding values is meant to be
290    // infallible) and retain downstream associativity/commutativity.
291    if numeric.is_special() {
292        return buf;
293    }
294
295    let mut cx = Numeric::context();
296
297    // Ensure `numeric` is a canonical coefficient.
298    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
309/// Converts an [`Numeric`] into a big endian two's complement representation where
310/// the encoded value has [`NUMERIC_AGG_MAX_PRECISION`] digits and a scale of
311/// [`NUMERIC_DATUM_MAX_PRECISION`].
312///
313/// This representation is appropriate to use in
314/// contexts requiring two's complement representation but `Numeric` values' scale
315/// isn't known, e.g. when working with columns with an explicitly defined
316/// scale.
317pub 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    // Avro doesn't specify how to handle NaN/infinity, so we simply treat them
322    // as zeroes so as to avoid erroring (encoding values is meant to be
323    // infallible) and retain downstream associativity/commutativity.
324    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    // Shape `d` so that its exponent is -NUMERIC_DATUM_MAX_PRECISION
332    cx.rescale(&mut d, &scaler);
333    // Adjust `d` so it is a canonical coefficient, i.e. its exact value can be
334    // recovered by setting its exponent to -39.
335    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    // Adjust negative values to be writable as series of `u128`.
348    let is_neg = if d.is_negative() {
349        cx.neg(&mut d);
350        true
351    } else {
352        false
353    };
354
355    // Values have all been made into canonical coefficients.
356    assert!(d.exponent() >= 0);
357
358    let mut buf_cursor = 0;
359    while !d.is_zero() {
360        let mut w = d.clone();
361        // Take the remainder; this represents one of our "units" to take the coefficient of, i.e. d & u128::MAX
362        cx.rem(&mut w, D::u128_splitter());
363
364        // Take the `u128` version of the coefficient, which will always be what
365        // we want given that we adjusted negative values to have an unsigned
366        // integer representation.
367        let c = w.coefficient::<u128>().unwrap();
368
369        // Determine the width of the coefficient we want to take, i.e. the full
370        // coefficient or a part of it to fill the buffer.
371        let e = std::cmp::min(buf_cursor + 16, D::TWOS_COMPLEMENT_BYTE_WIDTH);
372
373        // We're putting less significant bytes at index 0, which is little endian.
374        buf[buf_cursor..e].copy_from_slice(&c.to_le_bytes()[0..e - buf_cursor]);
375        // Advance cursor; ok that it will go past buffer on final + 1th iteration.
376        buf_cursor += 16;
377
378        // Take the quotient to represent the next unit, i.e. d >> 128
379        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    // Convert from little endian to big endian.
387    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    // If bytes were invalid for narrower representation, try to use wider
404    // representation in case e.g. simply has more trailing zeroes.
405    let mut n = twos_complement_be_to_numeric_inner::<NumericAgg, NUMERIC_AGG_WIDTH_USIZE>(input)?;
406    // Exponent must be set before converting to `Numeric` width, otherwise values can overflow 39 dop.
407    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
415/// Parses a buffer of two's complement digits in big-endian order and converts
416/// them to [`Decimal<N>`].
417pub 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        // An empty byte string is not a valid two's-complement integer. Our own
422        // encoder never emits one (zero is the single byte `0x00`), so this only
423        // arises from untrusted input. One example is an Avro `decimal` field
424        // whose unscaled value was encoded as zero-length `bytes`. Reject it
425        // rather than indexing `input[0]` below and panicking.
426        bail!("cannot parse a numeric value from an empty byte string");
427    }
428    let is_neg = if (input[0] & 0x80) != 0 {
429        // byte-level negate all negative values, guaranteeing all bytes are
430        // readable as unsigned.
431        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        // essentially d << 128
445        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)] // unsupported operation: can't call foreign function `decNumberFromInt32` on OS `linux`
464fn 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)] // unsupported operation: can't call foreign function `decNumberFromInt32` on OS `linux`
500fn test_twos_complement_empty_is_error() {
501    // An empty byte string is not a valid two's-complement integer and used to
502    // panic with an out-of-bounds index. It only arrives from untrusted input
503    // (e.g. an Avro `decimal` encoded as zero-length `bytes`), so it must be a
504    // clean error, not a panic. Regression test for that fix.
505    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)] // unsupported operation: can't call foreign function `decNumberFromInt32` on OS `linux`
515fn 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        // Extend negative sign into most-significant bits
528        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        // Ensure decimal value's two's complement representation matches an
538        // extended version of `to_be_bytes`.
539        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        // Ensure extended version of `to_be_bytes` generates same `i128`.
547        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        // Wide representation produces same result.
554        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        // Bytes do not need to be in `Numeric`-specific format
561        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    // We need a wrapper around i128 to implement the same traits as the other
577    // primitive types. This is less code than a second implementation of the
578    // same test that takes unwrapped i128s.
579    #[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)] // unsupported operation: can't call foreign function `decNumberFromInt32` on OS `linux`
640fn 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    // 17-byte signed digit's max value exceeds 39 digits of precision
646    let mut e = [0xFF; Numeric::TWOS_COMPLEMENT_BYTE_WIDTH];
647    e[0] -= 0x80;
648    inner(&mut e);
649
650    // 1 << 17 * 8 exceeds exceeds 39 digits of precision
651    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)] // unsupported operation: can't call foreign function `decNumberFromInt32` on OS `linux`
658fn 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
687/// Returns `n`'s precision, i.e. the total number of digits represented by `n`
688/// in standard notation not including a zero in the "one's place" in (-1,1).
689pub fn get_precision<const N: usize>(n: &Decimal<N>) -> u32 {
690    let e = n.exponent();
691    if e >= 0 {
692        // Positive exponent
693        n.digits() + u32::try_from(e).unwrap()
694    } else {
695        // Negative exponent
696        let d = n.digits();
697        let e = u32::try_from(e.abs()).unwrap();
698        // Precision is...
699        // - d if decimal point splits numbers
700        // - e if e dominates number of digits
701        std::cmp::max(d, e)
702    }
703}
704
705/// Returns `n`'s scale, i.e. the number of digits used after the decimal point.
706pub fn get_scale(n: &Numeric) -> u32 {
707    let exp = n.exponent();
708    if exp >= 0 { 0 } else { exp.unsigned_abs() }
709}
710
711/// Ensures [`Numeric`] values are:
712/// - Within `Numeric`'s max precision ([`NUMERIC_DATUM_MAX_PRECISION`]), or errors if not.
713/// - Never possible but invalid representations (i.e. never -Nan or -0).
714///
715/// Should be called after any operation that can change an [`Numeric`]'s scale or
716/// generate negative values (except addition and subtraction).
717pub 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
725/// Rescale's `n` to fit within [`Numeric`]'s max precision or error if not
726/// possible.
727fn 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
746/// Rescale `n` as an `OrderedDecimal` with the described scale, or error if:
747/// - Rescaling exceeds max precision
748/// - `n` requires > [`NUMERIC_DATUM_MAX_PRECISION`] - `scale` digits of precision
749///   left of the decimal point
750pub 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
766/// A type that can represent Real Numbers. Useful for interoperability between Numeric and
767/// floating point.
768pub 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    /// Used to do value-to-value conversions while consuming the input value. Depending on the
783    /// implementation it may be potentially lossy.
784    fn lossy_from(i: i64) -> Self;
785}
786
787impl DecimalLike for f64 {
788    // No other known way to convert `i64` to `f64`.
789    #[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/// An encoded packed variant of [`Numeric`].
802///
803/// Unlike other "Packed" types we _DO NOT_ uphold the invariant that
804/// [`PackedNumeric`] sorts the same as [`Numeric`].
805#[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)] // error: unsupported operation: can't call foreign function `decNumberFromInt32` on OS `linux`
894    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        // Returns an error if the size of the slice is invalid.
901        mz_ore::assert_err!(PackedNumeric::from_bytes(&[0, 0, 0, 0]));
902    }
903
904    #[mz_ore::test]
905    #[cfg_attr(miri, ignore)] // error: unsupported operation: can't call foreign function `decNumberCopyNegate` on OS `linux`
906    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    // Note: It's expected that this test will fail if you update the strategy
923    // for generating an arbitrary Numeric. In that case feel free to
924    // regenerate the snapshot.
925    #[mz_ore::test]
926    #[cfg_attr(miri, ignore)] // error: unsupported operation: can't call foreign function `decNumberCopyNegate` on OS `linux`
927    fn packed_numeric_stability() {
928        /// This is the seed [`proptest`] uses for their deterministic RNG. We
929        /// copy it here to prevent breaking this test if [`proptest`] changes.
930        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        // Generate a collection of Rows.
941        let config = proptest::test_runner::Config {
942            // We let the loop below drive how much data we generate.
943            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}