use anyhow::Result;
use fastnum::decimal::{Context, Sign};
use fastnum::{D128, U128};
use rust_decimal::Decimal;
use crate::err::Error;
pub(crate) struct DecimalLexEncoder;
impl DecimalLexEncoder {
const EXP_BIAS: i32 = 6144;
const FINITE_NEGATIVE_MARKER: u8 = 0x40;
const FINITE_POSITIVE_MARKER: u8 = 0xA0;
const ZERO_MARKER: u8 = 0x80;
const INFINITE_NEGATIVE_MARKER: u8 = 0x20;
const INFINITE_POSITIVE_MAKER: u8 = 0xC0;
const NAN_MARKER: u8 = 0xFF;
pub(crate) fn encode(dec: D128) -> Vec<u8> {
if dec.is_nan() {
return vec![Self::NAN_MARKER, 0x00];
}
let is_negative = dec.is_negative();
if dec.is_infinite() {
if is_negative {
return vec![Self::INFINITE_NEGATIVE_MARKER, 0x00];
} else {
return vec![Self::INFINITE_POSITIVE_MAKER, 0x00];
}
}
if dec.is_zero() {
return vec![Self::ZERO_MARKER, 0x00];
}
let normalized = dec.abs(); let e = -normalized.fractional_digits_count() as i32; let digit_count = normalized.digits_count();
let scale = e + (digit_count as i32 - 1);
let biased_exponent = (scale + Self::EXP_BIAS) as u16;
let encode_exponent = |e: u16| {
let q = (e / 255) as u8;
let r = (e % 255) as u8;
[q + 1, r + 1]
};
let mut result = Vec::with_capacity(5 + (digit_count + 1).div_ceil(2));
let radix10 = normalized.digits().to_str_radix(10);
if is_negative {
result.push(Self::FINITE_NEGATIVE_MARKER);
result.extend(encode_exponent(0xFFFF - biased_exponent));
Self::pack_digits_negative(&radix10, &mut result);
} else {
result.push(Self::FINITE_POSITIVE_MARKER);
result.extend(encode_exponent(biased_exponent));
Self::pack_digits_positive(&radix10, &mut result);
}
result.push(0x00);
result
}
pub(crate) fn decode(bytes: &[u8]) -> Result<D128> {
if bytes.is_empty() {
return Err(Error::Serialization("Cannot decode from empty buffer".to_string()).into());
}
let is_negative = match bytes[0] {
Self::ZERO_MARKER => {
return Ok(D128::ZERO);
}
Self::INFINITE_NEGATIVE_MARKER => return Ok(D128::NEG_INFINITY),
Self::INFINITE_POSITIVE_MAKER => return Ok(D128::INFINITY),
Self::NAN_MARKER => return Ok(D128::NAN),
Self::FINITE_NEGATIVE_MARKER => true,
Self::FINITE_POSITIVE_MARKER => false,
marker => {
return Err(Error::Serialization(format!("Invalid marker byte: {marker}")).into());
}
};
if bytes.len() < 3 {
return Err(Error::Serialization(format!("Buffer too short: {}", bytes.len())).into());
}
let biased_exponent = (bytes[1] - 1) as u16 * 255 + (bytes[2] - 1) as u16;
let biased_exponent = if is_negative {
0xFFFF - biased_exponent
} else {
biased_exponent
};
let scale = biased_exponent as i32 - Self::EXP_BIAS;
let (mantissa, digit_count) = if is_negative {
Self::unpack_digits_negative(&bytes[3..])?
} else {
Self::unpack_digits_positive(&bytes[3..])?
};
if digit_count == 0 {
return Err(Error::Serialization("Empty mantissa".to_string()).into());
}
let exponent = scale - (digit_count - 1);
Ok(D128::from_parts(
mantissa,
exponent,
if is_negative {
Sign::Minus
} else {
Sign::Plus
},
Context::default(),
))
}
fn pack_digits_negative(radix10: &str, buf: &mut Vec<u8>) {
let mut iter = radix10.as_bytes().chunks_exact(2);
for pair in &mut iter {
let hi = pair[0] - 47;
let lo = pair[1] - 47;
let packed = (hi << 4) | lo;
buf.push(!packed); }
if let Some(remainder) = iter.remainder().first() {
let hi = remainder - 47;
let packed = hi << 4;
buf.push(!packed);
} else {
buf.push(0xF0); }
}
fn pack_digits_positive(radix10: &str, buf: &mut Vec<u8>) {
let mut iter = radix10.as_bytes().chunks_exact(2);
for pair in &mut iter {
let hi = pair[0] - 47;
let lo = pair[1] - 47;
let packed = (hi << 4) | lo;
buf.push(packed);
}
if let Some(remainder) = iter.remainder().first() {
let hi = remainder - 47;
let packed = hi << 4;
buf.push(packed);
} else {
buf.push(0x0F);
}
}
fn unpack_digits_positive(buf: &[u8]) -> Result<(U128, i32)> {
let mut m = U128::ZERO;
let mut l = 0;
for pack in buf {
let d = Self::unpack_digit(*pack, &mut m)?;
l += d as i32;
if d < 2 {
break;
}
}
Ok((m, l))
}
fn unpack_digits_negative(buf: &[u8]) -> Result<(U128, i32)> {
let mut m = U128::ZERO;
let mut l = 0i32;
for pack in buf {
let d = Self::unpack_digit(!*pack, &mut m)?;
l += d as i32;
if d < 2 {
break;
}
}
Ok((m, l))
}
fn unpack_digit(pack: u8, m: &mut U128) -> Result<u8> {
let hi = pack >> 4;
let lo = pack & 0x0F;
if hi == 0x0 {
return Ok(0);
}
if !(1..=10).contains(&hi) {
return Err(anyhow::Error::new(Error::Serialization(format!(
"Invalid high nibble: {hi}"
))));
}
*m = *m * U128::TEN + U128::from(hi - 1);
if lo == 0 {
return Ok(1);
}
if !(1..=10).contains(&lo) {
return Err(anyhow::Error::new(Error::Serialization(format!(
"Invalid low nibble: {lo}"
))));
}
*m = *m * U128::TEN + U128::from(lo - 1);
Ok(2)
}
pub(crate) fn to_d128(dec: Decimal) -> D128 {
let scale = dec.scale();
let mantissa = dec.mantissa(); let sign = if mantissa < 0 {
Sign::Minus
} else {
Sign::Plus
};
let abs =
U128::from_u128(mantissa.unsigned_abs()).expect("u128 conversion should not fail");
D128::from_parts(abs, -(scale as i32), sign, Context::default())
}
pub(crate) fn to_decimal(d128: D128) -> Result<Decimal> {
Ok(Decimal::from_str_radix(&d128.to_string(), 10)?)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn test_cases() -> [D128; 32] {
[
D128::from(f64::NEG_INFINITY),
D128::from(f64::MIN),
D128::from_i128(i128::MIN).unwrap(),
D128::from(i64::MIN),
D128::from(-1001),
D128::from(-1000),
D128::from(-999),
D128::from(-100),
-D128::TEN,
D128::from(-9),
D128::from(-3.15),
D128::from(-std::f64::consts::PI),
D128::from(-1.5f64),
-D128::ONE,
D128::ZERO,
D128::ONE,
D128::from(1.5f64),
D128::from(2),
D128::from(std::f64::consts::PI),
D128::from(3.15),
D128::from(9),
D128::TEN,
D128::from(100),
D128::from(999),
D128::from(1000),
D128::from(1001),
D128::from(i64::MAX),
D128::from_i128(i128::MAX).unwrap(),
D128::from_u128(u128::MAX).unwrap(),
D128::from(f64::MAX),
D128::from(f64::INFINITY),
D128::from(f64::NAN),
]
}
#[test]
fn test_encode_decode_roundtrip() {
let cases = test_cases();
for (i, case) in cases.into_iter().enumerate() {
let encoded = DecimalLexEncoder::encode(case);
let decoded = DecimalLexEncoder::decode(&encoded).expect("Decode should succeed");
if case.is_nan() {
assert!(decoded.is_nan(), "Roundtrip failed for {i}: {case} != {decoded}");
} else {
assert_eq!(case, decoded, "Roundtrip failed for {i}: {case} != {decoded}");
}
}
}
#[test]
fn test_encode_terminate_with_zero() {
let cases = test_cases();
for (i, case) in cases.into_iter().enumerate() {
let encoded = DecimalLexEncoder::encode(case);
assert_eq!(
encoded.iter().filter(|&b| *b == 0x00).count(),
1,
"Encoded buffer should contains only one 0x00 - {i}: {case} {encoded:?}"
);
assert_eq!(
encoded.iter().position(|&b| b == 0x00).unwrap(),
encoded.len() - 1,
"Encoded buffer should terminate with 0x00 - {i}: {case} {encoded:?}"
);
}
}
#[test]
fn test_lexicographic_ordering() {
let cases = test_cases();
for (i, window) in cases.windows(2).enumerate() {
let n1 = &window[0];
let n2 = &window[1];
assert!(n1 < n2, "#{i} - {n1:?} < {n2:?} (before serialization)");
let b1 = DecimalLexEncoder::encode(*n1);
let b2 = DecimalLexEncoder::encode(*n2);
assert!(b1 < b2, "#{i} - {n1:?} < {n2:?} (after serialization) - {b1:?} < {b2:?}");
}
}
#[test]
fn test_decode_empty_buffer() {
let result = DecimalLexEncoder::decode(&[]);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("Cannot decode from empty buffer"));
}
#[test]
fn test_decode_buffer_too_short() {
let result = DecimalLexEncoder::decode(&[0xA0]);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(err.to_string().contains("Buffer too short"), "{err:?}");
}
#[test]
fn test_decode_invalid_marker() {
let result = DecimalLexEncoder::decode(&[0x42, 0x00, 0x00, 0x00]);
assert!(result.is_err());
let err = result.unwrap_err();
assert_eq!(err.to_string(), "Serialization error: Invalid marker byte: 66", "{err:?}");
}
#[test]
fn test_decode_empty_mantissa() {
let result = DecimalLexEncoder::decode(&[0xA0, 0x01, 0x01, 0x00]); assert!(result.is_err());
let err = result.unwrap_err();
assert!(err.to_string().contains("Empty mantissa"), "{err:?}");
}
}