use crate::ToOrderableBytes;
use rust_decimal::Decimal;
const MANTISSA_BYTES: usize = 13;
const EXP_BIAS: i32 = 64;
const PADDED_DIGITS: u32 = 29;
const SIGN_BIT: u8 = 0x80;
const EXP_MASK: u8 = 0x7F;
impl ToOrderableBytes for Decimal {
const ENCODED_LEN: usize = 14;
type Bytes = [u8; Self::ENCODED_LEN];
fn to_orderable_bytes(&self) -> [u8; Self::ENCODED_LEN] {
let d = self;
let mut out = [0u8; Self::ENCODED_LEN];
let raw_mantissa = d.mantissa();
let scale = d.scale() as i32;
let sign_extension = raw_mantissa >> 127;
let abs_mantissa = ((raw_mantissa ^ sign_extension).wrapping_sub(sign_extension)) as u128;
let (significand, trailing) = strip_trailing_zeros(abs_mantissa);
let digits = digit_count(significand);
let leading_exp = digits as i32 - 1 + trailing - scale;
debug_assert!(
(-29..=28).contains(&leading_exp),
"leading_exp {} out of bounds — mantissa or scale corrupted",
leading_exp,
);
let biased_exp = (leading_exp + EXP_BIAS) as u8;
debug_assert!(biased_exp <= EXP_MASK, "biased_exp overflowed 7 bits");
let mut padded_mantissa = significand;
for i in 0..PADDED_DIGITS {
let do_step = ((digits + i) < PADDED_DIGITS) as u128;
let mask = 0u128.wrapping_sub(do_step);
let stepped = padded_mantissa.wrapping_mul(10);
padded_mantissa = (padded_mantissa & !mask) | (stepped & mask);
}
let mant_be = padded_mantissa.to_be_bytes();
debug_assert!(
mant_be[..16 - MANTISSA_BYTES].iter().all(|&b| b == 0),
"padded mantissa overflowed 104 bits",
);
let mant_field = &mant_be[16 - MANTISSA_BYTES..];
let neg_mask = (raw_mantissa >> 127) as u8;
out[0] = (SIGN_BIT & !neg_mask) | (biased_exp ^ (neg_mask & EXP_MASK));
for (i, &b) in mant_field.iter().enumerate() {
out[1 + i] = b ^ neg_mask;
}
let mant_nonzero_bit = ((abs_mantissa | abs_mantissa.wrapping_neg()) >> 127) as u8;
let zero_mask = 0u8.wrapping_sub(mant_nonzero_bit ^ 1);
out[0] = (out[0] & !zero_mask) | (SIGN_BIT & zero_mask);
out
}
}
const INV5: u128 = 0xCCCC_CCCC_CCCC_CCCC_CCCC_CCCC_CCCC_CCCD;
const MAX_DIV_5: u128 = u128::MAX / 5;
fn strip_trailing_zeros(mut mantissa: u128) -> (u128, i32) {
let mut exponent: i32 = 0;
for _ in 0..PADDED_DIGITS {
let div = (mantissa >> 1).wrapping_mul(INV5);
let q5 = mantissa.wrapping_mul(INV5);
let (_, borrow) = MAX_DIV_5.overflowing_sub(q5);
let div_by_5 = (borrow as u128) ^ 1; let div_by_2 = (mantissa & 1) ^ 1; let div_by_10 = div_by_5 & div_by_2;
let mantissa_nz = (mantissa | mantissa.wrapping_neg()) >> 127;
let do_strip = mantissa_nz & div_by_10;
let mask = 0u128.wrapping_sub(do_strip);
mantissa = (div & mask) | (mantissa & !mask);
exponent = exponent.wrapping_add(do_strip as i32);
}
(mantissa, exponent)
}
fn digit_count(m: u128) -> u32 {
let mut n: u32 = 0;
let mut pow: u128 = 1; for _ in 0..PADDED_DIGITS {
let (_, borrow) = m.overflowing_sub(pow);
n = n.wrapping_add((borrow as u32) ^ 1);
pow = pow.wrapping_mul(10);
}
n
}
#[cfg(test)]
mod tests {
use super::*;
use rust_decimal_macros::dec;
#[test]
fn zero_canonicalises_to_sign_bit_only() {
let mut expected = [0u8; 14];
expected[0] = SIGN_BIT;
assert_eq!(dec!(0).to_orderable_bytes(), expected);
assert_eq!(dec!(0.0).to_orderable_bytes(), expected);
assert_eq!(dec!(0.000).to_orderable_bytes(), expected);
}
#[test]
fn negative_zero_canonicalises_with_zero() {
let neg_zero = -dec!(0);
assert_eq!(neg_zero.to_orderable_bytes(), dec!(0).to_orderable_bytes());
}
#[test]
fn equivalent_forms_canonicalise_identically() {
let one = dec!(1).to_orderable_bytes();
assert_eq!(dec!(1.0).to_orderable_bytes(), one);
assert_eq!(dec!(1.00).to_orderable_bytes(), one);
assert_eq!(dec!(1.000).to_orderable_bytes(), one);
}
#[test]
fn integer_trailing_zeros_share_significand_bytes() {
let one = dec!(1).to_orderable_bytes();
let hundred = dec!(100).to_orderable_bytes();
assert_eq!(&one[1..], &hundred[1..]);
assert_eq!(one[0] & SIGN_BIT, SIGN_BIT);
assert_eq!(hundred[0] & SIGN_BIT, SIGN_BIT);
assert_eq!(one[0] & EXP_MASK, EXP_BIAS as u8);
assert_eq!(hundred[0] & EXP_MASK, (2i32 + EXP_BIAS) as u8);
}
#[test]
fn worked_positive_examples() {
let one = dec!(1).to_orderable_bytes();
assert_eq!(one[0], SIGN_BIT | (EXP_BIAS as u8));
let half = dec!(0.5).to_orderable_bytes();
assert_eq!(half[0], SIGN_BIT | ((-1i32 + EXP_BIAS) as u8));
let ten = dec!(10).to_orderable_bytes();
assert_eq!(ten[0], SIGN_BIT | ((1i32 + EXP_BIAS) as u8));
}
#[test]
fn worked_negative_examples() {
let neg_one = dec!(-1).to_orderable_bytes();
let pos_one = dec!(1).to_orderable_bytes();
assert_eq!(neg_one[0] & SIGN_BIT, 0);
assert_eq!(neg_one[0] & EXP_MASK, !(EXP_BIAS as u8) & EXP_MASK);
for i in 1..<Decimal as ToOrderableBytes>::ENCODED_LEN {
assert_eq!(neg_one[i], !pos_one[i]);
}
}
#[test]
fn to_orderable_bytes_byte_order_matches_decimal_order() {
let values = [
Decimal::MIN,
dec!(-1000000000),
dec!(-1.5),
dec!(-1.05),
dec!(-1),
dec!(-0.001),
dec!(0),
dec!(0.001),
dec!(1),
dec!(1.05),
dec!(1.5),
dec!(1000000000),
Decimal::MAX,
];
for window in values.windows(2) {
let a = window[0].to_orderable_bytes();
let b = window[1].to_orderable_bytes();
assert!(
a < b,
"to_orderable_bytes({}) < to_orderable_bytes({}) failed",
window[0],
window[1]
);
}
}
#[test]
fn strip_zero_returns_zero_zero() {
assert_eq!(strip_trailing_zeros(0), (0, 0));
}
#[test]
fn strip_single_digits_are_canonical() {
for d in 1u128..=9 {
assert_eq!(strip_trailing_zeros(d), (d, 0), "input {d}");
}
}
#[test]
fn strip_powers_of_ten_collapse_to_one() {
for k in 0..=28u32 {
let m = 10u128.pow(k);
assert_eq!(strip_trailing_zeros(m), (1, k as i32), "input 10^{k}");
}
}
#[test]
fn strip_u96_max_has_no_trailing_zeros() {
let u96_max = (1u128 << 96) - 1;
assert_eq!(strip_trailing_zeros(u96_max), (u96_max, 0));
}
#[test]
fn strip_mixed_value_extracts_trailing_zero_count() {
assert_eq!(strip_trailing_zeros(12_340_000), (1234, 4));
assert_eq!(strip_trailing_zeros(50), (5, 1));
assert_eq!(strip_trailing_zeros(100_500), (1005, 2));
}
fn u96_of(m: u128) -> u128 {
m & ((1u128 << 96) - 1)
}
quickcheck! {
fn prop_strip_reconstructs(m: u128) -> bool {
let m = u96_of(m);
let (s, c) = strip_trailing_zeros(m);
s.wrapping_mul(10u128.pow(c as u32)) == m
}
fn prop_strip_result_is_canonical(m: u128) -> bool {
let m = u96_of(m);
let (s, _) = strip_trailing_zeros(m);
s == 0 || s % 10 != 0
}
fn prop_strip_count_bounded(m: u128) -> bool {
let m = u96_of(m);
let (_, c) = strip_trailing_zeros(m);
(0..=28).contains(&c)
}
fn prop_strip_zero_preserving(m: u128) -> bool {
let m = u96_of(m);
let (s, _) = strip_trailing_zeros(m);
(m == 0) == (s == 0)
}
fn prop_strip_idempotent(m: u128) -> bool {
let m = u96_of(m);
let (s, _) = strip_trailing_zeros(m);
strip_trailing_zeros(s) == (s, 0)
}
fn prop_strip_distributes_over_mul_by_pow_ten(m: u128, k: u8) -> quickcheck::TestResult {
let m = u96_of(m);
let (s, _) = strip_trailing_zeros(m);
if s == 0 {
return quickcheck::TestResult::discard();
}
let k = (k as u32) % 29; let extended = match s.checked_mul(10u128.pow(k)) {
Some(v) if v < (1u128 << 96) => v,
_ => return quickcheck::TestResult::discard(),
};
quickcheck::TestResult::from_bool(strip_trailing_zeros(extended) == (s, k as i32))
}
}
}