Skip to content

Commit

Permalink
Auto rounding of scales 29, 30 and 31 for deserialization (#436)
Browse files Browse the repository at this point in the history
  • Loading branch information
paupino committed Oct 15, 2021
1 parent 67c29ef commit 980c987
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 14 deletions.
20 changes: 16 additions & 4 deletions src/decimal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -697,17 +697,29 @@ impl Decimal {
// Bits 16-23: Contains "e", a value between 0-28 that indicates the scale
// Bits 24-30: unused
// Bit 31: the sign of the Decimal value, 0 meaning positive and 1 meaning negative.
let raw = Decimal {
let mut raw = Decimal {
flags: ((bytes[0] as u32) | (bytes[1] as u32) << 8 | (bytes[2] as u32) << 16 | (bytes[3] as u32) << 24)
& 0x801F_0000,
lo: (bytes[4] as u32) | (bytes[5] as u32) << 8 | (bytes[6] as u32) << 16 | (bytes[7] as u32) << 24,
mid: (bytes[8] as u32) | (bytes[9] as u32) << 8 | (bytes[10] as u32) << 16 | (bytes[11] as u32) << 24,
hi: (bytes[12] as u32) | (bytes[13] as u32) << 8 | (bytes[14] as u32) << 16 | (bytes[15] as u32) << 24,
};
// Scale must be bound to maximum precision. The panic currently prevents this function being
// marked as a const function.
// Scale must be bound to maximum precision. Only two values can be greater than this
if raw.scale() > MAX_PRECISION_U32 {
panic!("{}", Error::ScaleExceedsMaximumPrecision(raw.scale()))
let mut bits = raw.mantissa_array3();
let remainder = match raw.scale() {
29 => crate::ops::array::div_by_1x(&mut bits, 1),
30 => crate::ops::array::div_by_1x(&mut bits, 2),
31 => crate::ops::array::div_by_1x(&mut bits, 3),
_ => 0,
};
if remainder >= 5 {
ops::array::add_one_internal(&mut bits);
}
raw.lo = bits[0];
raw.mid = bits[1];
raw.hi = bits[2];
raw.flags = flags(raw.is_sign_negative(), MAX_PRECISION_U32);
}
raw
}
Expand Down
17 changes: 16 additions & 1 deletion src/ops/array.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::constants::U32_MASK;
use crate::constants::{POWERS_10, U32_MASK};

/// Rescales the given decimal to new scale.
/// e.g. with 1.23 and new scale 3 rescale the value to 1.230
Expand Down Expand Up @@ -209,6 +209,21 @@ pub(crate) fn div_by_u32(bits: &mut [u32], divisor: u32) -> u32 {
}
}

pub(crate) fn div_by_1x(bits: &mut [u32; 3], power: usize) -> u32 {
let mut remainder = 0u32;
let divisor = POWERS_10[power] as u64;
let temp = ((remainder as u64) << 32) + (bits[2] as u64);
remainder = (temp % divisor) as u32;
bits[2] = (temp / divisor) as u32;
let temp = ((remainder as u64) << 32) + (bits[1] as u64);
remainder = (temp % divisor) as u32;
bits[1] = (temp / divisor) as u32;
let temp = ((remainder as u64) << 32) + (bits[0] as u64);
remainder = (temp % divisor) as u32;
bits[0] = (temp / divisor) as u32;
remainder
}

#[inline]
pub(crate) fn shl1_internal(bits: &mut [u32], carry: u32) -> u32 {
let mut carry = carry;
Expand Down
36 changes: 27 additions & 9 deletions tests/decimal_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,19 +133,37 @@ fn it_can_serialize_deserialize() {
}

#[test]
#[should_panic(expected = "Scale exceeds the maximum precision allowed: 30 > 28")]
fn it_panics_deserializing_unbounded_values() {
let _ = Decimal::deserialize([1u8, 0, 30, 206, 97, 81, 216, 182, 20, 30, 165, 78, 18, 155, 169, 62]);
}

#[test]
fn it_can_deserialize_bounded_values() {
let tests = [[1u8, 0, 28, 206, 97, 81, 216, 182, 20, 30, 165, 78, 18, 155, 169, 62]];
for &bytes in &tests {
fn it_can_deserialize_unbounded_values() {
// Mantissa for these: 19393111376951473493673267553
let tests = [
(
[1u8, 0, 28, 206, 97, 81, 216, 182, 20, 30, 165, 78, 18, 155, 169, 62],
// Scale 28: -1.9393111376951473493673267553
"-1.9393111376951473493673267553",
),
(
[1u8, 0, 29, 206, 97, 81, 216, 182, 20, 30, 165, 78, 18, 155, 169, 62],
// Scale 29: -0.19393111376951473493673267553
"-0.1939311137695147349367326755",
),
(
[1u8, 0, 30, 206, 97, 81, 216, 182, 20, 30, 165, 78, 18, 155, 169, 62],
// Scale 30: -0.019393111376951473493673267553
"-0.0193931113769514734936732676",
),
(
[1u8, 0, 31, 206, 97, 81, 216, 182, 20, 30, 165, 78, 18, 155, 169, 62],
// Scale 31: -0.0019393111376951473493673267553
"-0.0019393111376951473493673268",
),
];
for &(bytes, expected) in &tests {
let dec = Decimal::deserialize(bytes);
let string = format!("{:.9999}", dec);
let dec2 = Decimal::from_str(&string).unwrap();
assert_eq!(dec, dec2);
assert_eq!(dec.to_string(), expected, "dec.to_string()");
assert_eq!(dec2.to_string(), expected, "dec2.to_string()");
}
}

Expand Down

0 comments on commit 980c987

Please sign in to comment.