From ac1e409916616983b24838227747dfe21881cfc3 Mon Sep 17 00:00:00 2001 From: serejkaaa512 Date: Thu, 14 May 2020 12:43:23 +0300 Subject: [PATCH 1/9] fixed postgres serialisation, deserialisation of decimal --- src/decimal.rs | 47 +++++++++ src/postgres.rs | 254 ++++++++++++++++++++++++++---------------------- 2 files changed, 187 insertions(+), 114 deletions(-) diff --git a/src/decimal.rs b/src/decimal.rs index 706c0159..0c4990e3 100644 --- a/src/decimal.rs +++ b/src/decimal.rs @@ -80,6 +80,19 @@ static BIG_POWERS_10: [u64; 10] = [ 1_000_000_000_000_000_000, 10_000_000_000_000_000_000, ]; +// Fast access for 10^n where n is 20-28 +#[allow(dead_code)] +static MAX_POWERS_10: [u128; 9] = [ + 100_000_000_000_000_000_000, + 1_000_000_000_000_000_000_000, + 10_000_000_000_000_000_000_000, + 100_000_000_000_000_000_000_000, + 1_000_000_000_000_000_000_000_000, + 10_000_000_000_000_000_000_000_000, + 100_000_000_000_000_000_000_000_000, + 1_000_000_000_000_000_000_000_000_000, + 10_000_000_000_000_000_000_000_000_000, +]; /// `UnpackedDecimal` contains unpacked representation of `Decimal` where each component /// of decimal-format stored in it's own field @@ -382,6 +395,40 @@ impl Decimal { Ok(()) } + /// Change scale of decimal number, without changing number itself + /// + /// > Note that values bigger then 28 will cause panic + /// > Note that setting scale which is less then current, cause number truncation + /// + /// # Example + /// + /// ``` + /// use rust_decimal::Decimal; + /// + /// let number = Decimal::new(1_123, 3).with_scale(6); + /// assert_eq!(number, Decimal::new(1_123_000, 6)); + /// ``` + pub fn with_scale(self, scale: u32) -> Self { + let unpacked = self.unpack(); + + match scale { + scale if scale > unpacked.scale => { + let scale_diff = scale - unpacked.scale; + let mul = match scale_diff { + 0..=9 => Decimal::from_u32(POWERS_10[scale_diff as usize]).unwrap(), + 10..=19 => Decimal::from_u64(BIG_POWERS_10[scale_diff as usize - 10]).unwrap(), + 20..=MAX_PRECISION => Decimal::from_u128(MAX_POWERS_10[scale_diff as usize - 20]).unwrap(), + _ => unreachable!(), + }; + let mut result = self * mul; + result.set_scale(scale).unwrap(); + result + } + scale if scale < unpacked.scale => self.round_dp_with_strategy(scale, RoundingStrategy::RoundDown), + _ => self, + } + } + /// Returns a serialized version of the decimal number. /// The resulting byte array will have the following representation: /// diff --git a/src/postgres.rs b/src/postgres.rs index e18c6eba..96c76310 100644 --- a/src/postgres.rs +++ b/src/postgres.rs @@ -6,36 +6,6 @@ use std::{convert::TryInto, error, fmt, result::*}; use crate::decimal::{div_by_u32, is_all_zero, mul_by_u32}; -const DECIMALS: [Decimal; 15] = [ - Decimal::from_parts(1, 0, 0, false, 28), - Decimal::from_parts(1, 0, 0, false, 24), - Decimal::from_parts(1, 0, 0, false, 20), - Decimal::from_parts(1, 0, 0, false, 16), - Decimal::from_parts(1, 0, 0, false, 12), - Decimal::from_parts(1, 0, 0, false, 8), - Decimal::from_parts(1, 0, 0, false, 4), - Decimal::from_parts(1, 0, 0, false, 0), - Decimal::from_parts(1_0000, 0, 0, false, 0), - Decimal::from_parts(1_0000_0000, 0, 0, false, 0), - Decimal::from_parts( - 1_0000_0000_0000u64 as u32, - (1_0000_0000_0000u64 >> 32) as u32, - 0, - false, - 0, - ), - Decimal::from_parts( - 1_0000_0000_0000_0000u64 as u32, - (1_0000_0000_0000_0000u64 >> 32) as u32, - 0, - false, - 0, - ), - Decimal::from_parts(1661992960, 1808227885, 5, false, 0), - Decimal::from_parts(2701131776, 466537709, 54210, false, 0), - Decimal::from_parts(268435456, 1042612833, 542101086, false, 0), -]; - #[derive(Debug, Clone, Copy)] pub struct InvalidDecimal; @@ -58,66 +28,45 @@ impl Decimal { fn from_postgres>( PostgresDecimal { neg, - weight, scale, digits, + weight, }: PostgresDecimal, ) -> Result { let mut digits = digits.into_iter().collect::>(); - let mut num_groups = digits.len() as u16; - // Number of digits (in base 10) to print after decimal separator - let fixed_scale = scale as i32; - // If we're greater than 8 groups then we have a higher precision than Decimal can represent. - // We limit this here. We also round up if the value AFTER our cutoff is over 5. - const MAX_GROUP_COUNT: usize = 8; - if num_groups as usize > MAX_GROUP_COUNT { - num_groups = MAX_GROUP_COUNT as u16; - if digits[MAX_GROUP_COUNT] >= 5000 { - digits[MAX_GROUP_COUNT - 1] += 1; - } - } - // Read all of the groups - let mut groups = digits - .into_iter() - .take(num_groups as usize) - .map(|d| Decimal::new(d as i64, 0)) - .collect::>(); - groups.reverse(); + let fractionals_part_count = digits.len() as i32 + (-weight as i32) - 1; + let integers_part_count = weight as i32 + 1; - // Now process the number let mut result = Decimal::zero(); - for (index, group) in groups.iter().enumerate() { - result = result + (DECIMALS[index + 7] * group); - } - - // Finally, adjust for the scale - let mut scale = (num_groups as i16 - weight - 1) as i32 * 4; - // Scale could be negative - if scale < 0 { - result *= Decimal::from_i128_with_scale(10i128.pow((-scale) as u32), 0); - scale = 0; - } else if scale > fixed_scale { - // Remove trailing zeroes - result /= Decimal::from_i128_with_scale(10i128.pow((scale - fixed_scale) as u32), 0); - scale = fixed_scale; - } else if scale < fixed_scale { - // Since we're only adding trailing zeros we only add as many as feasibly represented - let mut max_scale = fixed_scale; - if max_scale > 28 { - max_scale = 28; + // adding integer part + if integers_part_count > 0 { + let (start_integers, last) = if integers_part_count > digits.len() as i32 { + (integers_part_count - digits.len() as i32, digits.len() as i32) + } else { + (0, integers_part_count) + }; + let integers: Vec<_> = digits.drain(..last as usize).collect(); + for digit in integers { + result *= Decimal::from_i128_with_scale(10i128.pow(4), 0); + result += Decimal::new(digit as i64, 0); + } + result *= Decimal::from_i128_with_scale(10i128.pow(4 * start_integers as u32), 0); + } + // adding fractional part + if fractionals_part_count > 0 { + let dec: Vec<_> = digits.into_iter().collect(); + let start_fractionals = if weight < 0 { (-weight as u32) - 1 } else { 0 }; + for (i, digit) in dec.into_iter().enumerate() { + result += Decimal::new(digit as i64, 0) + / Decimal::from_i128_with_scale(10i128.pow(4 * (i as u32 + 1 + start_fractionals)), 0); } - result *= Decimal::from_i128_with_scale(10i128.pow((max_scale - scale) as u32), 0); - scale = max_scale; } - // Create the decimal - if result.set_scale(scale as u32).is_err() { - return Err(InvalidDecimal); - } result.set_sign_negative(neg); + // Setting scale frm PG value. + result = result.with_scale(scale as u32); - // Retain trailing zeroes. Ok(result) } @@ -133,8 +82,6 @@ impl Decimal { let scale = self.scale() as u16; let groups_diff = scale & 0x3; // groups_diff = scale % 4 - let mut fractional_groups_count = (scale >> 2) as isize; // fractional_groups_count = scale / 4 - fractional_groups_count += if groups_diff > 0 { 1 } else { 0 }; let mut mantissa = self.mantissa_array4(); @@ -153,13 +100,23 @@ impl Decimal { digits.push(digit.try_into().unwrap()); } digits.reverse(); - - let whole_portion_len = digits.len() as isize - fractional_groups_count; - let weight = if whole_portion_len < 0 { - -(fractional_groups_count as i16) + let digits_after_decimal = (scale + 3) as u16 / 4; + let weight = digits.len() as i16 - digits_after_decimal as i16 - 1; + + let unnecessary_zeroes = if weight >= 0 { + let index_of_decimal = (weight + 1) as usize; + digits + .get(index_of_decimal..) + .expect("enough digits exist") + .iter() + .rev() + .take_while(|i| **i == 0) + .count() } else { - whole_portion_len as i16 - 1 + 0 }; + let relevant_digits = digits.len() - unnecessary_zeroes; + digits.truncate(relevant_digits); PostgresDecimal { neg: self.is_sign_negative(), @@ -210,7 +167,7 @@ mod diesel { scale, digits: digits.iter().copied().map(|v| v.try_into().unwrap()), }) - .map_err(Box::new)?) + .map_err(Box::new)?) } } @@ -266,10 +223,92 @@ mod diesel { } #[cfg(test)] - mod tests { + mod pg_tests { use super::*; use std::str::FromStr; + #[test] + fn test_unnecessary_zeroes() { + let value = Decimal::from_str("0.000001660").unwrap(); + let pg = PgNumeric::from(value); + let dec = Decimal::try_from(pg).unwrap(); + assert_eq!(dec, value); + + let value = Decimal::from_str("41.120255926293").unwrap(); + let pg = PgNumeric::from(value); + let dec = Decimal::try_from(pg).unwrap(); + assert_eq!(dec, value); + + let value = Decimal::from_str("0.55389733").unwrap(); + let pg = PgNumeric::from(value); + let dec = Decimal::try_from(pg).unwrap(); + assert_eq!(dec, value); + + let value = Decimal::from_str("8883.559868542931").unwrap(); + let pg = PgNumeric::from(value); + let dec = Decimal::try_from(pg).unwrap(); + assert_eq!(dec, value); + + let value = Decimal::from_str("0.0000_0000_0016_6000_00").unwrap(); + let pg = PgNumeric::from(value); + let dec = Decimal::try_from(pg).unwrap(); + assert_eq!(dec, value); + + let value = Decimal::from_str("0.00000166650000").unwrap(); + let pg = PgNumeric::from(value); + let dec = Decimal::try_from(pg).unwrap(); + assert_eq!(dec.to_string(), "0.00000166650000".to_string()); + + let value = Decimal::from_str("1666500000000").unwrap(); + let pg = PgNumeric::from(value); + let dec = Decimal::try_from(pg).unwrap(); + assert_eq!(dec.to_string(), "1666500000000".to_string()); + + let value = Decimal::from_str("1666500000000.00000545").unwrap(); + let pg = PgNumeric::from(value); + let dec = Decimal::try_from(pg).unwrap(); + assert_eq!(dec.to_string(), "1666500000000.00000545".to_string()); + + let value = Decimal::from_str("8944.000000000000").unwrap(); + let pg = PgNumeric::from(value); + let dec = Decimal::try_from(pg).unwrap(); + assert_eq!(dec.to_string(), "8944.000000000000".to_string()); + } + + #[test] + fn test_with_scale() { + let value = Decimal::from_str("0.024537600000").unwrap(); + let from_value = Decimal::from_str("0.40000000").unwrap(); + let to_value = Decimal::from_str("16.30151278").unwrap(); + let new_value = (from_value / to_value).with_scale(12); + assert_eq!(new_value.to_string(), value.to_string()); + + let value = Decimal::from_str("0.12345600000").unwrap(); + let new_value = Decimal::from_str("0.123456").unwrap(); + let value = value.with_scale(6); + assert_eq!(new_value.to_string(), value.to_string()); + + let value = Decimal::from_str("0.123456").unwrap(); + let new_value = Decimal::from_str("0.123456000000").unwrap(); + let value = value.with_scale(12); + assert_eq!(new_value.to_string(), value.to_string()); + + let value = Decimal::from_str("0.123456").unwrap(); + let new_value = Decimal::from_str("0").unwrap(); + let value = value.with_scale(0); + assert_eq!(new_value.to_string(), value.to_string()); + + let value = Decimal::from_str("0.000001").unwrap(); + let new_value = Decimal::from_str("0.0000").unwrap(); + let value = value.with_scale(4); + assert_eq!(new_value.to_string(), value.to_string()); + + let value = Decimal::from_str("1233456").unwrap(); + let new_value = Decimal::from_str("1233456.0000").unwrap(); + let value = value.with_scale(4); + assert_eq!(new_value.to_string(), value.to_string()); + } + #[test] fn decimal_to_pgnumeric_converts_digits_to_base_10000() { let decimal = Decimal::from_str("1").unwrap(); @@ -327,7 +366,7 @@ mod diesel { let expected = PgNumeric::Positive { weight: 0, scale: 1, - digits: vec![1, 0], + digits: vec![1], }; assert_eq!(expected, decimal.into()); @@ -423,12 +462,22 @@ mod diesel { let res: Decimal = pg_numeric.try_into().unwrap(); assert_eq!(res.to_string(), expected.to_string()); + // To represent 5.00, Postgres can return either [5, 0] as the list of digits. + let expected = Decimal::from_str("5.00").unwrap(); + let pg_numeric = PgNumeric::Positive { + weight: 0, + scale: 2, + + digits: vec![5, 0], + }; + let res: Decimal = pg_numeric.try_into().unwrap(); + assert_eq!(res.to_string(), expected.to_string()); + + // To represent 5.00, Postgres can return [5] as the list of digits. let expected = Decimal::from_str("5.00").unwrap(); let pg_numeric = PgNumeric::Positive { weight: 0, scale: 2, - // To represent 5.00, Postgres can return either [5, 0] or just [5] - // as the list of digits. Verify this crate handles the shortened list correctly. digits: vec![5], }; let res: Decimal = pg_numeric.try_into().unwrap(); @@ -504,7 +553,7 @@ mod postgres { let mut raw = Cursor::new(raw); let num_groups = raw.read_u16::()?; let weight = raw.read_i16::()?; // 10000^weight - // Sign: 0x0000 = positive, 0x4000 = negative, 0xC000 = NaN + // Sign: 0x0000 = positive, 0x4000 = negative, 0xC000 = NaN let sign = raw.read_u16::()?; // Number of digits (in base 10) to print after decimal separator let scale = raw.read_u16::()?; @@ -521,7 +570,7 @@ mod postgres { scale, digits: groups.into_iter(), }) - .map_err(Box::new)?) + .map_err(Box::new)?) } fn accepts(ty: &Type) -> bool { @@ -654,29 +703,6 @@ mod postgres { (35, 6, "0.12345", "0.123450"), ]; - #[test] - fn ensure_equivalent_decimal_constants() { - let expected_decimals = [ - Decimal::new(1, 28), - Decimal::new(1, 24), - Decimal::new(1, 20), - Decimal::new(1, 16), - Decimal::new(1, 12), - Decimal::new(1, 8), - Decimal::new(1, 4), - Decimal::new(1, 0), - Decimal::new(10000, 0), - Decimal::new(100000000, 0), - Decimal::new(1000000000000, 0), - Decimal::new(10000000000000000, 0), - Decimal::from_parts(1661992960, 1808227885, 5, false, 0), - Decimal::from_parts(2701131776, 466537709, 54210, false, 0), - Decimal::from_parts(268435456, 1042612833, 542101086, false, 0), - ]; - - assert_eq!(&expected_decimals[..], &DECIMALS[..]); - } - #[test] fn test_null() { let mut client = match Client::connect(&get_postgres_url(), NoTls) { From cccf1daff6f7a3ef8b6de3ec8042e30739326f8b Mon Sep 17 00:00:00 2001 From: serejkaaa512 Date: Thu, 14 May 2020 12:58:49 +0300 Subject: [PATCH 2/9] fixed returned from postgres test value --- src/postgres.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/postgres.rs b/src/postgres.rs index 96c76310..50cc90c8 100644 --- a/src/postgres.rs +++ b/src/postgres.rs @@ -167,7 +167,7 @@ mod diesel { scale, digits: digits.iter().copied().map(|v| v.try_into().unwrap()), }) - .map_err(Box::new)?) + .map_err(Box::new)?) } } @@ -553,7 +553,7 @@ mod postgres { let mut raw = Cursor::new(raw); let num_groups = raw.read_u16::()?; let weight = raw.read_i16::()?; // 10000^weight - // Sign: 0x0000 = positive, 0x4000 = negative, 0xC000 = NaN + // Sign: 0x0000 = positive, 0x4000 = negative, 0xC000 = NaN let sign = raw.read_u16::()?; // Number of digits (in base 10) to print after decimal separator let scale = raw.read_u16::()?; @@ -570,7 +570,7 @@ mod postgres { scale, digits: groups.into_iter(), }) - .map_err(Box::new)?) + .map_err(Box::new)?) } fn accepts(ty: &Type) -> bool { @@ -660,7 +660,7 @@ mod postgres { (35, 6, "-100", "-100.000000"), (35, 6, "-123.456", "-123.456000"), (35, 6, "119996.25", "119996.250000"), - (35, 6, "1000000", "1000000"), + (35, 6, "1000000", "1000000.000000"), (35, 6, "9999999.99999", "9999999.999990"), (35, 6, "12340.56789", "12340.567890"), // Scale is only 28 since that is the maximum we can represent. From f2bc3be98c15b38774dc53a1b94bd2be1ddbb9cb Mon Sep 17 00:00:00 2001 From: serejkaaa512 Date: Thu, 14 May 2020 13:39:46 +0300 Subject: [PATCH 3/9] improved with_scale for MAX PRECISION --- src/decimal.rs | 23 +++++++---------------- src/postgres.rs | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 16 deletions(-) diff --git a/src/decimal.rs b/src/decimal.rs index 0c4990e3..06d77ee7 100644 --- a/src/decimal.rs +++ b/src/decimal.rs @@ -80,19 +80,6 @@ static BIG_POWERS_10: [u64; 10] = [ 1_000_000_000_000_000_000, 10_000_000_000_000_000_000, ]; -// Fast access for 10^n where n is 20-28 -#[allow(dead_code)] -static MAX_POWERS_10: [u128; 9] = [ - 100_000_000_000_000_000_000, - 1_000_000_000_000_000_000_000, - 10_000_000_000_000_000_000_000, - 100_000_000_000_000_000_000_000, - 1_000_000_000_000_000_000_000_000, - 10_000_000_000_000_000_000_000_000, - 100_000_000_000_000_000_000_000_000, - 1_000_000_000_000_000_000_000_000_000, - 10_000_000_000_000_000_000_000_000_000, -]; /// `UnpackedDecimal` contains unpacked representation of `Decimal` where each component /// of decimal-format stored in it's own field @@ -417,11 +404,15 @@ impl Decimal { let mul = match scale_diff { 0..=9 => Decimal::from_u32(POWERS_10[scale_diff as usize]).unwrap(), 10..=19 => Decimal::from_u64(BIG_POWERS_10[scale_diff as usize - 10]).unwrap(), - 20..=MAX_PRECISION => Decimal::from_u128(MAX_POWERS_10[scale_diff as usize - 20]).unwrap(), - _ => unreachable!(), + 20..=MAX_PRECISION => { + Decimal::from_u64(BIG_POWERS_10[0]).unwrap() + * Decimal::from_u64(BIG_POWERS_10[scale_diff as usize - 20]).unwrap() + } + _ => Decimal::from_u64(BIG_POWERS_10[0]).unwrap() * Decimal::from_u64(BIG_POWERS_10[8]).unwrap(), }; let mut result = self * mul; - result.set_scale(scale).unwrap(); + let set_scale = std::cmp::min(MAX_PRECISION, scale); + result.set_scale(set_scale).unwrap(); result } scale if scale < unpacked.scale => self.round_dp_with_strategy(scale, RoundingStrategy::RoundDown), diff --git a/src/postgres.rs b/src/postgres.rs index 50cc90c8..e1e16dd9 100644 --- a/src/postgres.rs +++ b/src/postgres.rs @@ -307,6 +307,39 @@ mod diesel { let new_value = Decimal::from_str("1233456.0000").unwrap(); let value = value.with_scale(4); assert_eq!(new_value.to_string(), value.to_string()); + + let value = Decimal::from_str("1.2").unwrap(); + let new_value = Decimal::from_str("1.2000000000000000000000000000").unwrap(); + let value = value.with_scale(30); + assert_eq!(new_value.to_string(), value.to_string()); + + // 0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF (96 bit) + let value = Decimal::from_str("79228162514264337593543950335").unwrap(); + let new_value = Decimal::from_str("79228162514264337593543950335").unwrap(); + let value = value.with_scale(0); + assert_eq!(new_value.to_string(), value.to_string()); + + // 0x0FFF_FFFF_FFFF_FFFF_FFFF_FFFF (95 bit) + let value = Decimal::from_str("4951760157141521099596496895").unwrap(); + let new_value = Decimal::from_str("4951760157141521099596496895.0").unwrap(); + let value = value.with_scale(1); + assert_eq!(new_value.to_string(), value.to_string()); + + // 0x1000_0000_0000_0000_0000_0000 + let value = Decimal::from_str("4951760157141521099596496896").unwrap(); + let new_value = Decimal::from_str("4951760157141521099596496896.0").unwrap(); + let value = value.with_scale(1); + assert_eq!(new_value.to_string(), value.to_string()); + + let value = Decimal::from_str("18446744073709551615").unwrap(); + let new_value = Decimal::from_str("18446744073709551615.000000").unwrap(); + let value = value.with_scale(6); + assert_eq!(new_value.to_string(), value.to_string()); + + let value = Decimal::from_str("-18446744073709551615").unwrap(); + let new_value = Decimal::from_str("-18446744073709551615.000000").unwrap(); + let value = value.with_scale(6); + assert_eq!(new_value.to_string(), value.to_string()); } #[test] From 127e28d70cb9a5e658278cda2fb3809cb9aee48f Mon Sep 17 00:00:00 2001 From: serejkaaa512 Date: Thu, 14 May 2020 14:06:45 +0300 Subject: [PATCH 4/9] improved PG deserialise for more than MAX PRECISION --- src/decimal.rs | 2 +- src/postgres.rs | 45 ++++++++++++++++++++++++++++++++++++++------- 2 files changed, 39 insertions(+), 8 deletions(-) diff --git a/src/decimal.rs b/src/decimal.rs index 06d77ee7..60b77034 100644 --- a/src/decimal.rs +++ b/src/decimal.rs @@ -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; // 281,474,976,710,655 const MAX_I128_REPR: i128 = 0x0000_0000_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF; diff --git a/src/postgres.rs b/src/postgres.rs index e1e16dd9..98e65e7c 100644 --- a/src/postgres.rs +++ b/src/postgres.rs @@ -4,7 +4,7 @@ use crate::Decimal; use std::{convert::TryInto, error, fmt, result::*}; -use crate::decimal::{div_by_u32, is_all_zero, mul_by_u32}; +use crate::decimal::{div_by_u32, is_all_zero, mul_by_u32, MAX_PRECISION}; #[derive(Debug, Clone, Copy)] pub struct InvalidDecimal; @@ -58,8 +58,10 @@ impl Decimal { let dec: Vec<_> = digits.into_iter().collect(); let start_fractionals = if weight < 0 { (-weight as u32) - 1 } else { 0 }; for (i, digit) in dec.into_iter().enumerate() { - result += Decimal::new(digit as i64, 0) - / Decimal::from_i128_with_scale(10i128.pow(4 * (i as u32 + 1 + start_fractionals)), 0); + let fract_pow = 4 * (i as u32 + 1 + start_fractionals); + if fract_pow <= MAX_PRECISION { + result += Decimal::new(digit as i64, 0) / Decimal::from_i128_with_scale(10i128.pow(fract_pow), 0); + } } } @@ -515,6 +517,35 @@ mod diesel { }; let res: Decimal = pg_numeric.try_into().unwrap(); assert_eq!(res.to_string(), expected.to_string()); + + let expected = Decimal::from_str("3.1415926535897932384626433832").unwrap(); + let pg_numeric = PgNumeric::Positive { + weight: 0, + scale: 30, + digits: vec![3, 1415, 9265, 3589, 7932, 3846, 2643, 3832, 7950, 2800], + }; + let res: Decimal = pg_numeric.try_into().unwrap(); + assert_eq!(res.to_string(), expected.to_string()); + + let expected = Decimal::from_str("3.1415926535897932384626433832").unwrap(); + let pg_numeric = PgNumeric::Positive { + weight: 0, + scale: 34, + digits: vec![3, 1415, 9265, 3589, 7932, 3846, 2643, 3832, 7950, 2800], + }; + + let res: Decimal = pg_numeric.try_into().unwrap(); + assert_eq!(res.to_string(), expected.to_string()); + + let expected = Decimal::from_str("1.2345678901234567890123456789").unwrap(); + let pg_numeric = PgNumeric::Positive { + weight: 0, + scale: 34, + digits: vec![1, 2345, 6789, 0123, 4567, 8901, 2345, 6789, 5000, 0], + }; + + let res: Decimal = pg_numeric.try_into().unwrap(); + assert_eq!(res.to_string(), expected.to_string()); } } } @@ -703,20 +734,20 @@ mod postgres { 65, 30, "3.141592653589793238462643383279", - "3.1415926535897932384626433833", + "3.1415926535897932384626433832", ), ( 65, 34, "3.1415926535897932384626433832795028", - "3.1415926535897932384626433833", + "3.1415926535897932384626433832", ), // Unrounded number ( 65, 34, "1.234567890123456789012345678950000", - "1.2345678901234567890123456790", + "1.2345678901234567890123456789", ), ( 65, @@ -778,7 +809,7 @@ mod postgres { let result: Decimal = match client.query(&*format!("SELECT {}::NUMERIC({}, {})", sent, precision, scale), &[]) { Ok(x) => x.iter().next().unwrap().get(0), - Err(err) => panic!("{:#?}", err), + Err(err) => panic!("SELECT {}::NUMERIC({}, {}), error - {:#?}", sent, precision, scale, err), }; assert_eq!( expected, From 8ef2f38bc67009cc91d0179d758248f01cde97a5 Mon Sep 17 00:00:00 2001 From: serejkaaa512 Date: Thu, 14 May 2020 14:40:20 +0300 Subject: [PATCH 5/9] improved rounding for last digit in values with scale greater than MAX PRECISION in serialisation / deserialisation --- src/decimal.rs | 2 +- src/postgres.rs | 20 +++++++++++++------- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/decimal.rs b/src/decimal.rs index 60b77034..0351b459 100644 --- a/src/decimal.rs +++ b/src/decimal.rs @@ -415,7 +415,7 @@ impl Decimal { result.set_scale(set_scale).unwrap(); result } - scale if scale < unpacked.scale => self.round_dp_with_strategy(scale, RoundingStrategy::RoundDown), + scale if scale < unpacked.scale => self.round_dp_with_strategy(scale, RoundingStrategy::BankersRounding), _ => self, } } diff --git a/src/postgres.rs b/src/postgres.rs index 98e65e7c..8afb0762 100644 --- a/src/postgres.rs +++ b/src/postgres.rs @@ -61,6 +61,12 @@ impl Decimal { let fract_pow = 4 * (i as u32 + 1 + start_fractionals); if fract_pow <= MAX_PRECISION { result += Decimal::new(digit as i64, 0) / Decimal::from_i128_with_scale(10i128.pow(fract_pow), 0); + } else if fract_pow == MAX_PRECISION + 4 { + // rounding last digit + if digit >= 5000 { + result += + Decimal::new(1 as i64, 0) / Decimal::from_i128_with_scale(10i128.pow(MAX_PRECISION), 0); + } } } } @@ -279,7 +285,7 @@ mod diesel { #[test] fn test_with_scale() { - let value = Decimal::from_str("0.024537600000").unwrap(); + let value = Decimal::from_str("0.024537600001").unwrap(); let from_value = Decimal::from_str("0.40000000").unwrap(); let to_value = Decimal::from_str("16.30151278").unwrap(); let new_value = (from_value / to_value).with_scale(12); @@ -518,7 +524,7 @@ mod diesel { let res: Decimal = pg_numeric.try_into().unwrap(); assert_eq!(res.to_string(), expected.to_string()); - let expected = Decimal::from_str("3.1415926535897932384626433832").unwrap(); + let expected = Decimal::from_str("3.1415926535897932384626433833").unwrap(); let pg_numeric = PgNumeric::Positive { weight: 0, scale: 30, @@ -527,7 +533,7 @@ mod diesel { let res: Decimal = pg_numeric.try_into().unwrap(); assert_eq!(res.to_string(), expected.to_string()); - let expected = Decimal::from_str("3.1415926535897932384626433832").unwrap(); + let expected = Decimal::from_str("3.1415926535897932384626433833").unwrap(); let pg_numeric = PgNumeric::Positive { weight: 0, scale: 34, @@ -537,7 +543,7 @@ mod diesel { let res: Decimal = pg_numeric.try_into().unwrap(); assert_eq!(res.to_string(), expected.to_string()); - let expected = Decimal::from_str("1.2345678901234567890123456789").unwrap(); + let expected = Decimal::from_str("1.2345678901234567890123456790").unwrap(); let pg_numeric = PgNumeric::Positive { weight: 0, scale: 34, @@ -734,20 +740,20 @@ mod postgres { 65, 30, "3.141592653589793238462643383279", - "3.1415926535897932384626433832", + "3.1415926535897932384626433833", ), ( 65, 34, "3.1415926535897932384626433832795028", - "3.1415926535897932384626433832", + "3.1415926535897932384626433833", ), // Unrounded number ( 65, 34, "1.234567890123456789012345678950000", - "1.2345678901234567890123456789", + "1.2345678901234567890123456790", ), ( 65, From c354b08dec8b36b95fbf5d58fee5ba29f8a30136 Mon Sep 17 00:00:00 2001 From: serejkaaa512 Date: Fri, 15 May 2020 11:19:00 +0300 Subject: [PATCH 6/9] fixed docs --- src/decimal.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/decimal.rs b/src/decimal.rs index 0351b459..43210646 100644 --- a/src/decimal.rs +++ b/src/decimal.rs @@ -384,8 +384,7 @@ impl Decimal { /// Change scale of decimal number, without changing number itself /// - /// > Note that values bigger then 28 will cause panic - /// > Note that setting scale which is less then current, cause number truncation + /// > Note that setting scale which is less then current, cause number Bankers Rounding /// /// # Example /// From 6b2432f4ff073dbfabcc4c702f5d5d56c4b23501 Mon Sep 17 00:00:00 2001 From: serejkaaa512 Date: Tue, 19 May 2020 17:32:59 +0300 Subject: [PATCH 7/9] refactored with_scale --- src/decimal.rs | 146 ++++++++++++++++++++++++++++++++++++++++-------- src/postgres.rs | 69 +---------------------- 2 files changed, 123 insertions(+), 92 deletions(-) diff --git a/src/decimal.rs b/src/decimal.rs index ff1b0f89..e08a89ce 100644 --- a/src/decimal.rs +++ b/src/decimal.rs @@ -390,32 +390,15 @@ impl Decimal { /// ``` /// use rust_decimal::Decimal; /// - /// let number = Decimal::new(1_123, 3).with_scale(6); + /// let number = Decimal::new(1_123, 3).rescale(6); /// assert_eq!(number, Decimal::new(1_123_000, 6)); /// ``` - pub fn with_scale(self, scale: u32) -> Self { - let unpacked = self.unpack(); - - match scale { - scale if scale > unpacked.scale => { - let scale_diff = scale - unpacked.scale; - let mul = match scale_diff { - 0..=9 => Decimal::from_u32(POWERS_10[scale_diff as usize]).unwrap(), - 10..=19 => Decimal::from_u64(BIG_POWERS_10[scale_diff as usize - 10]).unwrap(), - 20..=MAX_PRECISION => { - Decimal::from_u64(BIG_POWERS_10[0]).unwrap() - * Decimal::from_u64(BIG_POWERS_10[scale_diff as usize - 20]).unwrap() - } - _ => Decimal::from_u64(BIG_POWERS_10[0]).unwrap() * Decimal::from_u64(BIG_POWERS_10[8]).unwrap(), - }; - let mut result = self * mul; - let set_scale = std::cmp::min(MAX_PRECISION, scale); - result.set_scale(set_scale).unwrap(); - result - } - scale if scale < unpacked.scale => self.round_dp_with_strategy(scale, RoundingStrategy::BankersRounding), - _ => self, - } + 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. @@ -1593,6 +1576,59 @@ fn rescale(left: &mut [u32; 3], left_scale: &mut u32, right: &mut [u32; 3], righ } } +/// 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 is_all_zero(value) { + return; + } + + 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(value) { + *value_scale = new_scale; + return; + } + + diff -= 1; + + // 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; + } +} + // This method should only be used where copy from slice cannot be #[inline] fn copy_array_diff_lengths(into: &mut [u32], from: &[u32]) { @@ -3053,4 +3089,66 @@ mod test { 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()); + } + } } diff --git a/src/postgres.rs b/src/postgres.rs index 8afb0762..5d79ecd1 100644 --- a/src/postgres.rs +++ b/src/postgres.rs @@ -73,7 +73,7 @@ impl Decimal { result.set_sign_negative(neg); // Setting scale frm PG value. - result = result.with_scale(scale as u32); + result = result.rescale(scale as u32); Ok(result) } @@ -283,73 +283,6 @@ mod diesel { assert_eq!(dec.to_string(), "8944.000000000000".to_string()); } - #[test] - fn test_with_scale() { - let value = Decimal::from_str("0.024537600001").unwrap(); - let from_value = Decimal::from_str("0.40000000").unwrap(); - let to_value = Decimal::from_str("16.30151278").unwrap(); - let new_value = (from_value / to_value).with_scale(12); - assert_eq!(new_value.to_string(), value.to_string()); - - let value = Decimal::from_str("0.12345600000").unwrap(); - let new_value = Decimal::from_str("0.123456").unwrap(); - let value = value.with_scale(6); - assert_eq!(new_value.to_string(), value.to_string()); - - let value = Decimal::from_str("0.123456").unwrap(); - let new_value = Decimal::from_str("0.123456000000").unwrap(); - let value = value.with_scale(12); - assert_eq!(new_value.to_string(), value.to_string()); - - let value = Decimal::from_str("0.123456").unwrap(); - let new_value = Decimal::from_str("0").unwrap(); - let value = value.with_scale(0); - assert_eq!(new_value.to_string(), value.to_string()); - - let value = Decimal::from_str("0.000001").unwrap(); - let new_value = Decimal::from_str("0.0000").unwrap(); - let value = value.with_scale(4); - assert_eq!(new_value.to_string(), value.to_string()); - - let value = Decimal::from_str("1233456").unwrap(); - let new_value = Decimal::from_str("1233456.0000").unwrap(); - let value = value.with_scale(4); - assert_eq!(new_value.to_string(), value.to_string()); - - let value = Decimal::from_str("1.2").unwrap(); - let new_value = Decimal::from_str("1.2000000000000000000000000000").unwrap(); - let value = value.with_scale(30); - assert_eq!(new_value.to_string(), value.to_string()); - - // 0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF (96 bit) - let value = Decimal::from_str("79228162514264337593543950335").unwrap(); - let new_value = Decimal::from_str("79228162514264337593543950335").unwrap(); - let value = value.with_scale(0); - assert_eq!(new_value.to_string(), value.to_string()); - - // 0x0FFF_FFFF_FFFF_FFFF_FFFF_FFFF (95 bit) - let value = Decimal::from_str("4951760157141521099596496895").unwrap(); - let new_value = Decimal::from_str("4951760157141521099596496895.0").unwrap(); - let value = value.with_scale(1); - assert_eq!(new_value.to_string(), value.to_string()); - - // 0x1000_0000_0000_0000_0000_0000 - let value = Decimal::from_str("4951760157141521099596496896").unwrap(); - let new_value = Decimal::from_str("4951760157141521099596496896.0").unwrap(); - let value = value.with_scale(1); - assert_eq!(new_value.to_string(), value.to_string()); - - let value = Decimal::from_str("18446744073709551615").unwrap(); - let new_value = Decimal::from_str("18446744073709551615.000000").unwrap(); - let value = value.with_scale(6); - assert_eq!(new_value.to_string(), value.to_string()); - - let value = Decimal::from_str("-18446744073709551615").unwrap(); - let new_value = Decimal::from_str("-18446744073709551615.000000").unwrap(); - let value = value.with_scale(6); - assert_eq!(new_value.to_string(), value.to_string()); - } - #[test] fn decimal_to_pgnumeric_converts_digits_to_base_10000() { let decimal = Decimal::from_str("1").unwrap(); From a92e80a6ce4542e5b30f2a123865be5a8ccf08d3 Mon Sep 17 00:00:00 2001 From: serejkaaa512 Date: Tue, 19 May 2020 17:59:28 +0300 Subject: [PATCH 8/9] fixed tests --- src/postgres.rs | 66 +++++++++++++++++-------------------------------- 1 file changed, 22 insertions(+), 44 deletions(-) diff --git a/src/postgres.rs b/src/postgres.rs index 5d79ecd1..9a76700d 100644 --- a/src/postgres.rs +++ b/src/postgres.rs @@ -237,50 +237,28 @@ mod diesel { #[test] fn test_unnecessary_zeroes() { - let value = Decimal::from_str("0.000001660").unwrap(); - let pg = PgNumeric::from(value); - let dec = Decimal::try_from(pg).unwrap(); - assert_eq!(dec, value); - - let value = Decimal::from_str("41.120255926293").unwrap(); - let pg = PgNumeric::from(value); - let dec = Decimal::try_from(pg).unwrap(); - assert_eq!(dec, value); - - let value = Decimal::from_str("0.55389733").unwrap(); - let pg = PgNumeric::from(value); - let dec = Decimal::try_from(pg).unwrap(); - assert_eq!(dec, value); - - let value = Decimal::from_str("8883.559868542931").unwrap(); - let pg = PgNumeric::from(value); - let dec = Decimal::try_from(pg).unwrap(); - assert_eq!(dec, value); - - let value = Decimal::from_str("0.0000_0000_0016_6000_00").unwrap(); - let pg = PgNumeric::from(value); - let dec = Decimal::try_from(pg).unwrap(); - assert_eq!(dec, value); - - let value = Decimal::from_str("0.00000166650000").unwrap(); - let pg = PgNumeric::from(value); - let dec = Decimal::try_from(pg).unwrap(); - assert_eq!(dec.to_string(), "0.00000166650000".to_string()); - - let value = Decimal::from_str("1666500000000").unwrap(); - let pg = PgNumeric::from(value); - let dec = Decimal::try_from(pg).unwrap(); - assert_eq!(dec.to_string(), "1666500000000".to_string()); - - let value = Decimal::from_str("1666500000000.00000545").unwrap(); - let pg = PgNumeric::from(value); - let dec = Decimal::try_from(pg).unwrap(); - assert_eq!(dec.to_string(), "1666500000000.00000545".to_string()); - - let value = Decimal::from_str("8944.000000000000").unwrap(); - let pg = PgNumeric::from(value); - let dec = Decimal::try_from(pg).unwrap(); - assert_eq!(dec.to_string(), "8944.000000000000".to_string()); + fn extract(value: &str) -> Decimal { + Decimal::from_str(value).unwrap() + } + + let tests = &[ + ("0.000001660"), + ("41.120255926293000"), + ("0.5538973300"), + ("08883.55986854293100"), + ("0.0000_0000_0016_6000_00"), + ("0.00000166650000"), + ("1666500000000"), + ("1666500000000.0000054500"), + ("8944.000000000000"), + ]; + + for &value in tests { + let value = extract(value); + let pg = PgNumeric::from(value); + let dec = Decimal::try_from(pg).unwrap(); + assert_eq!(dec, value); + } } #[test] From 365f782c6732f00e0ca831231d6236d1448c7304 Mon Sep 17 00:00:00 2001 From: serejkaaa512 Date: Tue, 19 May 2020 19:07:10 +0300 Subject: [PATCH 9/9] refactored old rescale --- src/decimal.rs | 83 ++++++++------------------------------------------ 1 file changed, 13 insertions(+), 70 deletions(-) diff --git a/src/decimal.rs b/src/decimal.rs index e08a89ce..c0d16455 100644 --- a/src/decimal.rs +++ b/src/decimal.rs @@ -1035,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 @@ -1434,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. @@ -1492,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; @@ -1506,72 +1506,15 @@ 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; - } 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; - } - - match target { - Target::Left => { - *left_scale -= diff; - *right_scale = *left_scale; - } - Target::Right => { - *right_scale -= diff; - *left_scale = *right_scale; - } - } - - if diff == 0 { - // We're done - same scale - 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" - - // Now do the necessary rounding - let mut remainder = 0; - while diff > 0 { - if is_all_zero(other) { - return; + inner_rescale(right, right_scale, *left_scale); + if right_scale != left_scale { + inner_rescale(left, left_scale, *right_scale); } - - 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; - } + } else { + inner_rescale(left, left_scale, *right_scale); + if right_scale != left_scale { + inner_rescale(right, right_scale, *left_scale); } } } @@ -3000,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) } } @@ -3073,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); @@ -3082,7 +3025,7 @@ 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);