Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fixed postgres serialisation, deserialisation of decimal #243

Merged
merged 10 commits into from
May 20, 2020
198 changes: 138 additions & 60 deletions src/decimal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const SCALE_SHIFT: u32 = 16;
const SIGN_SHIFT: u32 = 31;

// The maximum supported precision
const MAX_PRECISION: u32 = 28;
pub(crate) const MAX_PRECISION: u32 = 28;
// 79,228,162,514,264,337,593,543,950,335
const MAX_I128_REPR: i128 = 0x0000_0000_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF;

Expand Down Expand Up @@ -381,6 +381,26 @@ impl Decimal {
Ok(())
}

/// Change scale of decimal number, without changing number itself
///
/// > Note that setting scale which is less then current, cause number Bankers Rounding
///
/// # Example
///
/// ```
/// use rust_decimal::Decimal;
///
/// let number = Decimal::new(1_123, 3).rescale(6);
/// assert_eq!(number, Decimal::new(1_123_000, 6));
/// ```
pub fn rescale(self, scale: u32) -> Self {
let mut array = [self.lo, self.mid, self.hi];
let mut value_scale = self.scale();
let negative = self.is_sign_negative();
inner_rescale(&mut array, &mut value_scale, scale);
Decimal::from_parts(array[0], array[1], array[2], negative, value_scale)
}

/// Returns a serialized version of the decimal number.
/// The resulting byte array will have the following representation:
///
Expand Down Expand Up @@ -1015,7 +1035,7 @@ impl Decimal {
let mut my_scale = self.scale();
let mut ot = [other.lo, other.mid, other.hi];
let mut other_scale = other.scale();
rescale(&mut my, &mut my_scale, &mut ot, &mut other_scale);
try_rescale_to_maximum(&mut my, &mut my_scale, &mut ot, &mut other_scale);
let mut final_scale = my_scale.max(other_scale);

// Add the items together
Expand Down Expand Up @@ -1414,7 +1434,7 @@ impl Decimal {
let mut quotient_scale = initial_scale;
let mut divisor = [other.lo, other.mid, other.hi];
let mut divisor_scale = other.scale();
rescale(&mut quotient, &mut quotient_scale, &mut divisor, &mut divisor_scale);
try_rescale_to_maximum(&mut quotient, &mut quotient_scale, &mut divisor, &mut divisor_scale);

// Working is the remainder + the quotient
// We use an aligned array since we'll be using it a lot.
Expand Down Expand Up @@ -1472,7 +1492,7 @@ const fn flags(neg: bool, scale: u32) -> u32 {
/// will try to reduce the accuracy of the other argument.
/// e.g. with 1.23 and 2.345 it'll rescale the first arg to 1.230
#[inline(always)]
fn rescale(left: &mut [u32; 3], left_scale: &mut u32, right: &mut [u32; 3], right_scale: &mut u32) {
fn try_rescale_to_maximum(left: &mut [u32; 3], left_scale: &mut u32, right: &mut [u32; 3], right_scale: &mut u32) {
if left_scale == right_scale {
// Nothing to do
return;
Expand All @@ -1486,73 +1506,69 @@ fn rescale(left: &mut [u32; 3], left_scale: &mut u32, right: &mut [u32; 3], righ
return;
}

enum Target {
Left,
Right,
}

let target; // The target which we're aiming for
let mut diff;
let my;
let other;
if left_scale > right_scale {
diff = *left_scale - *right_scale;
my = right;
other = left;
target = Target::Left;
inner_rescale(right, right_scale, *left_scale);
if right_scale != left_scale {
inner_rescale(left, left_scale, *right_scale);
}
} else {
diff = *right_scale - *left_scale;
my = left;
other = right;
target = Target::Right;
};

let mut working = [my[0], my[1], my[2]];
while diff > 0 && mul_by_10(&mut working) == 0 {
my.copy_from_slice(&working);
diff -= 1;
inner_rescale(left, left_scale, *right_scale);
if right_scale != left_scale {
inner_rescale(right, right_scale, *left_scale);
}
}
}

match target {
Target::Left => {
*left_scale -= diff;
*right_scale = *left_scale;
}
Target::Right => {
*right_scale -= diff;
*left_scale = *right_scale;
}
/// Rescales the given decimal to new scale.
/// e.g. with 1.23 and new scale 3 rescale the value to 1.230
#[inline(always)]
pub fn inner_rescale(value: &mut [u32; 3], value_scale: &mut u32, new_scale: u32) {
if *value_scale == new_scale {
// Nothing to do
return;
}

if diff == 0 {
// We're done - same scale
if is_all_zero(value) {
return;
}

// Scaling further isn't possible since we got an overflow
// In this case we need to reduce the accuracy of the "side to keep"
if *value_scale > new_scale {
let mut diff = *value_scale - new_scale;
// Scaling further isn't possible since we got an overflow
// In this case we need to reduce the accuracy of the "side to keep"

// Now do the necessary rounding
let mut remainder = 0;
while diff > 0 {
if is_all_zero(other) {
return;
}
// Now do the necessary rounding
let mut remainder = 0;
while diff > 0 {
if is_all_zero(value) {
*value_scale = new_scale;
return;
}

diff -= 1;
diff -= 1;

// Any remainder is discarded if diff > 0 still (i.e. lost precision)
remainder = div_by_10(other);
}
if remainder >= 5 {
for part in other.iter_mut() {
let digit = u64::from(*part) + 1u64;
remainder = if digit > 0xFFFF_FFFF { 1 } else { 0 };
*part = (digit & 0xFFFF_FFFF) as u32;
if remainder == 0 {
break;
// Any remainder is discarded if diff > 0 still (i.e. lost precision)
remainder = div_by_10(value);
}
if remainder >= 5 {
for part in value.iter_mut() {
let digit = u64::from(*part) + 1u64;
remainder = if digit > 0xFFFF_FFFF { 1 } else { 0 };
*part = (digit & 0xFFFF_FFFF) as u32;
if remainder == 0 {
break;
}
}
}
*value_scale = new_scale;
} else {
let mut diff = new_scale - *value_scale;
let mut working = [value[0], value[1], value[2]];
while diff > 0 && mul_by_10(&mut working) == 0 {
value.copy_from_slice(&working);
diff -= 1;
}
*value_scale = new_scale - diff;
}
}

Expand Down Expand Up @@ -2927,7 +2943,7 @@ impl Ord for Decimal {
// Rescale and compare
let mut left_raw = [left.lo, left.mid, left.hi];
let mut right_raw = [right.lo, right.mid, right.hi];
rescale(&mut left_raw, &mut left_scale, &mut right_raw, &mut right_scale);
try_rescale_to_maximum(&mut left_raw, &mut left_scale, &mut right_raw, &mut right_scale);
cmp_internal(&left_raw, &right_raw)
}
}
Expand Down Expand Up @@ -3000,7 +3016,7 @@ mod test {

let (mut left, mut left_scale) = extract(left_raw);
let (mut right, mut right_scale) = extract(right_raw);
rescale(&mut left, &mut left_scale, &mut right, &mut right_scale);
try_rescale_to_maximum(&mut left, &mut left_scale, &mut right, &mut right_scale);
assert_eq!(left, expected_left);
assert_eq!(left_scale, expected_lscale);
assert_eq!(right, expected_right);
Expand All @@ -3009,11 +3025,73 @@ mod test {
// Also test the transitive case
let (mut left, mut left_scale) = extract(left_raw);
let (mut right, mut right_scale) = extract(right_raw);
rescale(&mut right, &mut right_scale, &mut left, &mut left_scale);
try_rescale_to_maximum(&mut right, &mut right_scale, &mut left, &mut left_scale);
assert_eq!(left, expected_left);
assert_eq!(left_scale, expected_lscale);
assert_eq!(right, expected_right);
assert_eq!(right_scale, expected_rscale);
}
}

#[test]
fn it_can_inner_rescale() {
fn extract(value: &str) -> ([u32; 3], u32) {
let v = Decimal::from_str(value).unwrap();
([v.lo, v.mid, v.hi], v.scale())
}

let tests = &[
("1", 0, "1"),
("1", 1, "1.0"),
("1", 5, "1.00000"),
("1", 10, "1.0000000000"),
("1", 20, "1.00000000000000000000"),
("0.6386554621848739495798319328", 27, "0.638655462184873949579831933"),
(
"843.65000000", // Scale 8
25, // 25
"843.6500000000000000000000000", // 25
),
(
"843.65000000", // Scale 8
30, // 30
"843.6500000000000000000000000000", // 28
),
];

for &(value_raw, new_scale, expected_value) in tests {
let (expected_value, _) = extract(expected_value);
let (mut value, mut value_scale) = extract(value_raw);
inner_rescale(&mut value, &mut value_scale, new_scale);
assert_eq!(value, expected_value);
}
}

#[test]
fn test_rescale() {
fn extract(value: &str) -> Decimal {
Decimal::from_str(value).unwrap()
}

let tests = &[
("0.12345600000", 6, "0.123456"),
("0.123456", 12, "0.123456000000"),
("0.123456", 0, "0"),
("0.000001", 4, "0.0000"),
("1233456", 4, "1233456.0000"),
("1.2", 30, "1.2000000000000000000000000000"),
("79228162514264337593543950335", 0, "79228162514264337593543950335"),
("4951760157141521099596496895", 1, "4951760157141521099596496895.0"),
("4951760157141521099596496896", 1, "4951760157141521099596496896.0"),
("18446744073709551615", 6, "18446744073709551615.000000"),
("-18446744073709551615", 6, "-18446744073709551615.000000"),
];

for &(value_raw, new_scale, expected_value) in tests {
let new_value = extract(expected_value);
let value = extract(value_raw);
let value = value.rescale(new_scale);
assert_eq!(new_value.to_string(), value.to_string());
}
}
}
Loading