use std::error::Error;
use std::fmt;
use std::sync::LazyLock;
use anyhow::bail;
use dec::{Context, Decimal};
use mz_lowertest::MzReflect;
use mz_ore::cast;
use mz_persist_types::columnar::FixedSizeCodec;
use mz_proto::{ProtoType, RustType, TryFromProtoError};
use proptest_derive::Arbitrary;
use serde::{Deserialize, Serialize};
include!(concat!(env!("OUT_DIR"), "/mz_repr.adt.numeric.rs"));
pub const NUMERIC_DATUM_WIDTH: u8 = 13;
pub const NUMERIC_DATUM_WIDTH_USIZE: usize = cast::u8_to_usize(NUMERIC_DATUM_WIDTH);
pub const NUMERIC_DATUM_MAX_PRECISION: u8 = NUMERIC_DATUM_WIDTH * 3;
pub type Numeric = Decimal<NUMERIC_DATUM_WIDTH_USIZE>;
pub const NUMERIC_AGG_WIDTH: u8 = 27;
pub const NUMERIC_AGG_WIDTH_USIZE: usize = cast::u8_to_usize(NUMERIC_AGG_WIDTH);
pub const NUMERIC_AGG_MAX_PRECISION: u8 = NUMERIC_AGG_WIDTH * 3;
pub type NumericAgg = Decimal<NUMERIC_AGG_WIDTH_USIZE>;
static CX_DATUM: LazyLock<Context<Numeric>> = LazyLock::new(|| {
let mut cx = Context::<Numeric>::default();
cx.set_max_exponent(isize::from(NUMERIC_DATUM_MAX_PRECISION - 1))
.unwrap();
cx.set_min_exponent(-isize::from(NUMERIC_DATUM_MAX_PRECISION))
.unwrap();
cx
});
static CX_AGG: LazyLock<Context<NumericAgg>> = LazyLock::new(|| {
let mut cx = Context::<NumericAgg>::default();
cx.set_max_exponent(isize::from(NUMERIC_AGG_MAX_PRECISION - 1))
.unwrap();
cx.set_min_exponent(-isize::from(NUMERIC_AGG_MAX_PRECISION))
.unwrap();
cx
});
static U128_SPLITTER_DATUM: LazyLock<Numeric> = LazyLock::new(|| {
let mut cx = Numeric::context();
cx.parse("340282366920938463463374607431768211456").unwrap()
});
static U128_SPLITTER_AGG: LazyLock<NumericAgg> = LazyLock::new(|| {
let mut cx = NumericAgg::context();
cx.parse("340282366920938463463374607431768211456").unwrap()
});
pub mod str_serde {
use std::str::FromStr;
use serde::Deserialize;
use super::Numeric;
pub fn deserialize<'de, D>(deserializer: D) -> Result<Numeric, D::Error>
where
D: serde::Deserializer<'de>,
{
let buf = String::deserialize(deserializer)?;
Numeric::from_str(&buf).map_err(serde::de::Error::custom)
}
}
#[derive(
Arbitrary,
Debug,
Clone,
Copy,
Eq,
PartialEq,
Ord,
PartialOrd,
Hash,
Serialize,
Deserialize,
MzReflect,
)]
pub struct NumericMaxScale(pub(crate) u8);
impl NumericMaxScale {
pub const ZERO: NumericMaxScale = NumericMaxScale(0);
pub fn into_u8(self) -> u8 {
self.0
}
}
impl TryFrom<i64> for NumericMaxScale {
type Error = InvalidNumericMaxScaleError;
fn try_from(max_scale: i64) -> Result<Self, Self::Error> {
match u8::try_from(max_scale) {
Ok(max_scale) if max_scale <= NUMERIC_DATUM_MAX_PRECISION => {
Ok(NumericMaxScale(max_scale))
}
_ => Err(InvalidNumericMaxScaleError),
}
}
}
impl TryFrom<usize> for NumericMaxScale {
type Error = InvalidNumericMaxScaleError;
fn try_from(max_scale: usize) -> Result<Self, Self::Error> {
Self::try_from(i64::try_from(max_scale).map_err(|_| InvalidNumericMaxScaleError)?)
}
}
impl RustType<ProtoNumericMaxScale> for NumericMaxScale {
fn into_proto(&self) -> ProtoNumericMaxScale {
ProtoNumericMaxScale {
value: self.0.into_proto(),
}
}
fn from_proto(max_scale: ProtoNumericMaxScale) -> Result<Self, TryFromProtoError> {
Ok(NumericMaxScale(max_scale.value.into_rust()?))
}
}
impl RustType<ProtoOptionalNumericMaxScale> for Option<NumericMaxScale> {
fn into_proto(&self) -> ProtoOptionalNumericMaxScale {
ProtoOptionalNumericMaxScale {
value: self.into_proto(),
}
}
fn from_proto(max_scale: ProtoOptionalNumericMaxScale) -> Result<Self, TryFromProtoError> {
max_scale.value.into_rust()
}
}
#[derive(Debug, Clone)]
pub struct InvalidNumericMaxScaleError;
impl fmt::Display for InvalidNumericMaxScaleError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"scale for type numeric must be between 0 and {}",
NUMERIC_DATUM_MAX_PRECISION
)
}
}
impl Error for InvalidNumericMaxScaleError {}
pub trait Dec<const N: usize> {
const TWOS_COMPLEMENT_BYTE_WIDTH: usize;
fn context() -> Context<Decimal<N>>;
fn u128_splitter() -> &'static Decimal<N>;
}
impl Dec<NUMERIC_DATUM_WIDTH_USIZE> for Numeric {
const TWOS_COMPLEMENT_BYTE_WIDTH: usize = 17;
fn context() -> Context<Numeric> {
CX_DATUM.clone()
}
fn u128_splitter() -> &'static Numeric {
&U128_SPLITTER_DATUM
}
}
impl Dec<NUMERIC_AGG_WIDTH_USIZE> for NumericAgg {
const TWOS_COMPLEMENT_BYTE_WIDTH: usize = 33;
fn context() -> Context<NumericAgg> {
CX_AGG.clone()
}
fn u128_splitter() -> &'static NumericAgg {
&U128_SPLITTER_AGG
}
}
pub fn cx_datum() -> Context<Numeric> {
CX_DATUM.clone()
}
pub fn cx_agg() -> Context<NumericAgg> {
CX_AGG.clone()
}
fn twos_complement_be_to_u128(input: &[u8]) -> u128 {
assert!(input.len() <= 16);
let mut buf = [0; 16];
buf[16 - input.len()..16].copy_from_slice(input);
u128::from_be_bytes(buf)
}
fn negate_twos_complement_le<'a, I>(b: I)
where
I: Iterator<Item = &'a mut u8>,
{
let mut seen_first_one = false;
for i in b {
if seen_first_one {
*i = *i ^ 0xFF;
} else if *i > 0 {
seen_first_one = true;
if i == &0x80 {
continue;
}
let tz = i.trailing_zeros();
*i = *i ^ (0xFF << tz + 1);
}
}
}
pub fn numeric_to_twos_complement_be(
mut numeric: Numeric,
) -> [u8; Numeric::TWOS_COMPLEMENT_BYTE_WIDTH] {
let mut buf = [0; Numeric::TWOS_COMPLEMENT_BYTE_WIDTH];
if numeric.is_special() {
return buf;
}
let mut cx = Numeric::context();
if numeric.exponent() < 0 {
let s = Numeric::from(-numeric.exponent());
cx.scaleb(&mut numeric, &s);
}
numeric_to_twos_complement_inner::<Numeric, NUMERIC_DATUM_WIDTH_USIZE>(
numeric, &mut cx, &mut buf,
);
buf
}
pub fn numeric_to_twos_complement_wide(
numeric: Numeric,
) -> [u8; NumericAgg::TWOS_COMPLEMENT_BYTE_WIDTH] {
let mut buf = [0; NumericAgg::TWOS_COMPLEMENT_BYTE_WIDTH];
if numeric.is_special() {
return buf;
}
let mut cx = NumericAgg::context();
let mut d = cx.to_width(numeric);
let mut scaler = NumericAgg::from(NUMERIC_DATUM_MAX_PRECISION);
cx.neg(&mut scaler);
cx.rescale(&mut d, &scaler);
cx.abs(&mut scaler);
cx.scaleb(&mut d, &scaler);
numeric_to_twos_complement_inner::<NumericAgg, NUMERIC_AGG_WIDTH_USIZE>(d, &mut cx, &mut buf);
buf
}
fn numeric_to_twos_complement_inner<D: Dec<N>, const N: usize>(
mut d: Decimal<N>,
cx: &mut Context<Decimal<N>>,
buf: &mut [u8],
) {
let is_neg = if d.is_negative() {
cx.neg(&mut d);
true
} else {
false
};
assert!(d.exponent() >= 0);
let mut buf_cursor = 0;
while !d.is_zero() {
let mut w = d.clone();
cx.rem(&mut w, D::u128_splitter());
let c = w.coefficient::<u128>().unwrap();
let e = std::cmp::min(buf_cursor + 16, D::TWOS_COMPLEMENT_BYTE_WIDTH);
buf[buf_cursor..e].copy_from_slice(&c.to_le_bytes()[0..e - buf_cursor]);
buf_cursor += 16;
cx.div_integer(&mut d, D::u128_splitter());
}
if is_neg {
negate_twos_complement_le(buf.iter_mut());
}
buf.reverse();
}
pub fn twos_complement_be_to_numeric(
input: &mut [u8],
scale: u8,
) -> Result<Numeric, anyhow::Error> {
let mut cx = cx_datum();
if input.len() <= 17 {
if let Ok(mut n) =
twos_complement_be_to_numeric_inner::<Numeric, NUMERIC_DATUM_WIDTH_USIZE>(input)
{
n.set_exponent(-i32::from(scale));
return Ok(n);
}
}
let mut n = twos_complement_be_to_numeric_inner::<NumericAgg, NUMERIC_AGG_WIDTH_USIZE>(input)?;
n.set_exponent(-i32::from(scale));
let d = cx.to_width(n);
if cx.status().inexact() {
bail!("Value exceeds maximum numeric value")
}
Ok(d)
}
pub fn twos_complement_be_to_numeric_inner<D: Dec<N>, const N: usize>(
input: &mut [u8],
) -> Result<Decimal<N>, anyhow::Error> {
let is_neg = if (input[0] & 0x80) != 0 {
negate_twos_complement_le(input.iter_mut().rev());
true
} else {
false
};
let head = input.len() % 16;
let i = twos_complement_be_to_u128(&input[0..head]);
let mut cx = D::context();
let mut d = cx.from_u128(i);
for c in input[head..].chunks(16) {
assert_eq!(c.len(), 16);
cx.mul(&mut d, D::u128_splitter());
let i = twos_complement_be_to_u128(c);
let i = cx.from_u128(i);
cx.add(&mut d, &i);
}
if cx.status().inexact() {
bail!("Value exceeds maximum numeric value")
} else if cx.status().any() {
bail!("unexpected status {:?}", cx.status());
}
if is_neg {
cx.neg(&mut d);
}
Ok(d)
}
#[mz_ore::test]
#[cfg_attr(miri, ignore)] fn test_twos_complement_roundtrip() {
fn inner(s: &str) {
let mut cx = cx_datum();
let d = cx.parse(s).unwrap();
let scale = std::cmp::min(d.exponent(), 0).abs();
let mut b = numeric_to_twos_complement_be(d.clone());
let x = twos_complement_be_to_numeric(&mut b, u8::try_from(scale).unwrap()).unwrap();
assert_eq!(d, x);
}
inner("0");
inner("0.000000000000000000000000000000000012345");
inner("0.123456789012345678901234567890123456789");
inner("1.00000000000000000000000000000000000000");
inner("1");
inner("2");
inner("170141183460469231731687303715884105727");
inner("170141183460469231731687303715884105728");
inner("12345678901234567890.1234567890123456789");
inner("999999999999999999999999999999999999999");
inner("7e35");
inner("7e-35");
inner("-0.000000000000000000000000000000000012345");
inner("-0.12345678901234567890123456789012345678");
inner("-1.00000000000000000000000000000000000000");
inner("-1");
inner("-2");
inner("-170141183460469231731687303715884105727");
inner("-170141183460469231731687303715884105728");
inner("-12345678901234567890.1234567890123456789");
inner("-999999999999999999999999999999999999999");
inner("-7.2e35");
inner("-7.2e-35");
}
#[mz_ore::test]
#[cfg_attr(miri, ignore)] fn test_twos_comp_numeric_primitive() {
fn inner_inner<P>(i: P, i_be_bytes: &mut [u8])
where
P: Into<Numeric> + TryFrom<Numeric> + Eq + PartialEq + std::fmt::Debug + Copy,
{
let mut e = [0; Numeric::TWOS_COMPLEMENT_BYTE_WIDTH];
e[Numeric::TWOS_COMPLEMENT_BYTE_WIDTH - i_be_bytes.len()..].copy_from_slice(i_be_bytes);
let mut w = [0; NumericAgg::TWOS_COMPLEMENT_BYTE_WIDTH];
w[NumericAgg::TWOS_COMPLEMENT_BYTE_WIDTH - i_be_bytes.len()..].copy_from_slice(i_be_bytes);
let d: Numeric = i.into();
if d.is_negative() {
for i in e[..Numeric::TWOS_COMPLEMENT_BYTE_WIDTH - i_be_bytes.len()].iter_mut() {
*i = 0xFF;
}
for i in w[..NumericAgg::TWOS_COMPLEMENT_BYTE_WIDTH - i_be_bytes.len()].iter_mut() {
*i = 0xFF;
}
}
let d_be_bytes = numeric_to_twos_complement_be(d);
assert_eq!(
e, d_be_bytes,
"expected repr of {:?}, got {:?}",
e, d_be_bytes
);
let e_numeric = twos_complement_be_to_numeric(&mut e, 0).unwrap();
let e_p: P = match e_numeric.try_into() {
Ok(e_p) => e_p,
Err(_) => panic!(),
};
assert_eq!(i, e_p, "expected val of {:?}, got {:?}", i, e_p);
let w_numeric = twos_complement_be_to_numeric(&mut w, 0).unwrap();
let w_p: P = match w_numeric.try_into() {
Ok(w_p) => w_p,
Err(_) => panic!(),
};
assert_eq!(i, w_p, "expected val of {:?}, got {:?}", i, e_p);
let p_numeric = twos_complement_be_to_numeric(i_be_bytes, 0).unwrap();
let p_p: P = match p_numeric.try_into() {
Ok(p_p) => p_p,
Err(_) => panic!(),
};
assert_eq!(i, p_p, "expected val of {:?}, got {:?}", i, p_p);
}
fn inner_i32(i: i32) {
inner_inner(i, &mut i.to_be_bytes());
}
fn inner_i64(i: i64) {
inner_inner(i, &mut i.to_be_bytes());
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct FromableI128 {
i: i128,
}
impl From<i128> for FromableI128 {
fn from(i: i128) -> FromableI128 {
FromableI128 { i }
}
}
impl From<FromableI128> for Numeric {
fn from(n: FromableI128) -> Numeric {
Numeric::try_from(n.i).unwrap()
}
}
impl TryFrom<Numeric> for FromableI128 {
type Error = ();
fn try_from(n: Numeric) -> Result<FromableI128, Self::Error> {
match i128::try_from(n) {
Ok(i) => Ok(FromableI128 { i }),
Err(_) => Err(()),
}
}
}
fn inner_i128(i: i128) {
inner_inner(FromableI128::from(i), &mut i.to_be_bytes());
}
inner_i32(0);
inner_i32(1);
inner_i32(2);
inner_i32(-1);
inner_i32(-2);
inner_i32(i32::MAX);
inner_i32(i32::MIN);
inner_i32(i32::MAX / 7 + 7);
inner_i32(i32::MIN / 7 + 7);
inner_i64(0);
inner_i64(1);
inner_i64(2);
inner_i64(-1);
inner_i64(-2);
inner_i64(i64::MAX);
inner_i64(i64::MIN);
inner_i64(i64::MAX / 7 + 7);
inner_i64(i64::MIN / 7 + 7);
inner_i128(0);
inner_i128(1);
inner_i128(2);
inner_i128(-1);
inner_i128(-2);
inner_i128(i128::from(i64::MAX));
inner_i128(i128::from(i64::MIN));
inner_i128(i128::MAX);
inner_i128(i128::MIN);
inner_i128(i128::MAX / 7 + 7);
inner_i128(i128::MIN / 7 + 7);
}
#[mz_ore::test]
#[cfg_attr(miri, ignore)] fn test_twos_complement_to_numeric_fail() {
fn inner(b: &mut [u8]) {
let r = twos_complement_be_to_numeric(b, 0);
mz_ore::assert_err!(r);
}
let mut e = [0xFF; Numeric::TWOS_COMPLEMENT_BYTE_WIDTH];
e[0] -= 0x80;
inner(&mut e);
let mut e = [0; Numeric::TWOS_COMPLEMENT_BYTE_WIDTH + 1];
e[0] = 1;
inner(&mut e);
}
#[mz_ore::test]
#[cfg_attr(miri, ignore)] fn test_wide_twos_complement_roundtrip() {
fn inner(s: &str) {
let mut cx = cx_datum();
let d = cx.parse(s).unwrap();
let mut b = numeric_to_twos_complement_wide(d.clone());
let x = twos_complement_be_to_numeric(&mut b, NUMERIC_DATUM_MAX_PRECISION).unwrap();
assert_eq!(d, x);
}
inner("0");
inner("0.000000000000000000000000000000000012345");
inner("0.123456789012345678901234567890123456789");
inner("1.00000000000000000000000000000000000000");
inner("1");
inner("2");
inner("170141183460469231731687303715884105727");
inner("170141183460469231731687303715884105728");
inner("12345678901234567890.1234567890123456789");
inner("999999999999999999999999999999999999999");
inner("-0.000000000000000000000000000000000012345");
inner("-0.123456789012345678901234567890123456789");
inner("-1.00000000000000000000000000000000000000");
inner("-1");
inner("-2");
inner("-170141183460469231731687303715884105727");
inner("-170141183460469231731687303715884105728");
inner("-12345678901234567890.1234567890123456789");
inner("-999999999999999999999999999999999999999");
}
pub fn get_precision<const N: usize>(n: &Decimal<N>) -> u32 {
let e = n.exponent();
if e >= 0 {
n.digits() + u32::try_from(e).unwrap()
} else {
let d = n.digits();
let e = u32::try_from(e.abs()).unwrap();
std::cmp::max(d, e)
}
}
pub fn get_scale(n: &Numeric) -> u32 {
let exp = n.exponent();
if exp >= 0 {
0
} else {
exp.unsigned_abs()
}
}
pub fn munge_numeric(n: &mut Numeric) -> Result<(), anyhow::Error> {
rescale_within_max_precision(n)?;
if (n.is_zero() || n.is_nan()) && n.is_negative() {
cx_datum().neg(n);
}
Ok(())
}
fn rescale_within_max_precision(n: &mut Numeric) -> Result<(), anyhow::Error> {
let current_precision = get_precision(n);
if current_precision > u32::from(NUMERIC_DATUM_MAX_PRECISION) {
if n.exponent() < 0 {
let precision_diff = current_precision - u32::from(NUMERIC_DATUM_MAX_PRECISION);
let current_scale = u8::try_from(get_scale(n))?;
let scale_diff = current_scale - u8::try_from(precision_diff).unwrap();
rescale(n, scale_diff)?;
} else {
bail!(
"numeric value {} exceed maximum precision {}",
n,
NUMERIC_DATUM_MAX_PRECISION
)
}
}
Ok(())
}
pub fn rescale(n: &mut Numeric, scale: u8) -> Result<(), anyhow::Error> {
let mut cx = cx_datum();
cx.rescale(n, &Numeric::from(-i32::from(scale)));
if cx.status().invalid_operation() || get_precision(n) > u32::from(NUMERIC_DATUM_MAX_PRECISION)
{
bail!(
"numeric value {} exceed maximum precision {}",
n,
NUMERIC_DATUM_MAX_PRECISION
)
}
munge_numeric(n)?;
Ok(())
}
pub trait DecimalLike:
From<u8>
+ From<u16>
+ From<u32>
+ From<i8>
+ From<i16>
+ From<i32>
+ From<f32>
+ From<f64>
+ std::ops::Add<Output = Self>
+ std::ops::Sub<Output = Self>
+ std::ops::Mul<Output = Self>
+ std::ops::Div<Output = Self>
{
fn lossy_from(i: i64) -> Self;
}
impl DecimalLike for f64 {
#[allow(clippy::as_conversions)]
fn lossy_from(i: i64) -> Self {
i as f64
}
}
impl DecimalLike for Numeric {
fn lossy_from(i: i64) -> Self {
Numeric::from(i)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct PackedNumeric(pub [u8; 40]);
impl FixedSizeCodec<Numeric> for PackedNumeric {
const SIZE: usize = 40;
fn as_bytes(&self) -> &[u8] {
&self.0
}
fn from_bytes(slice: &[u8]) -> Result<Self, String> {
let buf: [u8; Self::SIZE] = slice.try_into().map_err(|_| {
format!(
"size for PackedNumeric is {} bytes, got {}",
Self::SIZE,
slice.len()
)
})?;
Ok(PackedNumeric(buf))
}
fn from_value(val: Numeric) -> PackedNumeric {
let (digits, exponent, bits, lsu) = val.to_raw_parts();
let mut buf = [0u8; 40];
buf[0..4].copy_from_slice(&digits.to_le_bytes());
buf[4..8].copy_from_slice(&exponent.to_le_bytes());
for i in 0..13 {
buf[(i * 2) + 8..(i * 2) + 10].copy_from_slice(&lsu[i].to_le_bytes());
}
buf[34..35].copy_from_slice(&bits.to_le_bytes());
PackedNumeric(buf)
}
fn into_value(self) -> Numeric {
let digits: [u8; 4] = self.0[0..4].try_into().unwrap();
let digits = u32::from_le_bytes(digits);
let exponent: [u8; 4] = self.0[4..8].try_into().unwrap();
let exponent = i32::from_le_bytes(exponent);
let mut lsu = [0u16; 13];
for i in 0..13 {
let x: [u8; 2] = self.0[(i * 2) + 8..(i * 2) + 10].try_into().unwrap();
let x = u16::from_le_bytes(x);
lsu[i] = x;
}
let bits: [u8; 1] = self.0[34..35].try_into().unwrap();
let bits = u8::from_le_bytes(bits);
Numeric::from_raw_parts(digits, exponent, bits, lsu)
}
}
#[cfg(test)]
mod tests {
use mz_ore::assert_ok;
use mz_proto::protobuf_roundtrip;
use proptest::prelude::*;
use crate::scalar::arb_numeric;
use super::*;
proptest! {
#[mz_ore::test]
fn numeric_max_scale_protobuf_roundtrip(expect in any::<NumericMaxScale>()) {
let actual = protobuf_roundtrip::<_, ProtoNumericMaxScale>(&expect);
assert_ok!(actual);
assert_eq!(actual.unwrap(), expect);
}
#[mz_ore::test]
fn optional_numeric_max_scale_protobuf_roundtrip(expect in any::<Option<NumericMaxScale>>()) {
let actual = protobuf_roundtrip::<_, ProtoOptionalNumericMaxScale>(&expect);
assert_ok!(actual);
assert_eq!(actual.unwrap(), expect);
}
}
#[mz_ore::test]
#[cfg_attr(miri, ignore)] fn smoketest_packed_numeric_roundtrips() {
let og = PackedNumeric::from_value(Numeric::from(-42));
let bytes = og.as_bytes();
let rnd = PackedNumeric::from_bytes(bytes).expect("valid");
assert_eq!(og, rnd);
mz_ore::assert_err!(PackedNumeric::from_bytes(&[0, 0, 0, 0]));
}
#[mz_ore::test]
#[cfg_attr(miri, ignore)] fn proptest_packed_numeric_roundtrip() {
fn test(og: Numeric) {
let packed = PackedNumeric::from_value(og);
let rnd = packed.into_value();
if og.is_nan() && rnd.is_nan() {
return;
}
assert_eq!(og, rnd);
}
proptest!(|(num in arb_numeric())| {
test(num);
});
}
#[mz_ore::test]
#[cfg_attr(miri, ignore)] fn packed_numeric_stability() {
const RNG_SEED: [u8; 32] = [
0xf4, 0x16, 0x16, 0x48, 0xc3, 0xac, 0x77, 0xac, 0x72, 0x20, 0x0b, 0xea, 0x99, 0x67,
0x2d, 0x6d, 0xca, 0x9f, 0x76, 0xaf, 0x1b, 0x09, 0x73, 0xa0, 0x59, 0x22, 0x6d, 0xc5,
0x46, 0x39, 0x1c, 0x4a,
];
let rng = proptest::test_runner::TestRng::from_seed(
proptest::test_runner::RngAlgorithm::ChaCha,
&RNG_SEED,
);
let config = proptest::test_runner::Config {
cases: u32::MAX,
rng_algorithm: proptest::test_runner::RngAlgorithm::ChaCha,
..Default::default()
};
let mut runner = proptest::test_runner::TestRunner::new_with_rng(config, rng);
let test_cases = 2_000;
let strat = arb_numeric();
let mut all_numerics = Vec::new();
for _ in 0..test_cases {
let value_tree = strat.new_tree(&mut runner).unwrap();
let numeric = value_tree.current();
let packed = PackedNumeric::from_value(numeric);
let hex_bytes = format!("{:x?}", packed.as_bytes());
all_numerics.push((numeric, hex_bytes));
}
insta::assert_debug_snapshot!(all_numerics);
}
}