From 27c62bcc76f5f6a013079ec6c0ddf284be04380c Mon Sep 17 00:00:00 2001 From: Gautham Date: Tue, 6 Sep 2022 13:01:02 +0530 Subject: [PATCH 01/37] Add parity scale codec --- Cargo.toml | 3 +++ src/decimal.rs | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index f3d97e00..adf07577 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,8 @@ build = "build.rs" all-features = true [dependencies] +parity-scale-codec = {package = "parity-scale-codec", optional = true, version = "3.1.5"} +parity-scale-codec-derive = {package = "parity-scale-codec-derive", optional = true, version = "3.1.3"} arbitrary = { default-features = false, optional = true, version = "1.0" } arrayvec = { default-features = false, version = "0.7" } borsh = { default-features = false, optional = true, version = "0.9.3" } @@ -78,6 +80,7 @@ serde-with-float = ["serde"] serde-with-str = ["serde"] std = [] tokio-pg = ["db-tokio-postgres"] # Backwards compatability +scale-codec = ["parity-scale-codec-derive","parity-scale-codec"] [workspace] members = [".", "./macros"] diff --git a/src/decimal.rs b/src/decimal.rs index c9e36049..f3be105c 100644 --- a/src/decimal.rs +++ b/src/decimal.rs @@ -30,6 +30,8 @@ use num_traits::float::FloatCore; use num_traits::{FromPrimitive, Num, One, Signed, ToPrimitive, Zero}; #[cfg(feature = "rkyv")] use rkyv::{Archive, Deserialize, Serialize}; +#[cfg(feature = "scale-codec")] +use parity_scale_codec_derive::{Decode,Encode}; /// The smallest value that can be represented by this decimal type. const MIN: Decimal = Decimal { @@ -124,6 +126,10 @@ pub struct UnpackedDecimal { archive_attr(derive(Clone, Copy, Debug)) )] #[cfg_attr(feature = "rkyv-safe", archive_attr(derive(CheckBytes)))] +#[cfg_attr( +feature = "scale-codec", +derive(Decode, Encode), +)] pub struct Decimal { // Bits 0-15: unused // Bits 16-23: Contains "e", a value between 0-28 that indicates the scale From 394d8f8329e27344e8fe03e42d305338b7a83cd9 Mon Sep 17 00:00:00 2001 From: Gautham Date: Tue, 6 Sep 2022 19:24:24 +0530 Subject: [PATCH 02/37] Add scale-info too --- Cargo.toml | 3 ++- src/decimal.rs | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index adf07577..7da5d2b4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ all-features = true [dependencies] parity-scale-codec = {package = "parity-scale-codec", optional = true, version = "3.1.5"} parity-scale-codec-derive = {package = "parity-scale-codec-derive", optional = true, version = "3.1.3"} +scale-info = {optional=true, version = "2.1.2", features = ["derive"]} arbitrary = { default-features = false, optional = true, version = "1.0" } arrayvec = { default-features = false, version = "0.7" } borsh = { default-features = false, optional = true, version = "0.9.3" } @@ -80,7 +81,7 @@ serde-with-float = ["serde"] serde-with-str = ["serde"] std = [] tokio-pg = ["db-tokio-postgres"] # Backwards compatability -scale-codec = ["parity-scale-codec-derive","parity-scale-codec"] +scale-codec = ["parity-scale-codec-derive","parity-scale-codec","scale-info"] [workspace] members = [".", "./macros"] diff --git a/src/decimal.rs b/src/decimal.rs index f3be105c..94913f10 100644 --- a/src/decimal.rs +++ b/src/decimal.rs @@ -32,6 +32,8 @@ use num_traits::{FromPrimitive, Num, One, Signed, ToPrimitive, Zero}; use rkyv::{Archive, Deserialize, Serialize}; #[cfg(feature = "scale-codec")] use parity_scale_codec_derive::{Decode,Encode}; +#[cfg(feature = "scale-codec")] +use scale_info::TypeInfo; /// The smallest value that can be represented by this decimal type. const MIN: Decimal = Decimal { @@ -128,7 +130,7 @@ pub struct UnpackedDecimal { #[cfg_attr(feature = "rkyv-safe", archive_attr(derive(CheckBytes)))] #[cfg_attr( feature = "scale-codec", -derive(Decode, Encode), +derive(Decode, Encode, TypeInfo), )] pub struct Decimal { // Bits 0-15: unused From 204d1271a3ff680ce9a5be0467246771d2388b2a Mon Sep 17 00:00:00 2001 From: Gautham Date: Tue, 6 Sep 2022 19:27:25 +0530 Subject: [PATCH 03/37] Add scale-info too --- Cargo.toml | 6 +++--- src/decimal.rs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7da5d2b4..7518b1fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,9 +22,9 @@ build = "build.rs" all-features = true [dependencies] -parity-scale-codec = {package = "parity-scale-codec", optional = true, version = "3.1.5"} -parity-scale-codec-derive = {package = "parity-scale-codec-derive", optional = true, version = "3.1.3"} -scale-info = {optional=true, version = "2.1.2", features = ["derive"]} +parity-scale-codec = {package = "parity-scale-codec", optional = true, version = "3.1.5", features = ["max-encoded-len"], default-features = false} +parity-scale-codec-derive = {package = "parity-scale-codec-derive", optional = true, version = "3.1.3",default-features = false} +scale-info = {optional=true, version = "2.1.2", features = ["derive"],default-features = false} arbitrary = { default-features = false, optional = true, version = "1.0" } arrayvec = { default-features = false, version = "0.7" } borsh = { default-features = false, optional = true, version = "0.9.3" } diff --git a/src/decimal.rs b/src/decimal.rs index 94913f10..e4cdbf01 100644 --- a/src/decimal.rs +++ b/src/decimal.rs @@ -31,7 +31,7 @@ use num_traits::{FromPrimitive, Num, One, Signed, ToPrimitive, Zero}; #[cfg(feature = "rkyv")] use rkyv::{Archive, Deserialize, Serialize}; #[cfg(feature = "scale-codec")] -use parity_scale_codec_derive::{Decode,Encode}; +use parity_scale_codec_derive::{Decode,Encode, MaxEncodedLen}; #[cfg(feature = "scale-codec")] use scale_info::TypeInfo; @@ -130,7 +130,7 @@ pub struct UnpackedDecimal { #[cfg_attr(feature = "rkyv-safe", archive_attr(derive(CheckBytes)))] #[cfg_attr( feature = "scale-codec", -derive(Decode, Encode, TypeInfo), +derive(Decode, Encode, TypeInfo, MaxEncodedLen), )] pub struct Decimal { // Bits 0-15: unused From 610096f3e5b70d58b64f4e93e8a8d50822938ac6 Mon Sep 17 00:00:00 2001 From: Gautham Date: Wed, 14 Sep 2022 17:26:18 +0530 Subject: [PATCH 04/37] Make from_parts_raw --- src/decimal.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/decimal.rs b/src/decimal.rs index e4cdbf01..7c5b4735 100644 --- a/src/decimal.rs +++ b/src/decimal.rs @@ -556,7 +556,7 @@ impl Decimal { } #[must_use] - pub(crate) const fn from_parts_raw(lo: u32, mid: u32, hi: u32, flags: u32) -> Decimal { + pub const fn from_parts_raw(lo: u32, mid: u32, hi: u32, flags: u32) -> Decimal { if lo == 0 && mid == 0 && hi == 0 { Decimal { lo, From 25f084996a7c941593aa1d2d8711e8218c391563 Mon Sep 17 00:00:00 2001 From: Gautham Date: Thu, 22 Jun 2023 07:47:56 +0530 Subject: [PATCH 05/37] Make deps in alphabetical order --- Cargo.toml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6e93e3c8..d1652f1c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,9 +23,6 @@ version = "1.30.0" all-features = true [dependencies] -parity-scale-codec = {package = "parity-scale-codec", optional = true, version = "3.1.5", features = ["max-encoded-len"], default-features = false} -parity-scale-codec-derive = {package = "parity-scale-codec-derive", optional = true, version = "3.1.3",default-features = false} -scale-info = {optional=true, version = "2.1.2", features = ["derive"],default-features = false} arbitrary = { default-features = false, optional = true, version = "1.0" } arrayvec = { default-features = false, version = "0.7" } borsh = { default-features = false, optional = true, version = "0.10.0" } @@ -36,11 +33,14 @@ diesel1 = { default-features = false, optional = true, package = "diesel", versi diesel2 = { default-features = false, optional = true, package = "diesel", version = "2.0" } ndarray = { default-features = false, optional = true, version = "0.15.6" } num-traits = { default-features = false, features = ["i128"], version = "0.2" } +parity-scale-codec = { optional = true, version = "3.1.5", features = ["max-encoded-len"], default-features = false} +parity-scale-codec-derive = { optional = true, version = "3.1.3",default-features = false} postgres = { default-features = false, optional = true, version = "0.19" } proptest = { default-features = false, optional = true, features = ["std"], version = "1.0" } rand = { default-features = false, optional = true, version = "0.8" } rkyv = { default-features = false, features = ["size_32", "std"], optional = true, version = "0.7" } rocket = { default-features = false, optional = true, version = "0.5.0-rc.1" } +scale-info = {optional=true, version = "2.1.2", features = ["derive"], default-features = false} serde = { default-features = false, optional = true, version = "1.0" } serde_json = { default-features = false, optional = true, version = "1.0" } tokio-postgres = { default-features = false, optional = true, version = "0.7" } @@ -81,6 +81,7 @@ rkyv = ["dep:rkyv"] rkyv-safe = ["dep:bytecheck", "rkyv/validation"] rocket-traits = ["dep:rocket"] rust-fuzz = ["dep:arbitrary"] +scale-codec = ["parity-scale-codec-derive","parity-scale-codec","scale-info"] serde = ["dep:serde"] serde-arbitrary-precision = ["serde-with-arbitrary-precision"] serde-bincode = ["serde-str"] # Backwards compatability @@ -89,9 +90,8 @@ serde-str = ["serde-with-str"] serde-with-arbitrary-precision = ["serde", "serde_json/arbitrary_precision", "serde_json/std"] serde-with-float = ["serde"] serde-with-str = ["serde"] -std = ["arrayvec/std", "borsh?/std", "bytecheck?/std", "byteorder?/std", "bytes?/std", "rand?/std", "rkyv?/std", "serde?/std", "serde_json?/std"] +std = ["arrayvec/std", "borsh?/std", "bytecheck?/std", "byteorder?/std", "bytes?/std", "rand?/std", "rkyv?/std", "serde?/std", "serde_json?/std", "parity-scale-codec?/std","scale-info?/std"] tokio-pg = ["db-tokio-postgres"] # Backwards compatability -scale-codec = ["parity-scale-codec-derive","parity-scale-codec","scale-info"] [workspace] members = [".", "./macros"] From 031d20357d6b6db8e97fc4859d0902f1d7e6e04d Mon Sep 17 00:00:00 2001 From: Mikhail Katychev Date: Sun, 16 Jul 2023 18:39:21 -0500 Subject: [PATCH 06/37] Clippy/fix ruleset (#598) * Added clippy configurations in .cargo/config.toml * clippy --fix * cargo clippy --fix -- -W clippy::uninlined_format_args --- .cargo/config.toml | 11 ++ build.rs | 4 +- src/error.rs | 5 +- src/ops/array.rs | 3 +- src/postgres/driver.rs | 22 ++-- src/serde.rs | 5 +- src/str.rs | 2 +- tests/decimal_tests.rs | 230 ++++++++++++++++++----------------------- 8 files changed, 130 insertions(+), 152 deletions(-) create mode 100644 .cargo/config.toml diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 00000000..b5a6cbbf --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,11 @@ +[target.'cfg(all())'] +rustflags = [ + # BEGIN - Embark standard lints v0.4 + # do not change or add/remove here, but one can add exceptions after this section + # for more info see: + "-Aclippy::inconsistent_digit_grouping", + "-Aclippy::large_digit_groups", + "-Aclippy::excessive_precision", + "-Aclippy::zero_prefixed_literal", +] + diff --git a/build.rs b/build.rs index 604a77c7..fb886edd 100644 --- a/build.rs +++ b/build.rs @@ -43,14 +43,14 @@ fn prepare(readme: &str) -> Result> { writeln!(cleaned, "```rust")?; writeln!(cleaned, "# use rust_decimal::Decimal;")?; writeln!(cleaned, "# use serde::{{Serialize, Deserialize}};")?; - write!(cleaned, "# #[cfg(features = \"{}\")]", feature)?; + write!(cleaned, "# #[cfg(features = \"{feature}\")]")?; } else { if !feature_section && line.starts_with("## Features") { feature_section = true; } else if feature_section && line.starts_with("### ") { feature = line.replace("### ", "").replace('`', ""); } - write!(cleaned, "{}", line)?; + write!(cleaned, "{line}")?; } writeln!(cleaned)?; } diff --git a/src/error.rs b/src/error.rs index 5e5969e4..0bd8cfed 100644 --- a/src/error.rs +++ b/src/error.rs @@ -57,12 +57,11 @@ impl fmt::Display for Error { Self::ScaleExceedsMaximumPrecision(ref scale) => { write!( f, - "Scale exceeds the maximum precision allowed: {} > {}", - scale, MAX_PRECISION_U32 + "Scale exceeds the maximum precision allowed: {scale} > {MAX_PRECISION_U32}" ) } Self::ConversionTo(ref type_name) => { - write!(f, "Error while converting to {}", type_name) + write!(f, "Error while converting to {type_name}") } } } diff --git a/src/ops/array.rs b/src/ops/array.rs index e058c983..2840e4b1 100644 --- a/src/ops/array.rs +++ b/src/ops/array.rs @@ -358,8 +358,7 @@ mod test { assert_eq!(value, expected_value); assert_eq!( value_scale, expected_scale, - "value: {}, requested scale: {}", - value_raw, new_scale + "value: {value_raw}, requested scale: {new_scale}" ); } } diff --git a/src/postgres/driver.rs b/src/postgres/driver.rs index 7d185e3b..eb94a4a6 100644 --- a/src/postgres/driver.rs +++ b/src/postgres/driver.rs @@ -213,7 +213,7 @@ mod test { // Test NULL let result: Option = match client.query("SELECT NULL::numeric", &[]) { - Ok(x) => x.iter().next().unwrap().get(0), + Ok(x) => x.first().unwrap().get(0), Err(err) => panic!("{:#?}", err), }; assert_eq!(None, result); @@ -229,9 +229,9 @@ mod test { let connection = connection.map(|e| e.unwrap()); tokio::spawn(connection); - let statement = client.prepare(&"SELECT NULL::numeric").await.unwrap(); + let statement = client.prepare("SELECT NULL::numeric").await.unwrap(); let rows = client.query(&statement, &[]).await.unwrap(); - let result: Option = rows.iter().next().unwrap().get(0); + let result: Option = rows.first().unwrap().get(0); assert_eq!(None, result); } @@ -243,7 +243,7 @@ mod test { Err(err) => panic!("{:#?}", err), }; let result: Decimal = match client.query("SELECT 1e-130::NUMERIC(130, 0)", &[]) { - Ok(x) => x.iter().next().unwrap().get(0), + Ok(x) => x.first().unwrap().get(0), Err(err) => panic!("error - {:#?}", err), }; // We compare this to zero since it is so small that it is effectively zero @@ -259,7 +259,7 @@ mod test { for &(precision, scale, sent, expected) in TEST_DECIMALS.iter() { let result: Decimal = match client.query(&*format!("SELECT {}::NUMERIC({}, {})", sent, precision, scale), &[]) { - Ok(x) => x.iter().next().unwrap().get(0), + Ok(x) => x.first().unwrap().get(0), Err(err) => panic!("SELECT {}::NUMERIC({}, {}), error - {:#?}", sent, precision, scale, err), }; assert_eq!( @@ -284,11 +284,11 @@ mod test { tokio::spawn(connection); for &(precision, scale, sent, expected) in TEST_DECIMALS.iter() { let statement = client - .prepare(&*format!("SELECT {}::NUMERIC({}, {})", sent, precision, scale)) + .prepare(&format!("SELECT {}::NUMERIC({}, {})", sent, precision, scale)) .await .unwrap(); let rows = client.query(&statement, &[]).await.unwrap(); - let result: Decimal = rows.iter().next().unwrap().get(0); + let result: Decimal = rows.first().unwrap().get(0); assert_eq!(expected, result.to_string(), "NUMERIC({}, {})", precision, scale); } @@ -304,7 +304,7 @@ mod test { let number = Decimal::from_str(sent).unwrap(); let result: Decimal = match client.query(&*format!("SELECT $1::NUMERIC({}, {})", precision, scale), &[&number]) { - Ok(x) => x.iter().next().unwrap().get(0), + Ok(x) => x.first().unwrap().get(0), Err(err) => panic!("{:#?}", err), }; assert_eq!(expected, result.to_string(), "NUMERIC({}, {})", precision, scale); @@ -323,12 +323,12 @@ mod test { for &(precision, scale, sent, expected) in TEST_DECIMALS.iter() { let statement = client - .prepare(&*format!("SELECT $1::NUMERIC({}, {})", precision, scale)) + .prepare(&format!("SELECT $1::NUMERIC({}, {})", precision, scale)) .await .unwrap(); let number = Decimal::from_str(sent).unwrap(); let rows = client.query(&statement, &[&number]).await.unwrap(); - let result: Decimal = rows.iter().next().unwrap().get(0); + let result: Decimal = rows.first().unwrap().get(0); assert_eq!(expected, result.to_string(), "NUMERIC({}, {})", precision, scale); } @@ -367,7 +367,7 @@ mod test { for &(precision, scale, sent) in tests.iter() { let statement = client - .prepare(&*format!("SELECT {}::NUMERIC({}, {})", sent, precision, scale)) + .prepare(&format!("SELECT {}::NUMERIC({}, {})", sent, precision, scale)) .await .unwrap(); diff --git a/src/serde.rs b/src/serde.rs index ce876309..c4237727 100644 --- a/src/serde.rs +++ b/src/serde.rs @@ -555,8 +555,7 @@ mod test { ]; for &(serialized, value) in data.iter() { let result = serde_json::from_str(serialized); - assert_eq!( - true, + assert!( result.is_ok(), "expected successful deserialization for {}. Error: {:?}", serialized, @@ -568,7 +567,7 @@ mod test { record.amount.to_string(), "expected: {}, actual: {}", value, - record.amount.to_string() + record.amount ); } } diff --git a/src/str.rs b/src/str.rs index f3b89d31..94545732 100644 --- a/src/str.rs +++ b/src/str.rs @@ -710,7 +710,7 @@ mod test { fn display_does_not_overflow_max_capacity() { let num = Decimal::from_str("1.2").unwrap(); let mut buffer = ArrayString::<64>::new(); - let _ = buffer.write_fmt(format_args!("{:.31}", num)).unwrap(); + buffer.write_fmt(format_args!("{num:.31}")).unwrap(); assert_eq!("1.2000000000000000000000000000000", buffer.as_str()); } diff --git a/tests/decimal_tests.rs b/tests/decimal_tests.rs index d7c3c380..c106ea88 100644 --- a/tests/decimal_tests.rs +++ b/tests/decimal_tests.rs @@ -19,8 +19,8 @@ fn it_can_extract_the_mantissa() { ]; for &(input, mantissa, scale) in &tests { let num = Decimal::from_str(input).unwrap(); - assert_eq!(num.mantissa(), mantissa, "Mantissa for {}", input); - assert_eq!(num.scale(), scale, "Scale for {}", input); + assert_eq!(num.mantissa(), mantissa, "Mantissa for {input}"); + assert_eq!(num.scale(), scale, "Scale for {input}"); } } @@ -29,7 +29,7 @@ fn it_can_extract_the_mantissa() { #[test] fn it_creates_a_new_negative_decimal() { let a = Decimal::new(-100, 2); - assert_eq!(a.is_sign_negative(), true); + assert!(a.is_sign_negative()); assert_eq!(a.scale(), 2); assert_eq!("-1.00", a.to_string()); } @@ -37,12 +37,12 @@ fn it_creates_a_new_negative_decimal() { #[test] fn it_creates_a_new_decimal_using_numeric_boundaries() { let a = Decimal::new(i64::MAX, 2); - assert_eq!(a.is_sign_negative(), false); + assert!(!a.is_sign_negative()); assert_eq!(a.scale(), 2); assert_eq!("92233720368547758.07", a.to_string()); let b = Decimal::new(i64::MIN, 2); - assert_eq!(b.is_sign_negative(), true); + assert!(b.is_sign_negative()); assert_eq!(b.scale(), 2); assert_eq!("-92233720368547758.08", b.to_string()); } @@ -56,7 +56,7 @@ fn it_parses_empty_string() { #[test] fn it_parses_positive_int_string() { let a = Decimal::from_str("233").unwrap(); - assert_eq!(a.is_sign_negative(), false); + assert!(!a.is_sign_negative()); assert_eq!(a.scale(), 0); assert_eq!("233", a.to_string()); } @@ -64,7 +64,7 @@ fn it_parses_positive_int_string() { #[test] fn it_parses_negative_int_string() { let a = Decimal::from_str("-233").unwrap(); - assert_eq!(a.is_sign_negative(), true); + assert!(a.is_sign_negative()); assert_eq!(a.scale(), 0); assert_eq!("-233", a.to_string()); } @@ -72,7 +72,7 @@ fn it_parses_negative_int_string() { #[test] fn it_parses_positive_float_string() { let a = Decimal::from_str("233.323223").unwrap(); - assert_eq!(a.is_sign_negative(), false); + assert!(!a.is_sign_negative()); assert_eq!(a.scale(), 6); assert_eq!("233.323223", a.to_string()); } @@ -80,7 +80,7 @@ fn it_parses_positive_float_string() { #[test] fn it_parses_negative_float_string() { let a = Decimal::from_str("-233.43343").unwrap(); - assert_eq!(a.is_sign_negative(), true); + assert!(a.is_sign_negative()); assert_eq!(a.scale(), 5); assert_eq!("-233.43343", a.to_string()); } @@ -88,7 +88,7 @@ fn it_parses_negative_float_string() { #[test] fn it_parses_positive_tiny_float_string() { let a = Decimal::from_str(".000001").unwrap(); - assert_eq!(a.is_sign_negative(), false); + assert!(!a.is_sign_negative()); assert_eq!(a.scale(), 6); assert_eq!("0.000001", a.to_string()); } @@ -96,7 +96,7 @@ fn it_parses_positive_tiny_float_string() { #[test] fn it_parses_negative_tiny_float_string() { let a = Decimal::from_str("-0.000001").unwrap(); - assert_eq!(a.is_sign_negative(), true); + assert!(a.is_sign_negative()); assert_eq!(a.scale(), 6); assert_eq!("-0.000001", a.to_string()); } @@ -255,7 +255,7 @@ fn it_can_deserialize_unbounded_values() { ]; for &(bytes, expected) in &tests { let dec = Decimal::deserialize(bytes); - let string = format!("{:.9999}", dec); + let string = format!("{dec:.9999}"); let dec2 = Decimal::from_str(&string).unwrap(); assert_eq!(dec, dec2); assert_eq!(dec.to_string(), expected, "dec.to_string()"); @@ -268,74 +268,74 @@ fn it_can_deserialize_unbounded_values() { #[test] fn it_formats() { let a = Decimal::from_str("233.323223").unwrap(); - assert_eq!(format!("{}", a), "233.323223"); - assert_eq!(format!("{:.9}", a), "233.323223000"); - assert_eq!(format!("{:.0}", a), "233"); - assert_eq!(format!("{:.2}", a), "233.32"); - assert_eq!(format!("{:010.2}", a), "0000233.32"); - assert_eq!(format!("{:0<10.2}", a), "233.320000"); + assert_eq!(format!("{a}"), "233.323223"); + assert_eq!(format!("{a:.9}"), "233.323223000"); + assert_eq!(format!("{a:.0}"), "233"); + assert_eq!(format!("{a:.2}"), "233.32"); + assert_eq!(format!("{a:010.2}"), "0000233.32"); + assert_eq!(format!("{a:0<10.2}"), "233.320000"); } #[test] fn it_formats_neg() { let a = Decimal::from_str("-233.323223").unwrap(); - assert_eq!(format!("{}", a), "-233.323223"); - assert_eq!(format!("{:.9}", a), "-233.323223000"); - assert_eq!(format!("{:.0}", a), "-233"); - assert_eq!(format!("{:.2}", a), "-233.32"); - assert_eq!(format!("{:010.2}", a), "-000233.32"); - assert_eq!(format!("{:0<10.2}", a), "-233.32000"); + assert_eq!(format!("{a}"), "-233.323223"); + assert_eq!(format!("{a:.9}"), "-233.323223000"); + assert_eq!(format!("{a:.0}"), "-233"); + assert_eq!(format!("{a:.2}"), "-233.32"); + assert_eq!(format!("{a:010.2}"), "-000233.32"); + assert_eq!(format!("{a:0<10.2}"), "-233.32000"); } #[test] fn it_formats_small() { let a = Decimal::from_str("0.2223").unwrap(); - assert_eq!(format!("{}", a), "0.2223"); - assert_eq!(format!("{:.9}", a), "0.222300000"); - assert_eq!(format!("{:.0}", a), "0"); - assert_eq!(format!("{:.2}", a), "0.22"); - assert_eq!(format!("{:010.2}", a), "0000000.22"); - assert_eq!(format!("{:0<10.2}", a), "0.22000000"); + assert_eq!(format!("{a}"), "0.2223"); + assert_eq!(format!("{a:.9}"), "0.222300000"); + assert_eq!(format!("{a:.0}"), "0"); + assert_eq!(format!("{a:.2}"), "0.22"); + assert_eq!(format!("{a:010.2}"), "0000000.22"); + assert_eq!(format!("{a:0<10.2}"), "0.22000000"); } #[test] fn it_formats_small_leading_zeros() { let a = Decimal::from_str("0.0023554701772169").unwrap(); - assert_eq!(format!("{}", a), "0.0023554701772169"); - assert_eq!(format!("{:.9}", a), "0.002355470"); - assert_eq!(format!("{:.0}", a), "0"); - assert_eq!(format!("{:.2}", a), "0.00"); - assert_eq!(format!("{:010.2}", a), "0000000.00"); - assert_eq!(format!("{:0<10.2}", a), "0.00000000"); + assert_eq!(format!("{a}"), "0.0023554701772169"); + assert_eq!(format!("{a:.9}"), "0.002355470"); + assert_eq!(format!("{a:.0}"), "0"); + assert_eq!(format!("{a:.2}"), "0.00"); + assert_eq!(format!("{a:010.2}"), "0000000.00"); + assert_eq!(format!("{a:0<10.2}"), "0.00000000"); } #[test] fn it_formats_small_neg() { let a = Decimal::from_str("-0.2223").unwrap(); - assert_eq!(format!("{}", a), "-0.2223"); - assert_eq!(format!("{:.9}", a), "-0.222300000"); - assert_eq!(format!("{:.0}", a), "-0"); - assert_eq!(format!("{:.2}", a), "-0.22"); - assert_eq!(format!("{:010.2}", a), "-000000.22"); - assert_eq!(format!("{:0<10.2}", a), "-0.2200000"); + assert_eq!(format!("{a}"), "-0.2223"); + assert_eq!(format!("{a:.9}"), "-0.222300000"); + assert_eq!(format!("{a:.0}"), "-0"); + assert_eq!(format!("{a:.2}"), "-0.22"); + assert_eq!(format!("{a:010.2}"), "-000000.22"); + assert_eq!(format!("{a:0<10.2}"), "-0.2200000"); } #[test] fn it_formats_zero() { let a = Decimal::from_str("0").unwrap(); - assert_eq!(format!("{}", a), "0"); - assert_eq!(format!("{:.9}", a), "0.000000000"); - assert_eq!(format!("{:.0}", a), "0"); - assert_eq!(format!("{:.2}", a), "0.00"); - assert_eq!(format!("{:010.2}", a), "0000000.00"); - assert_eq!(format!("{:0<10.2}", a), "0.00000000"); + assert_eq!(format!("{a}"), "0"); + assert_eq!(format!("{a:.9}"), "0.000000000"); + assert_eq!(format!("{a:.0}"), "0"); + assert_eq!(format!("{a:.2}"), "0.00"); + assert_eq!(format!("{a:010.2}"), "0000000.00"); + assert_eq!(format!("{a:0<10.2}"), "0.00000000"); } #[test] fn it_formats_int() { let a = Decimal::from_str("5").unwrap(); - assert_eq!(format!("{}", a), "5"); - assert_eq!(format!("{:.9}", a), "5.000000000"); - assert_eq!(format!("{:.0}", a), "5"); - assert_eq!(format!("{:.2}", a), "5.00"); - assert_eq!(format!("{:010.2}", a), "0000005.00"); - assert_eq!(format!("{:0<10.2}", a), "5.00000000"); + assert_eq!(format!("{a}"), "5"); + assert_eq!(format!("{a:.9}"), "5.000000000"); + assert_eq!(format!("{a:.0}"), "5"); + assert_eq!(format!("{a:.2}"), "5.00"); + assert_eq!(format!("{a:010.2}"), "0000005.00"); + assert_eq!(format!("{a:0<10.2}"), "5.00000000"); } #[test] @@ -349,7 +349,7 @@ fn it_formats_lower_exp() { ]; for (value, expected) in &tests { let a = Decimal::from_str(value).unwrap(); - assert_eq!(&format!("{:e}", a), *expected, "format!(\"{{:e}}\", {})", a); + assert_eq!(&format!("{a:e}"), *expected, "format!(\"{{:e}}\", {a})"); } } @@ -364,7 +364,7 @@ fn it_formats_lower_exp_padding() { ]; for (value, expected) in &tests { let a = Decimal::from_str(value).unwrap(); - assert_eq!(&format!("{:05e}", a), *expected, "format!(\"{{:05e}}\", {})", a); + assert_eq!(&format!("{a:05e}"), *expected, "format!(\"{{:05e}}\", {a})"); } } @@ -448,11 +448,8 @@ fn it_formats_scientific_precision() { ), ] { assert_eq!(format!("{:e}", Decimal::new(num, scale)), expected_no_precision); - for i in 0..expected_precision.len() { - assert_eq!( - format!("{:.prec$e}", Decimal::new(num, scale), prec = i), - expected_precision[i] - ); + for (i, precision) in expected_precision.iter().enumerate() { + assert_eq!(&format!("{:.prec$e}", Decimal::new(num, scale), prec = i), precision); } } } @@ -463,7 +460,7 @@ fn it_negates_decimals() { fn neg(a: &str, b: &str) { let a = Decimal::from_str(a).unwrap(); let result = -a; - assert_eq!(b, result.to_string(), "- {}", a.to_string()); + assert_eq!(b, result.to_string(), "- {a}"); } let tests = &[ @@ -495,9 +492,9 @@ fn it_adds_decimals() { let a = Decimal::from_str(a).unwrap(); let b = Decimal::from_str(b).unwrap(); let result = a + b; - assert_eq!(c, result.to_string(), "{} + {}", a.to_string(), b.to_string()); + assert_eq!(c, result.to_string(), "{a} + {b}"); let result = b + a; - assert_eq!(c, result.to_string(), "{} + {}", b.to_string(), a.to_string()); + assert_eq!(c, result.to_string(), "{b} + {a}"); } let tests = &[ @@ -630,7 +627,7 @@ fn it_subtracts_decimals() { let a = Decimal::from_str(a).unwrap(); let b = Decimal::from_str(b).unwrap(); let result = a - b; - assert_eq!(c, result.to_string(), "{} - {}", a.to_string(), b.to_string()); + assert_eq!(c, result.to_string(), "{a} - {b}"); } let tests = &[ @@ -691,9 +688,9 @@ fn it_multiplies_decimals() { let a = Decimal::from_str(a).unwrap(); let b = Decimal::from_str(b).unwrap(); let result = a * b; - assert_eq!(c, result.to_string(), "{} * {}", a.to_string(), b.to_string()); + assert_eq!(c, result.to_string(), "{a} * {b}"); let result = b * a; - assert_eq!(c, result.to_string(), "{} * {}", b.to_string(), a.to_string()); + assert_eq!(c, result.to_string(), "{b} * {a}"); } let tests = &[ @@ -780,7 +777,7 @@ fn it_divides_decimals() { let a = Decimal::from_str(a).unwrap(); let b = Decimal::from_str(b).unwrap(); let result = a / b; - assert_eq!(c, result.to_string(), "{} / {}", a.to_string(), b.to_string()); + assert_eq!(c, result.to_string(), "{a} / {b}"); } let tests = &[ @@ -866,7 +863,7 @@ fn it_rems_decimals() { let b = Decimal::from_str(b).unwrap(); // a = qb + r let result = a % b; - assert_eq!(c, result.to_string(), "{} % {}", a.to_string(), b.to_string()); + assert_eq!(c, result.to_string(), "{a} % {b}"); } let tests = &[ @@ -916,8 +913,8 @@ fn it_eqs_decimals() { fn eq(a: &str, b: &str, c: bool) { let a = Decimal::from_str(a).unwrap(); let b = Decimal::from_str(b).unwrap(); - assert_eq!(c, a.eq(&b), "{} == {}", a.to_string(), b.to_string()); - assert_eq!(c, b.eq(&a), "{} == {}", b.to_string(), a.to_string()); + assert_eq!(c, a.eq(&b), "{a} == {b}"); + assert_eq!(c, b.eq(&a), "{b} == {a}"); } let tests = &[ @@ -946,13 +943,13 @@ fn it_cmps_decimals() { c, a.cmp(&b), "{} {} {}", - a.to_string(), + a, match c { Less => "<", Equal => "==", Greater => ">", }, - b.to_string() + b ); } @@ -2098,7 +2095,7 @@ fn it_floors_decimals() { ]; for &(a, expected) in tests { let a = Decimal::from_str(a).unwrap(); - assert_eq!(expected, a.floor().to_string(), "Failed flooring {}", a); + assert_eq!(expected, a.floor().to_string(), "Failed flooring {a}"); } } @@ -2114,7 +2111,7 @@ fn it_ceils_decimals() { ]; for &(a, expected) in tests { let a = Decimal::from_str(a).unwrap(); - assert_eq!(expected, a.ceil().to_string(), "Failed ceiling {}", a); + assert_eq!(expected, a.ceil().to_string(), "Failed ceiling {a}"); } } @@ -2172,16 +2169,12 @@ fn it_can_parse_from_i32() { assert_eq!( expected, parsed.to_string(), - "expected {} does not match parsed {}", - expected, - parsed + "expected {expected} does not match parsed {parsed}" ); assert_eq!( input.to_string(), parsed.to_string(), - "i32 to_string {} does not match parsed {}", - input, - parsed + "i32 to_string {input} does not match parsed {parsed}" ); } } @@ -2202,16 +2195,12 @@ fn it_can_parse_from_i64() { assert_eq!( expected, parsed.to_string(), - "expected {} does not match parsed {}", - expected, - parsed + "expected {expected} does not match parsed {parsed}" ); assert_eq!( input.to_string(), parsed.to_string(), - "i64 to_string {} does not match parsed {}", - input, - parsed + "i64 to_string {input} does not match parsed {parsed}" ); } } @@ -2279,7 +2268,7 @@ fn it_can_round_using_basic_midpoint_rules() { for &(input, strategy, expected) in tests { let a = Decimal::from_str(input).unwrap(); let b = a.round_dp_with_strategy(0, strategy); - assert_eq!(expected, b.to_string(), "{} > {} for {:?}", input, expected, strategy); + assert_eq!(expected, b.to_string(), "{input} > {expected} for {strategy:?}"); } } @@ -2617,10 +2606,10 @@ fn it_can_round_significant_figures() { let input = Decimal::from_str(input).unwrap(); let result = input.round_sf(sf); if let Some(expected) = expected { - assert!(result.is_some(), "Expected result for {}.round_sf({})", input, sf); - assert_eq!(expected, result.unwrap().to_string(), "{}.round_sf({})", input, sf); + assert!(result.is_some(), "Expected result for {input}.round_sf({sf})"); + assert_eq!(expected, result.unwrap().to_string(), "{input}.round_sf({sf})"); } else { - assert!(result.is_none(), "Unexpected result for {}.round_sf({})", input, sf); + assert!(result.is_none(), "Unexpected result for {input}.round_sf({sf})"); } } } @@ -2642,26 +2631,17 @@ fn it_can_round_significant_figures_with_strategy() { if let Some(expected) = expected { assert!( result.is_some(), - "Expected result for {}.round_sf_with_strategy({}, {:?})", - input, - sf, - strategy + "Expected result for {input}.round_sf_with_strategy({sf}, {strategy:?})" ); assert_eq!( expected, result.unwrap().to_string(), - "{}.round_sf_with_strategy({}, {:?})", - input, - sf, - strategy + "{input}.round_sf_with_strategy({sf}, {strategy:?})" ); } else { assert!( result.is_none(), - "Unexpected result for {}.round_sf_with_strategy({}, {:?})", - input, - sf, - strategy + "Unexpected result for {input}.round_sf_with_strategy({sf}, {strategy:?})" ); } } @@ -2863,7 +2843,7 @@ fn it_converts_to_i64() { for (input, expected) in tests { let input = Decimal::from_str(input).unwrap(); let actual = input.to_i64(); - assert_eq!(expected, actual, "Input: {}", input); + assert_eq!(expected, actual, "Input: {input}"); } } @@ -2977,14 +2957,12 @@ fn it_converts_from_f32() { assert_eq!( expected, Decimal::from_f32(input).unwrap().to_string(), - "from_f32({})", - input + "from_f32({input})" ); assert_eq!( expected, Decimal::try_from(input).unwrap().to_string(), - "try_from({})", - input + "try_from({input})" ); } } @@ -3018,8 +2996,7 @@ fn it_converts_from_f32_retaining_bits() { assert_eq!( expected, Decimal::from_f32_retain(input).unwrap().to_string(), - "from_f32_retain({})", - input + "from_f32_retain({input})" ); } } @@ -3046,14 +3023,12 @@ fn it_converts_from_f64() { assert_eq!( expected, Decimal::from_f64(input).unwrap().to_string(), - "from_f64({})", - input + "from_f64({input})" ); assert_eq!( expected, Decimal::try_from(input).unwrap().to_string(), - "try_from({})", - input + "try_from({input})" ); } } @@ -3087,8 +3062,7 @@ fn it_converts_from_f64_retaining_bits() { assert_eq!( expected, Decimal::from_f64_retain(input).unwrap().to_string(), - "from_f64_retain({})", - input + "from_f64_retain({input})" ); } } @@ -3183,7 +3157,7 @@ fn it_can_parse_exact_highly_significant_numbers() { (".00000000000000000000000000001", Err(Error::Underflow)), (".10000000000000000000000000000", Err(Error::Underflow)), ]; - for &(value, ref expected) in tests.into_iter() { + for &(value, ref expected) in tests.iter() { let actual = Decimal::from_str_exact(value).map(|d| d.to_string()); assert_eq!(*expected, actual); } @@ -3207,7 +3181,7 @@ fn it_can_parse_alternative_formats() { #[test] fn it_can_parse_fractional_numbers_with_underscore_separators() { let a = Decimal::from_str("0.1_23_456").unwrap(); - assert_eq!(a.is_sign_negative(), false); + assert!(!a.is_sign_negative()); assert_eq!(a.scale(), 6); assert_eq!("0.123456", a.to_string()); } @@ -3215,7 +3189,7 @@ fn it_can_parse_fractional_numbers_with_underscore_separators() { #[test] fn it_can_parse_numbers_with_underscore_separators_before_decimal_point() { let a = Decimal::from_str("1_234.56").unwrap(); - assert_eq!(a.is_sign_negative(), false); + assert!(!a.is_sign_negative()); assert_eq!(a.scale(), 2); assert_eq!("1234.56", a.to_string()); } @@ -3247,8 +3221,7 @@ fn it_can_reject_invalid_formats() { for &value in tests { assert!( Decimal::from_str(value).is_err(), - "This succeeded unexpectedly: {}", - value + "This succeeded unexpectedly: {value}" ); } } @@ -3265,7 +3238,7 @@ fn it_can_reject_large_numbers_with_panic() { ]; for &value in tests { if let Ok(out) = Decimal::from_str(value) { - panic!("Unexpectedly parsed {} into {}", value, out) + panic!("Unexpectedly parsed {value} into {out}") } } } @@ -3359,9 +3332,7 @@ fn it_can_parse_different_radix() { assert_eq!( expected, result.unwrap().to_string(), - "Original input: {} radix {}", - input, - radix + "Original input: {input} radix {radix}" ); } } @@ -3373,7 +3344,7 @@ fn it_can_calculate_signum() { for &(input, expected) in tests { let input = Decimal::from_str(input).unwrap(); - assert_eq!(expected, input.signum().to_i32().unwrap(), "Input: {}", input); + assert_eq!(expected, input.signum().to_i32().unwrap(), "Input: {input}"); } } @@ -3394,9 +3365,7 @@ fn it_can_calculate_abs_sub() { assert_eq!( expected, input1.abs_sub(&input2).to_i32().unwrap(), - "Input: {} {}", - input1, - input2 + "Input: {input1} {input2}" ); } } @@ -3630,7 +3599,7 @@ fn test_is_integer() { ]; for &(raw, integer) in tests { let value = Decimal::from_str(raw).unwrap(); - assert_eq!(value.is_integer(), integer, "value: {}", raw) + assert_eq!(value.is_integer(), integer, "value: {raw}") } } @@ -4655,6 +4624,7 @@ mod proptest { } #[cfg(feature = "rocket-traits")] +#[allow(clippy::disallowed_names)] mod rocket { use crate::Decimal; use rocket::form::{Form, FromForm}; From deeb242f3832e15833bf21791a53ecf79234553f Mon Sep 17 00:00:00 2001 From: Paul Mason Date: Sun, 16 Jul 2023 17:05:17 -0700 Subject: [PATCH 07/37] Fixes issue with truncating implicitly rounding in some cases (#600) --- src/ops/array.rs | 30 ++++----------- tests/decimal_tests.rs | 86 ++++++++++++++++++++++++++++++++++-------- 2 files changed, 78 insertions(+), 38 deletions(-) diff --git a/src/ops/array.rs b/src/ops/array.rs index 2840e4b1..ed3fce6d 100644 --- a/src/ops/array.rs +++ b/src/ops/array.rs @@ -2,8 +2,13 @@ use crate::constants::{MAX_PRECISION_U32, 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 -#[inline(always)] +#[inline] pub(crate) fn rescale_internal(value: &mut [u32; 3], value_scale: &mut u32, new_scale: u32) { + rescale::(value, value_scale, new_scale); +} + +#[inline(always)] +fn rescale(value: &mut [u32; 3], value_scale: &mut u32, new_scale: u32) { if *value_scale == new_scale { // Nothing to do return; @@ -32,7 +37,7 @@ pub(crate) fn rescale_internal(value: &mut [u32; 3], value_scale: &mut u32, new_ // Any remainder is discarded if diff > 0 still (i.e. lost precision) remainder = div_by_u32(value, 10); } - if remainder >= 5 { + if ROUND && remainder >= 5 { for part in value.iter_mut() { let digit = u64::from(*part) + 1u64; remainder = if digit > U32_MASK { 1 } else { 0 }; @@ -60,26 +65,7 @@ pub(crate) fn rescale_internal(value: &mut [u32; 3], value_scale: &mut u32, new_ #[inline] pub(crate) fn truncate_internal(value: &mut [u32; 3], value_scale: &mut u32, desired_scale: u32) { - if *value_scale <= desired_scale { - // Nothing to do, we're already at the desired scale (or less) - return; - } - if is_all_zero(value) { - *value_scale = desired_scale; - return; - } - while *value_scale > desired_scale { - // We're removing precision, so we don't care about handling the remainder - if *value_scale < 10 { - let adjustment = *value_scale - desired_scale; - div_by_u32(value, POWERS_10[adjustment as usize]); - *value_scale = desired_scale; - } else { - div_by_u32(value, POWERS_10[9]); - // Only 9 as this array starts with 1 - *value_scale -= 9; - } - } + rescale::(value, value_scale, desired_scale); } #[cfg(feature = "legacy-ops")] diff --git a/tests/decimal_tests.rs b/tests/decimal_tests.rs index c106ea88..1391cdbc 100644 --- a/tests/decimal_tests.rs +++ b/tests/decimal_tests.rs @@ -2674,24 +2674,78 @@ fn it_can_trunc() { #[test] fn it_can_trunc_with_scale() { let cmp = Decimal::from_str("1.2345").unwrap(); - assert_eq!(Decimal::from_str("1.23450").unwrap().trunc_with_scale(4), cmp); - assert_eq!(Decimal::from_str("1.234500001").unwrap().trunc_with_scale(4), cmp); - assert_eq!(Decimal::from_str("1.23451").unwrap().trunc_with_scale(4), cmp); - assert_eq!(Decimal::from_str("1.23454").unwrap().trunc_with_scale(4), cmp); - assert_eq!(Decimal::from_str("1.23455").unwrap().trunc_with_scale(4), cmp); - assert_eq!(Decimal::from_str("1.23456").unwrap().trunc_with_scale(4), cmp); - assert_eq!(Decimal::from_str("1.23459").unwrap().trunc_with_scale(4), cmp); - assert_eq!(Decimal::from_str("1.234599999").unwrap().trunc_with_scale(4), cmp); + let tests = [ + "1.23450", + "1.234500001", + "1.23451", + "1.23454", + "1.23455", + "1.23456", + "1.23459", + "1.234599999", + ]; + for test in tests { + assert_eq!( + Decimal::from_str(test).unwrap().trunc_with_scale(4), + cmp, + "Original: {}", + test + ); + } let cmp = Decimal::from_str("-1.2345").unwrap(); - assert_eq!(Decimal::from_str("-1.23450").unwrap().trunc_with_scale(4), cmp); - assert_eq!(Decimal::from_str("-1.234500001").unwrap().trunc_with_scale(4), cmp); - assert_eq!(Decimal::from_str("-1.23451").unwrap().trunc_with_scale(4), cmp); - assert_eq!(Decimal::from_str("-1.23454").unwrap().trunc_with_scale(4), cmp); - assert_eq!(Decimal::from_str("-1.23455").unwrap().trunc_with_scale(4), cmp); - assert_eq!(Decimal::from_str("-1.23456").unwrap().trunc_with_scale(4), cmp); - assert_eq!(Decimal::from_str("-1.23459").unwrap().trunc_with_scale(4), cmp); - assert_eq!(Decimal::from_str("-1.234599999").unwrap().trunc_with_scale(4), cmp); + let tests = [ + "-1.23450", + "-1.234500001", + "-1.23451", + "-1.23454", + "-1.23455", + "-1.23456", + "-1.23459", + "-1.234599999", + ]; + for test in tests { + assert_eq!( + Decimal::from_str(test).unwrap().trunc_with_scale(4), + cmp, + "Original: {}", + test + ); + } + + // Complex cases + let cmp = Decimal::from_str("0.5156").unwrap(); + let tests = [ + "0.51560089", + "0.515600893", + "0.5156008936", + "0.51560089369", + "0.515600893691", + "0.5156008936910", + "0.51560089369101", + "0.515600893691016", + "0.5156008936910161", + "0.51560089369101613", + "0.515600893691016134", + "0.5156008936910161349", + "0.51560089369101613494", + "0.515600893691016134941", + "0.5156008936910161349411", + "0.51560089369101613494115", + "0.515600893691016134941151", + "0.5156008936910161349411515", + "0.51560089369101613494115158", + "0.515600893691016134941151581", + "0.5156008936910161349411515818", + ]; + for test in tests { + assert_eq!( + Decimal::from_str(test).unwrap().trunc_with_scale(4), + cmp, + "Original: {}", + test + ); + } } #[test] From 091d715e60319d41b5f58e2377c7c00de14be3c8 Mon Sep 17 00:00:00 2001 From: Mikhail Katychev Date: Sun, 16 Jul 2023 19:49:13 -0500 Subject: [PATCH 08/37] added new rkyv (#597) --- Cargo.toml | 7 +++---- src/decimal.rs | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 429f1012..b6a01c5e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,7 +26,6 @@ all-features = true arbitrary = { default-features = false, optional = true, version = "1.0" } arrayvec = { default-features = false, version = "0.7" } borsh = { default-features = false, optional = true, version = "0.10.0" } -bytecheck = { default-features= false, optional = true, version = "0.6" } byteorder = { default-features = false, optional = true, version = "1.0" } bytes = { default-features = false, optional = true, version = "1.0" } diesel1 = { default-features = false, optional = true, package = "diesel", version = "1.0" } @@ -36,7 +35,7 @@ num-traits = { default-features = false, features = ["i128"], version = "0.2" } postgres = { default-features = false, optional = true, version = "0.19" } proptest = { default-features = false, optional = true, features = ["std"], version = "1.0" } rand = { default-features = false, optional = true, version = "0.8" } -rkyv = { default-features = false, features = ["size_32", "std"], optional = true, version = "0.7" } +rkyv = { default-features = false, features = ["size_32", "std"], optional = true, version = "0.7.42" } rocket = { default-features = false, optional = true, version = "0.5.0-rc.1" } serde = { default-features = false, optional = true, version = "1.0" } serde_json = { default-features = false, optional = true, version = "1.0" } @@ -75,7 +74,7 @@ ndarray = ["dep:ndarray"] proptest = ["dep:proptest"] rand = ["dep:rand"] rkyv = ["dep:rkyv"] -rkyv-safe = ["dep:bytecheck", "rkyv/validation"] +rkyv-safe = ["rkyv/validation"] rocket-traits = ["dep:rocket"] rust-fuzz = ["dep:arbitrary"] serde = ["dep:serde"] @@ -86,7 +85,7 @@ serde-str = ["serde-with-str"] serde-with-arbitrary-precision = ["serde", "serde_json/arbitrary_precision", "serde_json/std"] serde-with-float = ["serde"] serde-with-str = ["serde"] -std = ["arrayvec/std", "borsh?/std", "bytecheck?/std", "byteorder?/std", "bytes?/std", "rand?/std", "rkyv?/std", "serde?/std", "serde_json?/std"] +std = ["arrayvec/std", "borsh?/std", "byteorder?/std", "bytes?/std", "rand?/std", "rkyv?/std", "serde?/std", "serde_json?/std"] tokio-pg = ["db-tokio-postgres"] # Backwards compatability [workspace] diff --git a/src/decimal.rs b/src/decimal.rs index 8730d2c8..191d31fb 100644 --- a/src/decimal.rs +++ b/src/decimal.rs @@ -120,7 +120,7 @@ pub struct UnpackedDecimal { archive(compare(PartialEq)), archive_attr(derive(Clone, Copy, Debug)) )] -#[cfg_attr(feature = "rkyv-safe", archive_attr(derive(bytecheck::CheckBytes)))] +#[cfg_attr(feature = "rkyv-safe", archive(check_bytes))] pub struct Decimal { // Bits 0-15: unused // Bits 16-23: Contains "e", a value between 0-28 that indicates the scale From 67efda988c1f6679a8b009c2983f0805c6f232aa Mon Sep 17 00:00:00 2001 From: Paul Mason Date: Sat, 29 Jul 2023 16:38:31 -0700 Subject: [PATCH 09/37] Version 1.31.0 (#601) --- .buildnumber | 2 +- CHANGELOG.md | 14 ++++++++++++++ Cargo.toml | 18 +++++++++--------- README.md | 4 ++-- fuzz/Cargo.toml | 2 +- macros/Cargo.toml | 4 ++-- 6 files changed, 29 insertions(+), 15 deletions(-) diff --git a/.buildnumber b/.buildnumber index 5a95707f..78624c49 100644 --- a/.buildnumber +++ b/.buildnumber @@ -1,3 +1,3 @@ 1 -30 +31 0 diff --git a/CHANGELOG.md b/CHANGELOG.md index c13612be..62c94849 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Version History +## 1.31.0 + +### Fixed + +* Fixes an issue with `trunc_with_scale` implicitly rounding in some scenarios ([#600](https://github.com/paupino/rust-decimal/pull/600)) + +### Changed + +* Various dependency features were updated. + +### Credit + +Thank you to [@mkatychev](https://github.com/mkatychev) for your contribution this release. + ## 1.30.0 As the minor releases for Rust Decimal are getting smaller, I'll be looking at formally starting version 2 of the diff --git a/Cargo.toml b/Cargo.toml index b6a01c5e..4c2ff876 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,3 @@ -[[bench]] -harness = false -name = "comparison" -path = "benches/comparison.rs" - [package] authors = ["Paul Mason "] build = "build.rs" @@ -17,7 +12,7 @@ name = "rust_decimal" readme = "./README.md" repository = "https://github.com/paupino/rust-decimal" rust-version = "1.60" -version = "1.30.0" +version = "1.31.0" [package.metadata.docs.rs] all-features = true @@ -29,14 +24,14 @@ borsh = { default-features = false, optional = true, version = "0.10.0" } byteorder = { default-features = false, optional = true, version = "1.0" } bytes = { default-features = false, optional = true, version = "1.0" } diesel1 = { default-features = false, optional = true, package = "diesel", version = "1.0" } -diesel2 = { default-features = false, optional = true, package = "diesel", version = "2.0" } +diesel2 = { default-features = false, optional = true, package = "diesel", version = "2.1" } ndarray = { default-features = false, optional = true, version = "0.15.6" } num-traits = { default-features = false, features = ["i128"], version = "0.2" } postgres = { default-features = false, optional = true, version = "0.19" } proptest = { default-features = false, optional = true, features = ["std"], version = "1.0" } rand = { default-features = false, optional = true, version = "0.8" } rkyv = { default-features = false, features = ["size_32", "std"], optional = true, version = "0.7.42" } -rocket = { default-features = false, optional = true, version = "0.5.0-rc.1" } +rocket = { default-features = false, optional = true, version = "0.5.0-rc.3" } serde = { default-features = false, optional = true, version = "1.0" } serde_json = { default-features = false, optional = true, version = "1.0" } tokio-postgres = { default-features = false, optional = true, version = "0.7" } @@ -44,7 +39,7 @@ tokio-postgres = { default-features = false, optional = true, version = "0.7" } [dev-dependencies] bincode = { default-features = false, version = "1.0" } bytes = { default-features = false, version = "1.0" } -criterion = { default-features = false, version = "0.4.0" } +criterion = { default-features = false, version = "0.5" } csv = "1" futures = { default-features = false, version = "0.3" } rand = { default-features = false, features = ["getrandom"], version = "0.8" } @@ -88,5 +83,10 @@ serde-with-str = ["serde"] std = ["arrayvec/std", "borsh?/std", "byteorder?/std", "bytes?/std", "rand?/std", "rkyv?/std", "serde?/std", "serde_json?/std"] tokio-pg = ["db-tokio-postgres"] # Backwards compatability +[[bench]] +harness = false +name = "comparison" +path = "benches/comparison.rs" + [workspace] members = [".", "./macros"] diff --git a/README.md b/README.md index d11a866d..660ffc42 100644 --- a/README.md +++ b/README.md @@ -29,8 +29,8 @@ Alternatively, you can edit your `Cargo.toml` directly and run `cargo update`: ```toml [dependencies] -rust_decimal = "1.30" -rust_decimal_macros = "1.30" +rust_decimal = "1.31" +rust_decimal_macros = "1.31" ``` ## Usage diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index c0330c97..d3e37a06 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -21,7 +21,7 @@ authors = ["Automatically generated"] edition = "2021" name = "rust-decimal-fuzz" publish = false -version = "1.30.0" +version = "1.31.0" [package.metadata] cargo-fuzz = true diff --git a/macros/Cargo.toml b/macros/Cargo.toml index c71654bc..03d5d7b9 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rust_decimal_macros" -version = "1.30.0" +version = "1.31.0" authors = ["Paul Mason "] edition = "2021" description = "Shorthand macros to assist creating Decimal types." @@ -12,7 +12,7 @@ categories = ["science","data-structures"] license = "MIT" [dependencies] -rust_decimal = { path = "..", version = "1.29", default-features = false } +rust_decimal = { path = "..", default-features = false } quote = "1.0" [dev-dependencies] From 29d3e2e8b789051f209438261cb4ef824cddd2a9 Mon Sep 17 00:00:00 2001 From: Paul Mason Date: Sat, 29 Jul 2023 16:40:14 -0700 Subject: [PATCH 10/37] Specify version on macro dependency --- macros/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/macros/Cargo.toml b/macros/Cargo.toml index 03d5d7b9..3dc1302d 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -12,7 +12,7 @@ categories = ["science","data-structures"] license = "MIT" [dependencies] -rust_decimal = { path = "..", default-features = false } +rust_decimal = { path = "..", version = "1.31", default-features = false } quote = "1.0" [dev-dependencies] From a6d5caf0328842e612787b30239abefdeda48951 Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Fri, 18 Aug 2023 21:21:49 +0200 Subject: [PATCH 11/37] Drop unnecessary byteorder dependency (#603) --- Cargo.toml | 7 +++---- src/postgres/driver.rs | 21 +++++++++++++-------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4c2ff876..c1ebd238 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,6 @@ all-features = true arbitrary = { default-features = false, optional = true, version = "1.0" } arrayvec = { default-features = false, version = "0.7" } borsh = { default-features = false, optional = true, version = "0.10.0" } -byteorder = { default-features = false, optional = true, version = "1.0" } bytes = { default-features = false, optional = true, version = "1.0" } diesel1 = { default-features = false, optional = true, package = "diesel", version = "1.0" } diesel2 = { default-features = false, optional = true, package = "diesel", version = "2.1" } @@ -60,8 +59,8 @@ db-diesel1-mysql = ["diesel1/mysql", "std"] db-diesel1-postgres = ["diesel1/postgres", "std"] db-diesel2-mysql = ["diesel2/mysql", "std"] db-diesel2-postgres = ["diesel2/postgres", "std"] -db-postgres = ["dep:byteorder", "dep:bytes", "dep:postgres", "std"] -db-tokio-postgres = ["dep:byteorder", "dep:bytes", "dep:postgres", "std", "dep:tokio-postgres"] +db-postgres = ["dep:bytes", "dep:postgres", "std"] +db-tokio-postgres = ["dep:bytes", "dep:postgres", "std", "dep:tokio-postgres"] legacy-ops = [] maths = [] maths-nopanic = ["maths"] @@ -80,7 +79,7 @@ serde-str = ["serde-with-str"] serde-with-arbitrary-precision = ["serde", "serde_json/arbitrary_precision", "serde_json/std"] serde-with-float = ["serde"] serde-with-str = ["serde"] -std = ["arrayvec/std", "borsh?/std", "byteorder?/std", "bytes?/std", "rand?/std", "rkyv?/std", "serde?/std", "serde_json?/std"] +std = ["arrayvec/std", "borsh?/std", "bytes?/std", "rand?/std", "rkyv?/std", "serde?/std", "serde_json?/std"] tokio-pg = ["db-tokio-postgres"] # Backwards compatability [[bench]] diff --git a/src/postgres/driver.rs b/src/postgres/driver.rs index eb94a4a6..1aafd788 100644 --- a/src/postgres/driver.rs +++ b/src/postgres/driver.rs @@ -1,9 +1,14 @@ use crate::postgres::common::*; use crate::Decimal; -use byteorder::{BigEndian, ReadBytesExt}; use bytes::{BufMut, BytesMut}; use postgres::types::{to_sql_checked, FromSql, IsNull, ToSql, Type}; -use std::io::Cursor; +use std::io::{Cursor, Read}; + +fn read_two_bytes(cursor: &mut Cursor<&[u8]>) -> std::io::Result<[u8; 2]> { + let mut result = [0; 2]; + cursor.read_exact(&mut result)?; + Ok(result) +} impl<'a> FromSql<'a> for Decimal { // Decimals are represented as follows: @@ -61,17 +66,17 @@ impl<'a> FromSql<'a> for Decimal { fn from_sql(_: &Type, raw: &[u8]) -> Result> { 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 - let sign = raw.read_u16::()?; + let num_groups = u16::from_be_bytes(read_two_bytes(&mut raw)?); + let weight = i16::from_be_bytes(read_two_bytes(&mut raw)?); // 10000^weight + // Sign: 0x0000 = positive, 0x4000 = negative, 0xC000 = NaN + let sign = u16::from_be_bytes(read_two_bytes(&mut raw)?); // Number of digits (in base 10) to print after decimal separator - let scale = raw.read_u16::()?; + let scale = u16::from_be_bytes(read_two_bytes(&mut raw)?); // Read all of the groups let mut groups = Vec::new(); for _ in 0..num_groups as usize { - groups.push(raw.read_u16::()?); + groups.push(u16::from_be_bytes(read_two_bytes(&mut raw)?)); } Ok(Self::from_postgres(PostgresDecimal { From 951512d003a4b65724d6074a15630534994b40e6 Mon Sep 17 00:00:00 2001 From: Paul Mason Date: Wed, 23 Aug 2023 07:49:28 -0700 Subject: [PATCH 12/37] Fixes issue with is_integer failing on decimal bounds (#605) --- src/decimal.rs | 2 +- tests/decimal_tests.rs | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/decimal.rs b/src/decimal.rs index 191d31fb..e057d4f7 100644 --- a/src/decimal.rs +++ b/src/decimal.rs @@ -768,7 +768,7 @@ impl Decimal { let mut scale = scale; while scale > 0 { let remainder = if scale > 9 { - scale -= 10; + scale -= 9; ops::array::div_by_u32(&mut bits, POWERS_10[9]) } else { let power = POWERS_10[scale as usize]; diff --git a/tests/decimal_tests.rs b/tests/decimal_tests.rs index 1391cdbc..fd1d6775 100644 --- a/tests/decimal_tests.rs +++ b/tests/decimal_tests.rs @@ -3650,6 +3650,10 @@ fn test_is_integer() { ("1.1", false), ("3.1415926535897932384626433833", false), ("3.0000000000000000000000000000", true), + ("0.400000000", false), + ("0.4000000000", false), + ("0.4000000000000000000", false), + ("0.4000000000000000001", false), ]; for &(raw, integer) in tests { let value = Decimal::from_str(raw).unwrap(); From 03ce4421f74b7025ccb49dd1b8a05d97df0b8db8 Mon Sep 17 00:00:00 2001 From: Paul Mason Date: Wed, 23 Aug 2023 08:11:00 -0700 Subject: [PATCH 13/37] Version 1.32 (#606) --- .buildnumber | 2 +- CHANGELOG.md | 14 ++++++++++++++ Cargo.toml | 2 +- README.md | 4 ++-- fuzz/Cargo.toml | 2 +- macros/Cargo.toml | 2 +- 6 files changed, 20 insertions(+), 6 deletions(-) diff --git a/.buildnumber b/.buildnumber index 78624c49..76e28878 100644 --- a/.buildnumber +++ b/.buildnumber @@ -1,3 +1,3 @@ 1 -31 +32 0 diff --git a/CHANGELOG.md b/CHANGELOG.md index 62c94849..e866fbdd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Version History +## 1.32.0 + +### Fixed + +* Fixes an issue with `is_integer` returning incorrect results for mantissa's 10^n where n >= 10. ([#605](https://github.com/paupino/rust-decimal/pull/605)) + +### Changed + +* `byteorder` is no longer required as a dependency for the postgres feature. ([#603](https://github.com/paupino/rust-decimal/pull/603)) + +### Credit + +Thank you to [@psychon](https://github.com/psychon) for your contribution! + ## 1.31.0 ### Fixed diff --git a/Cargo.toml b/Cargo.toml index c1ebd238..5373244a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ name = "rust_decimal" readme = "./README.md" repository = "https://github.com/paupino/rust-decimal" rust-version = "1.60" -version = "1.31.0" +version = "1.32.0" [package.metadata.docs.rs] all-features = true diff --git a/README.md b/README.md index 660ffc42..aa4c39fa 100644 --- a/README.md +++ b/README.md @@ -29,8 +29,8 @@ Alternatively, you can edit your `Cargo.toml` directly and run `cargo update`: ```toml [dependencies] -rust_decimal = "1.31" -rust_decimal_macros = "1.31" +rust_decimal = "1.32" +rust_decimal_macros = "1.32" ``` ## Usage diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index d3e37a06..c36b3765 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -21,7 +21,7 @@ authors = ["Automatically generated"] edition = "2021" name = "rust-decimal-fuzz" publish = false -version = "1.31.0" +version = "1.32.0" [package.metadata] cargo-fuzz = true diff --git a/macros/Cargo.toml b/macros/Cargo.toml index 3dc1302d..0b02f445 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rust_decimal_macros" -version = "1.31.0" +version = "1.32.0" authors = ["Paul Mason "] edition = "2021" description = "Shorthand macros to assist creating Decimal types." From f18e57c022ca309dacc5fbeaa658b613a565a06c Mon Sep 17 00:00:00 2001 From: Paul Mason Date: Fri, 8 Sep 2023 19:42:35 -0700 Subject: [PATCH 14/37] Introduce breaking change check (#608) --- .github/workflows/main.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 329fbdab..e9929ddf 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -163,3 +163,12 @@ jobs: - name: Check build run: cargo check --workspace + + breaking_changes: + name: Check for breaking changes + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Check semver + uses: obi1kenobi/cargo-semver-checks-action@v2 From aacdefd827cb1be8a92ae50f37c74b6acbf3b50f Mon Sep 17 00:00:00 2001 From: Paul Mason Date: Sat, 9 Sep 2023 13:59:05 -0700 Subject: [PATCH 15/37] Remove semver checks from build and put into makefile (#609) --- .github/workflows/main.yml | 9 --------- make/utils.toml | 5 +++++ 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e9929ddf..329fbdab 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -163,12 +163,3 @@ jobs: - name: Check build run: cargo check --workspace - - breaking_changes: - name: Check for breaking changes - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - - name: Check semver - uses: obi1kenobi/cargo-semver-checks-action@v2 diff --git a/make/utils.toml b/make/utils.toml index ba83b8de..2f5e9984 100644 --- a/make/utils.toml +++ b/make/utils.toml @@ -29,3 +29,8 @@ run_task = "propagate-version" private = true script_runner = "@rust" script = { file = "${CARGO_MAKE_WORKING_DIRECTORY}/make/scripts/version.rs", absolute_path = true } + +[tasks.semver-check] +install_crate = "cargo-semver-checks" +command = "cargo" +args = ["semver-checks"] From 1686b69fb6fc43018397cec69f99023ee6081d08 Mon Sep 17 00:00:00 2001 From: Paul Mason Date: Fri, 10 Nov 2023 09:34:35 -0800 Subject: [PATCH 16/37] Fixes issue whereby scale 29 is required however is optimized away (#619) * Fixes issue whereby scale 29 is required however is optimized away * Disable test for legacy ops --- src/ops/add.rs | 6 ++++-- tests/decimal_tests.rs | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/src/ops/add.rs b/src/ops/add.rs index 52ba675f..9a0c5bd9 100644 --- a/src/ops/add.rs +++ b/src/ops/add.rs @@ -1,4 +1,6 @@ -use crate::constants::{MAX_I32_SCALE, POWERS_10, SCALE_MASK, SCALE_SHIFT, SIGN_MASK, U32_MASK, U32_MAX}; +use crate::constants::{ + MAX_I32_SCALE, MAX_PRECISION_U32, POWERS_10, SCALE_MASK, SCALE_SHIFT, SIGN_MASK, U32_MASK, U32_MAX, +}; use crate::decimal::{CalculationResult, Decimal}; use crate::ops::common::{Buf24, Dec64}; @@ -261,7 +263,7 @@ fn unaligned_add( rescale_factor -= MAX_I32_SCALE; - if tmp64 > U32_MAX { + if tmp64 > U32_MAX || scale > MAX_PRECISION_U32 { break; } else { high = tmp64 as u32; diff --git a/tests/decimal_tests.rs b/tests/decimal_tests.rs index fd1d6775..1eca9b17 100644 --- a/tests/decimal_tests.rs +++ b/tests/decimal_tests.rs @@ -4746,4 +4746,38 @@ mod issues { assert!(c.is_some()); assert_eq!("-429391.87200000000002327170816", c.unwrap().to_string()) } + + #[test] + #[cfg(not(feature = "legacy-ops"))] // I will deprecate this feature/behavior in an upcoming release + fn issue_618_rescaling_overflow() { + fn assert_result(scale: u32, v1: Decimal, v2: Decimal) { + assert_eq!(scale, v1.scale(), "initial scale: {scale}"); + let result1 = v1 + -v2; + assert_eq!( + result1.to_string(), + "-0.0999999999999999999999999999", + "a + -b : {scale}" + ); + assert_eq!(28, result1.scale(), "a + -b : {scale}"); + let result2 = v1 - v2; + assert_eq!( + result2.to_string(), + "-0.0999999999999999999999999999", + "a - b : {scale}" + ); + assert_eq!(28, result2.scale(), "a - b : {scale}"); + } + + let mut a = Decimal::from_str("0.0000000000000000000000000001").unwrap(); + let b = Decimal::from_str("0.1").unwrap(); + assert_result(28, a, b); + + // Try at a new scale (this works) + a.rescale(30); + assert_result(30, a, b); + + // And finally the scale causing an issue + a.rescale(29); + assert_result(29, a, b); + } } From ede308d407291759f73b9eb5aabc165a768b7e8b Mon Sep 17 00:00:00 2001 From: Paul Mason Date: Mon, 13 Nov 2023 07:38:44 -0800 Subject: [PATCH 17/37] Upgrade Borsh to resolve vulnerability (#621) * Update to 1.1 borsh * Fixes Borsh upgrade --------- Co-authored-by: John Barker --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 5373244a..7d160f5c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ all-features = true [dependencies] arbitrary = { default-features = false, optional = true, version = "1.0" } arrayvec = { default-features = false, version = "0.7" } -borsh = { default-features = false, optional = true, version = "0.10.0" } +borsh = { default-features = false, features = ["derive", "unstable__schema"], optional = true, version = "1.1.1" } bytes = { default-features = false, optional = true, version = "1.0" } diesel1 = { default-features = false, optional = true, package = "diesel", version = "1.0" } diesel2 = { default-features = false, optional = true, package = "diesel", version = "2.1" } From 6cfccf3980fe204ecd333370fefb596d1b5127ae Mon Sep 17 00:00:00 2001 From: gaius <61444500+gai6948@users.noreply.github.com> Date: Tue, 14 Nov 2023 08:57:48 +0800 Subject: [PATCH 18/37] deserializes empty string into None Option (#607) Co-authored-by: Paul Mason --- src/serde.rs | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/serde.rs b/src/serde.rs index c4237727..993b0081 100644 --- a/src/serde.rs +++ b/src/serde.rs @@ -426,7 +426,22 @@ impl<'de> serde::de::Visitor<'de> for OptionDecimalStrVisitor { where D: serde::de::Deserializer<'de>, { - d.deserialize_str(DecimalVisitor).map(Some) + d.deserialize_str(Self) + } + + fn visit_str(self, v: &str) -> Result + where + E: serde::de::Error, + { + match v.is_empty() { + true => Ok(None), + false => { + let d = Decimal::from_str(v) + .or_else(|_| Decimal::from_scientific(v)) + .map_err(serde::de::Error::custom)?; + Ok(Some(d)) + } + } } } @@ -843,6 +858,12 @@ mod test { let deserialized: StringExample = serde_json::from_str(r#"{"value":null}"#).unwrap(); assert_eq!(deserialized.value, original.value); assert!(deserialized.value.is_none()); + + // Empty string deserialization tests + let original = StringExample { value: None }; + let deserialized: StringExample = serde_json::from_str(r#"{"value":""}"#).unwrap(); + assert_eq!(deserialized.value, original.value); + assert!(deserialized.value.is_none()); } #[test] From 1c80137e170940b03abc3e338c69c5ca98df5f34 Mon Sep 17 00:00:00 2001 From: Paul Mason Date: Mon, 13 Nov 2023 17:10:36 -0800 Subject: [PATCH 19/37] Version 1.33.0 (#622) --- .buildnumber | 2 +- CHANGELOG.md | 16 ++++++++++++++++ Cargo.toml | 2 +- README.md | 4 ++-- fuzz/Cargo.toml | 2 +- macros/Cargo.toml | 2 +- 6 files changed, 22 insertions(+), 6 deletions(-) diff --git a/.buildnumber b/.buildnumber index 76e28878..d802d842 100644 --- a/.buildnumber +++ b/.buildnumber @@ -1,3 +1,3 @@ 1 -32 +33 0 diff --git a/CHANGELOG.md b/CHANGELOG.md index e866fbdd..a026c5ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ # Version History +## 1.33.0 + +### Fixed + +* Fixes an issue when adding/subtracting two `Decimal`s when one has a scale of 29. ([#619](https://github.com/paupino/rust-decimal/pull/619)) +* An empty string will be parsed as `None` during JSON deserialization instead of panicking. ([#607](https://github.com/paupino/rust-decimal/pull/607)) + + +### Changed + +* Upgrades `borsh` to version `1.1` as a result of a [security advisory](https://rustsec.org/advisories/RUSTSEC-2023-0033.html). ([#621](https://github.com/paupino/rust-decimal/pull/621)) + +### Credit + +Thank you to [@gai6948](https://github.com/gai6948) for their contribution! Also thank you to all of those that pushed for the security advisory changes. + ## 1.32.0 ### Fixed diff --git a/Cargo.toml b/Cargo.toml index 7d160f5c..a18051fa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ name = "rust_decimal" readme = "./README.md" repository = "https://github.com/paupino/rust-decimal" rust-version = "1.60" -version = "1.32.0" +version = "1.33.0" [package.metadata.docs.rs] all-features = true diff --git a/README.md b/README.md index aa4c39fa..c70280b2 100644 --- a/README.md +++ b/README.md @@ -29,8 +29,8 @@ Alternatively, you can edit your `Cargo.toml` directly and run `cargo update`: ```toml [dependencies] -rust_decimal = "1.32" -rust_decimal_macros = "1.32" +rust_decimal = "1.33" +rust_decimal_macros = "1.33" ``` ## Usage diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index c36b3765..4b4a65fb 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -21,7 +21,7 @@ authors = ["Automatically generated"] edition = "2021" name = "rust-decimal-fuzz" publish = false -version = "1.32.0" +version = "1.33.0" [package.metadata] cargo-fuzz = true diff --git a/macros/Cargo.toml b/macros/Cargo.toml index 0b02f445..1cfa6de1 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rust_decimal_macros" -version = "1.32.0" +version = "1.33.0" authors = ["Paul Mason "] edition = "2021" description = "Shorthand macros to assist creating Decimal types." From c3802dba39fc467669030ee279fad22f1e44912f Mon Sep 17 00:00:00 2001 From: Paul Mason Date: Thu, 16 Nov 2023 15:08:09 -0800 Subject: [PATCH 20/37] Fixes precision issue with to_f64 with some numbers without fractions (#625) * Fixes precision issue with to_f64 with some numbers without fractions * Sign should not be applied to early for to_f64 --- src/decimal.rs | 21 ++++++++++++++++++--- src/serde.rs | 4 ++-- tests/decimal_tests.rs | 24 ++++++++++++++++++++++++ 3 files changed, 44 insertions(+), 5 deletions(-) diff --git a/src/decimal.rs b/src/decimal.rs index e057d4f7..45f5661f 100644 --- a/src/decimal.rs +++ b/src/decimal.rs @@ -2354,7 +2354,7 @@ impl ToPrimitive for Decimal { let integer = self.to_i128(); integer.map(|i| i as f64) } else { - let sign: f64 = if self.is_sign_negative() { -1.0 } else { 1.0 }; + let neg = self.is_sign_negative(); let mut mantissa: u128 = self.lo.into(); mantissa |= (self.mid as u128) << 32; mantissa |= (self.hi as u128) << 64; @@ -2364,9 +2364,24 @@ impl ToPrimitive for Decimal { let integral_part = mantissa / precision; let frac_part = mantissa % precision; let frac_f64 = (frac_part as f64) / (precision as f64); - let value = sign * ((integral_part as f64) + frac_f64); + let integral = integral_part as f64; + // If there is a fractional component then we will need to add that and remove any + // inaccuracies that creep in during addition. Otherwise, if the fractional component + // is zero we can exit early. + if frac_f64.is_zero() { + if neg { + return Some(-integral); + } + return Some(integral); + } + let value = integral + frac_f64; let round_to = 10f64.powi(self.scale() as i32); - Some((value * round_to).round() / round_to) + let rounded = (value * round_to).round() / round_to; + if neg { + Some(-rounded) + } else { + Some(rounded) + } } } } diff --git a/src/serde.rs b/src/serde.rs index 993b0081..58245f49 100644 --- a/src/serde.rs +++ b/src/serde.rs @@ -658,7 +658,7 @@ mod test { #[test] #[cfg(all(feature = "serde-str", not(feature = "serde-float")))] - fn bincode_serialization() { + fn bincode_serialization_not_float() { use bincode::{deserialize, serialize}; let data = [ @@ -682,7 +682,7 @@ mod test { #[test] #[cfg(all(feature = "serde-str", feature = "serde-float"))] - fn bincode_serialization() { + fn bincode_serialization_serde_float() { use bincode::{deserialize, serialize}; let data = [ diff --git a/tests/decimal_tests.rs b/tests/decimal_tests.rs index 1eca9b17..1e985f76 100644 --- a/tests/decimal_tests.rs +++ b/tests/decimal_tests.rs @@ -2842,6 +2842,12 @@ fn it_converts_to_f64() { ("2.2238", Some(2.2238_f64)), ("2.2238123", Some(2.2238123_f64)), ("22238", Some(22238_f64)), + ("1000000", Some(1000000_f64)), + ("1000000.000000000000000000", Some(1000000_f64)), + ("10000", Some(10000_f64)), + ("10000.000000000000000000", Some(10000_f64)), + ("100000", Some(100000_f64)), + ("100000.000000000000000000", Some(100000_f64)), ]; for &(value, expected) in tests { let value = Decimal::from_str(value).unwrap().to_f64(); @@ -4747,6 +4753,24 @@ mod issues { assert_eq!("-429391.87200000000002327170816", c.unwrap().to_string()) } + #[test] + fn issue_624_to_f64_precision() { + let tests = [ + ("1000000.000000000000000000", 1000000.0f64), + ("10000.000000000000000000", 10000.0f64), + ("100000.000000000000000000", 100000.0f64), // Problematic value + ]; + for (index, (test, expected)) in tests.iter().enumerate() { + let decimal = Decimal::from_str_exact(test).unwrap(); + assert_eq!( + f64::try_from(decimal).unwrap(), + *expected, + "Test index {} failed", + index + ); + } + } + #[test] #[cfg(not(feature = "legacy-ops"))] // I will deprecate this feature/behavior in an upcoming release fn issue_618_rescaling_overflow() { From c4d4d3e9527b1b6c45fdc16e246b6827b513916a Mon Sep 17 00:00:00 2001 From: Paul Mason Date: Thu, 16 Nov 2023 15:23:22 -0800 Subject: [PATCH 21/37] Version 1.33.1 (#626) --- .buildnumber | 2 +- CHANGELOG.md | 7 ++++++- Cargo.toml | 2 +- fuzz/Cargo.toml | 2 +- macros/Cargo.toml | 2 +- 5 files changed, 10 insertions(+), 5 deletions(-) diff --git a/.buildnumber b/.buildnumber index d802d842..b122d9bc 100644 --- a/.buildnumber +++ b/.buildnumber @@ -1,3 +1,3 @@ 1 33 -0 +1 diff --git a/CHANGELOG.md b/CHANGELOG.md index a026c5ce..a3591a46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Version History +## 1.33.1 + +### Fixed + +* Fixes an issue when converting from a `Decimal` to a float whereby the float would be inaccurate after rounding. ([#625](https://github.com/paupino/rust-decimal/pull/625)) + ## 1.33.0 ### Fixed @@ -7,7 +13,6 @@ * Fixes an issue when adding/subtracting two `Decimal`s when one has a scale of 29. ([#619](https://github.com/paupino/rust-decimal/pull/619)) * An empty string will be parsed as `None` during JSON deserialization instead of panicking. ([#607](https://github.com/paupino/rust-decimal/pull/607)) - ### Changed * Upgrades `borsh` to version `1.1` as a result of a [security advisory](https://rustsec.org/advisories/RUSTSEC-2023-0033.html). ([#621](https://github.com/paupino/rust-decimal/pull/621)) diff --git a/Cargo.toml b/Cargo.toml index a18051fa..b036a684 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ name = "rust_decimal" readme = "./README.md" repository = "https://github.com/paupino/rust-decimal" rust-version = "1.60" -version = "1.33.0" +version = "1.33.1" [package.metadata.docs.rs] all-features = true diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 4b4a65fb..dab106f8 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -21,7 +21,7 @@ authors = ["Automatically generated"] edition = "2021" name = "rust-decimal-fuzz" publish = false -version = "1.33.0" +version = "1.33.1" [package.metadata] cargo-fuzz = true diff --git a/macros/Cargo.toml b/macros/Cargo.toml index 1cfa6de1..e2796e69 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rust_decimal_macros" -version = "1.33.0" +version = "1.33.1" authors = ["Paul Mason "] edition = "2021" description = "Shorthand macros to assist creating Decimal types." From 829355a377ef01affbd53a0c12de431c6904173e Mon Sep 17 00:00:00 2001 From: Paul Mason Date: Fri, 1 Dec 2023 09:07:21 -0800 Subject: [PATCH 22/37] Introduces `macros` feature and cleans up serde documentation a little bit (#628) --- CHANGELOG.md | 10 ++++ Cargo.toml | 10 +++- README.md | 67 ++++++++++++++++++----- examples/README.md | 16 ++++++ examples/serde-json-scenarios/Cargo.toml | 12 ++++ examples/serde-json-scenarios/src/main.rs | 56 +++++++++++++++++++ src/lib.rs | 5 ++ 7 files changed, 159 insertions(+), 17 deletions(-) create mode 100644 examples/README.md create mode 100644 examples/serde-json-scenarios/Cargo.toml create mode 100644 examples/serde-json-scenarios/src/main.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index a3591a46..6599743c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Version History +## 1.x + +### Added + +* `rust_decimal_macros` can now be utilized using the `macros` feature flag. + +### Changed + +* Added documentation for serde features as well as a few examples. + ## 1.33.1 ### Fixed diff --git a/Cargo.toml b/Cargo.toml index b036a684..d1c1be96 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,6 +31,7 @@ proptest = { default-features = false, optional = true, features = ["std"], vers rand = { default-features = false, optional = true, version = "0.8" } rkyv = { default-features = false, features = ["size_32", "std"], optional = true, version = "0.7.42" } rocket = { default-features = false, optional = true, version = "0.5.0-rc.3" } +rust_decimal_macros = { default-features = false, optional = true, version = "1.33" } # This needs to be the n-1 published version serde = { default-features = false, optional = true, version = "1.0" } serde_json = { default-features = false, optional = true, version = "1.0" } tokio-postgres = { default-features = false, optional = true, version = "0.7" } @@ -42,7 +43,7 @@ criterion = { default-features = false, version = "0.5" } csv = "1" futures = { default-features = false, version = "0.3" } rand = { default-features = false, features = ["getrandom"], version = "0.8" } -rust_decimal_macros = { path = "macros" } # This should be ok since it's just for tests +rust_decimal_macros = { default-features = false, version = "1.33" } serde = { default-features = false, features = ["derive"], version = "1.0" } serde_json = "1.0" tokio = { default-features = false, features = ["macros", "rt-multi-thread", "test-util"], version = "1.0" } @@ -50,6 +51,7 @@ version-sync = { default-features = false, features = ["html_root_url_updated", [features] default = ["serde", "std"] +macros = ["dep:rust_decimal_macros"] borsh = ["dep:borsh", "std"] c-repr = [] # Force Decimal to be repr(C) @@ -88,4 +90,8 @@ name = "comparison" path = "benches/comparison.rs" [workspace] -members = [".", "./macros"] +members = [ + ".", + "./macros" +] +resolver = "2" diff --git a/README.md b/README.md index c70280b2..9d6a4a21 100644 --- a/README.md +++ b/README.md @@ -19,26 +19,27 @@ Using [`cargo-edit`](https://crates.io/crates/cargo-edit): $ cargo add rust_decimal ``` -In addition, if you would like to use the optimized macro for convenient creation of decimals: +If you would like to use the optimized macro for convenient creation of decimals you can add `rust_decimal` with the `macros` feature flag: ```sh -$ cargo add rust_decimal_macros +$ cargo add rust_decimal --features macros ``` Alternatively, you can edit your `Cargo.toml` directly and run `cargo update`: ```toml [dependencies] -rust_decimal = "1.33" -rust_decimal_macros = "1.33" +rust_decimal = { version = "1.33", features = ["macros"] } ``` ## Usage -Decimal numbers can be created in a few distinct ways. The easiest and most efficient method of creating a Decimal is to use the procedural macro within the `rust_decimal_macros` crate: +Decimal numbers can be created in a few distinct ways. The easiest and most efficient method of creating a Decimal is to use the procedural macro that can be enabled using the `macros` feature: ```rust -// Procedural macros need importing directly +// The macros feature exposes a `dec` macro which will parse the input into a raw decimal number at compile time. +// It is also exposed when using `rust_decimal::prelude::*`. That said, you can also import the +// `rust_decimal_macros` crate and use the macro directly from there. use rust_decimal_macros::dec; let number = dec!(-1.23) + dec!(3.45); @@ -198,8 +199,8 @@ Enable `rust-fuzz` support by implementing the `Arbitrary` trait. ### `serde-float` -**Note:** it is recommended to use the `serde-with-*` features for greater control. This allows configurability at the data -level. +> **Note:** This feature applies float serialization/deserialization rules as the default method for handling `Decimal` numbers. +See also the `serde-with-*` features for greater flexibility. Enable this so that JSON serialization of `Decimal` types are sent as a float instead of a string (default). @@ -212,8 +213,8 @@ e.g. with this turned on, JSON serialization would output: ### `serde-str` -**Note:** it is recommended to use the `serde-with-*` features for greater control. This allows configurability at the data -level. +> **Note:** This feature applies string serialization/deserialization rules as the default method for handling `Decimal` numbers. +See also the `serde-with-*` features for greater flexibility. This is typically useful for `bincode` or `csv` like implementations. @@ -227,17 +228,20 @@ converting to `f64` _loses_ precision, it's highly recommended that you do NOT e ### `serde-arbitrary-precision` -**Note:** it is recommended to use the `serde-with-*` features for greater control. This allows configurability at the data -level. +> **Note:** This feature applies arbitrary serialization/deserialization rules as the default method for handling `Decimal` numbers. +See also the `serde-with-*` features for greater flexibility. This is used primarily with `serde_json` and consequently adds it as a "weak dependency". This supports the `arbitrary_precision` feature inside `serde_json` when parsing decimals. This is recommended when parsing "float" looking data as it will prevent data loss. +Please note, this currently serializes numbers in a float like format by default, which can be an unexpected consequence. For greater +control over the serialization format, please use the `serde-with-arbitrary-precision` feature. + ### `serde-with-float` -Enable this to access the module for serializing `Decimal` types to a float. This can be use in `struct` definitions like so: +Enable this to access the module for serializing `Decimal` types to a float. This can be used in `struct` definitions like so: ```rust #[derive(Serialize, Deserialize)] @@ -254,9 +258,18 @@ pub struct OptionFloatExample { } ``` +Alternatively, if only the serialization feature is desired (e.g. to keep flexibility while deserialization): +```rust +#[derive(Serialize, Deserialize)] +pub struct FloatExample { + #[serde(serialize_with = "rust_decimal::serde::float::serialize")] + value: Decimal, +} +``` + ### `serde-with-str` -Enable this to access the module for serializing `Decimal` types to a `String`. This can be use in `struct` definitions like so: +Enable this to access the module for serializing `Decimal` types to a `String`. This can be used in `struct` definitions like so: ```rust #[derive(Serialize, Deserialize)] @@ -273,9 +286,19 @@ pub struct OptionStrExample { } ``` +This feature isn't typically required for serialization however can be useful for deserialization purposes since it does not require +a type hint. Consequently, you can force this for just deserialization by: +```rust +#[derive(Serialize, Deserialize)] +pub struct StrExample { + #[serde(deserialize_with = "rust_decimal::serde::str::deserialize")] + value: Decimal, +} +``` + ### `serde-with-arbitrary-precision` -Enable this to access the module for serializing `Decimal` types to a `String`. This can be use in `struct` definitions like so: +Enable this to access the module for deserializing `Decimal` types using the `serde_json/arbitrary_precision` feature. This can be used in `struct` definitions like so: ```rust #[derive(Serialize, Deserialize)] @@ -292,6 +315,20 @@ pub struct OptionArbitraryExample { } ``` +An unexpected consequence of this feature is that it will serialize as a float like number. To prevent this, you can +target the struct to only deserialize with the `arbitrary_precision` feature: +```rust +#[derive(Serialize, Deserialize)] +pub struct ArbitraryExample { + #[serde(deserialize_with = "rust_decimal::serde::arbitrary_precision::deserialize")] + value: Decimal, +} +``` + +This will ensure that serialization still occurs as a string. + +Please see the `examples` directory for more information regarding `serde_json` scenarios. + ### `std` Enable `std` library support. This is enabled by default, however in the future will be opt in. For now, to support `no_std` diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 00000000..c7eecde5 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,16 @@ +# Examples + +This contains some more advanced examples of using the rust decimal library of complex usage. + +All examples are crate based to demonstrate feature configurations. Examples can be run by using: + +```shell +cd examples/ +cargo run +``` + +## serde-json-scenarios + +This example shows how to use the `serde` crate to serialize and deserialize the `Decimal` type using multiple different +serialization formats. + diff --git a/examples/serde-json-scenarios/Cargo.toml b/examples/serde-json-scenarios/Cargo.toml new file mode 100644 index 00000000..5d390481 --- /dev/null +++ b/examples/serde-json-scenarios/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "serde-json-scenarios" +version = "0.0.0" +edition = "2021" +publish = false + +[workspace] + +[dependencies] +rust_decimal = { path = "../..", features = ["macros", "serde-with-arbitrary-precision"] } +serde = { version = "1.0", features = ["derive"] } +serde_json = { version = "1.0", features = ["arbitrary_precision"]} diff --git a/examples/serde-json-scenarios/src/main.rs b/examples/serde-json-scenarios/src/main.rs new file mode 100644 index 00000000..168950a0 --- /dev/null +++ b/examples/serde-json-scenarios/src/main.rs @@ -0,0 +1,56 @@ +use rust_decimal::prelude::*; + +type ExampleResult = Result<(), Box>; + +fn main() -> ExampleResult { + demonstrate_default_behavior()?; + demonstrate_arbitrary_precision_deserialization_with_string_serialization()?; + Ok(()) +} + +/// The default behavior of the library always expects string results. That is, it will serialize the +/// Decimal as string, but also expect a string when deserializing. +/// Note: this is not enough for bincode representations since there is no deserialization hint that the +/// field is a string. +fn demonstrate_default_behavior() -> ExampleResult { + #[derive(serde::Serialize, serde::Deserialize)] + struct Total { + value: Decimal, + } + let total = Total { value: dec!(1.23) }; + let serialized = serde_json::to_string(&total)?; + assert_eq!(r#"{"value":"1.23"}"#, serialized); + + // If we try to deserialize the same string we should succeed + let deserialized: Total = serde_json::from_str(&serialized)?; + assert_eq!(dec!(1.23), deserialized.value); + + // Technically, by default we also support deserializing from a number, however this is doing a float + // conversion and is not recommended. + let deserialized: Total = serde_json::from_str(r#"{"value":1.23}"#)?; + assert_eq!(dec!(1.23), deserialized.value); + Ok(()) +} + +/// This demonstrates using arbitrary precision for a decimal value - even though the +/// default string serialization behavior is baked in. +fn demonstrate_arbitrary_precision_deserialization_with_string_serialization() -> ExampleResult { + #[derive(serde::Serialize, serde::Deserialize)] + struct Total { + #[serde(deserialize_with = "rust_decimal::serde::arbitrary_precision::deserialize")] + value: Decimal, + } + + let total = Total { value: dec!(1.23) }; + let serialized = serde_json::to_string(&total)?; + assert_eq!(r#"{"value":"1.23"}"#, serialized); + + // If we try to deserialize the same string we should succeed + let deserialized: Total = serde_json::from_str(&serialized)?; + assert_eq!(dec!(1.23), deserialized.value); + + // If we try to deserialize a float then this will succeed as well + let deserialized: Total = serde_json::from_str(r#"{"value":1.23}"#)?; + assert_eq!(dec!(1.23), deserialized.value); + Ok(()) +} diff --git a/src/lib.rs b/src/lib.rs index 59900e65..56ecaaf6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -57,6 +57,9 @@ pub use error::Error; #[cfg(feature = "maths")] pub use maths::MathematicalOps; +#[cfg(feature = "macros")] +pub use rust_decimal_macros::dec; + /// A convenience module appropriate for glob imports (`use rust_decimal::prelude::*;`). pub mod prelude { #[cfg(feature = "maths")] @@ -64,6 +67,8 @@ pub mod prelude { pub use crate::{Decimal, RoundingStrategy}; pub use core::str::FromStr; pub use num_traits::{FromPrimitive, One, Signed, ToPrimitive, Zero}; + #[cfg(feature = "macros")] + pub use rust_decimal_macros::dec; } #[cfg(all(feature = "diesel1", not(feature = "diesel2")))] From f02f5ac7472682719204b67faf851551b4ea699e Mon Sep 17 00:00:00 2001 From: Paul Mason Date: Fri, 22 Dec 2023 08:01:17 -0800 Subject: [PATCH 23/37] Update readme to add comparisons (#630) --- README.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/README.md b/README.md index 9d6a4a21..662240d4 100644 --- a/README.md +++ b/README.md @@ -346,3 +346,25 @@ which was released on `2022-04-07`. This library maintains support for rust compiler versions that are 4 minor versions away from the current stable rust compiler version. For example, if the current stable compiler version is `1.50.0` then we will guarantee support up to and including `1.46.0`. Of note, we will only update the minimum supported version if and when required. + +## Comparison to other Decimal implementations + +During the development of this library, there were various design decisions made to ensure that decimal calculations would +be quick, accurate and efficient. Some decisions, however, put limitations on what this library can do and ultimately what +it is suitable for. One such decision was the structure of the internal decimal representation. + +This library uses a mantissa of 96 bits made up of three 32-bit unsigned integers with a fourth 32-bit unsigned integer to represent the scale/sign +(similar to the C and .NET Decimal implementations). +This structure allows us to make use of algorithmic optimizations to implement basic arithmetic; ultimately this gives us the ability +to squeeze out performance and make it one of the fastest implementations available. The downside of this approach however is that +the maximum number of significant digits that can be represented is roughly 28 base-10 digits (29 in some cases). + +While this constraint is not an issue for many applications (e.g. when dealing with money), some applications may require a higher number of significant digits to be represented. Fortunately, +there are alternative implementations that may be worth investigating, such as: + +* [bigdecimal](https://crates.io/crates/bigdecimal) +* [decimal-rs](https://crates.io/crates/decimal-rs) + +If you have further questions about the suitability of this library for your project, then feel free to either start a +[discussion](https://github.com/paupino/rust-decimal/discussions) or open an [issue](https://github.com/paupino/rust-decimal/issues) and we'll +do our best to help. \ No newline at end of file From d323dd483ba7cf49d6374054567b7a8794744ff8 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 9 Jan 2024 16:58:46 +0000 Subject: [PATCH 24/37] Remove cargo-edit reference from README (#633) --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index 662240d4..626e5fdf 100644 --- a/README.md +++ b/README.md @@ -13,8 +13,6 @@ The binary representation consists of a 96 bit integer number, a scaling factor ## Installing -Using [`cargo-edit`](https://crates.io/crates/cargo-edit): - ```sh $ cargo add rust_decimal ``` @@ -367,4 +365,4 @@ there are alternative implementations that may be worth investigating, such as: If you have further questions about the suitability of this library for your project, then feel free to either start a [discussion](https://github.com/paupino/rust-decimal/discussions) or open an [issue](https://github.com/paupino/rust-decimal/issues) and we'll -do our best to help. \ No newline at end of file +do our best to help. From 10ee2eefba5f473fb67e9b0f625fb6a07cbc7d4b Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 9 Jan 2024 23:18:43 +0000 Subject: [PATCH 25/37] Auto-document feature guarded items (#634) Co-authored-by: Paul Mason --- Cargo.toml | 1 + src/lib.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index d1c1be96..5bf71999 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ version = "1.33.1" [package.metadata.docs.rs] all-features = true +rustdoc-args = ["--cfg", "docsrs"] [dependencies] arbitrary = { default-features = false, optional = true, version = "1.0" } diff --git a/src/lib.rs b/src/lib.rs index 56ecaaf6..cb3c7dc2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,6 +2,7 @@ #![forbid(unsafe_code)] #![deny(clippy::print_stdout, clippy::print_stderr)] #![cfg_attr(not(feature = "std"), no_std)] +#![cfg_attr(docsrs, feature(doc_auto_cfg))] extern crate alloc; mod constants; From 80e9f086fe2d60f8b65b472f7bb4b3bb1ef6f4a3 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sat, 13 Jan 2024 19:26:12 +0200 Subject: [PATCH 26/37] Reimplement pow function for integer exponent. (#638) * Reimplement checked_powu. * Add pow tests. * Add short-cuts to powu. * Fix x.powu(0) * Refine powu implementation. * Exclude a failing test that was caused by the deprecated legacy-ops feature --------- Co-authored-by: Paul Mason --- .gitignore | 1 + src/maths.rs | 51 ++++++++++++++++++++++++------------------ tests/decimal_tests.rs | 30 ++++++++----------------- 3 files changed, 39 insertions(+), 43 deletions(-) diff --git a/.gitignore b/.gitignore index 983ab23a..3a3f7afe 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ Cargo.lock artifacts corpus target +.vscode/settings.json diff --git a/src/maths.rs b/src/maths.rs index 7cde970b..6345d203 100644 --- a/src/maths.rs +++ b/src/maths.rs @@ -227,33 +227,40 @@ impl MathematicalOps for Decimal { } fn checked_powu(&self, exp: u64) -> Option { + if exp == 0 { + return Some(Decimal::ONE); + } + if self.is_zero() { + return Some(Decimal::ZERO); + } + if self.is_one() { + return Some(Decimal::ONE); + } + match exp { - 0 => Some(Decimal::ONE), + 0 => unreachable!(), 1 => Some(*self), 2 => self.checked_mul(*self), + // Do the exponentiation by multiplying squares: + // y = Sum (for each 1 bit in binary representation) of (2 ^ bit) + // x ^ y = Sum (for each 1 bit in y) of (x ^ (2 ^ bit)) + // See: https://en.wikipedia.org/wiki/Exponentiation_by_squaring _ => { - // Get the squared value - let squared = match self.checked_mul(*self) { - Some(s) => s, - None => return None, - }; - // Square self once and make an infinite sized iterator of the square. - let iter = core::iter::repeat(squared); - - // We then take half of the exponent to create a finite iterator and then multiply those together. let mut product = Decimal::ONE; - for x in iter.take((exp >> 1) as usize) { - match product.checked_mul(x) { - Some(r) => product = r, - None => return None, - }; - } - - // If the exponent is odd we still need to multiply once more - if exp & 0x1 > 0 { - match self.checked_mul(product) { - Some(p) => product = p, - None => return None, + let mut mask = exp; + let mut power = *self; + + // Run through just enough 1 bits + for n in 0..(64 - exp.leading_zeros()) { + if n > 0 { + power = power.checked_mul(power)?; + mask >>= 1; + } + if mask & 0x01 > 0 { + match product.checked_mul(power) { + Some(r) => product = r, + None => return None, + }; } } product.normalize_assign(); diff --git a/tests/decimal_tests.rs b/tests/decimal_tests.rs index 1e985f76..6bb7df13 100644 --- a/tests/decimal_tests.rs +++ b/tests/decimal_tests.rs @@ -3693,6 +3693,8 @@ mod maths { ("0.1", 0_u64, "1"), ("342.4", 1_u64, "342.4"), ("2.0", 16_u64, "65536"), + ("0.99999999999999", 1477289400_u64, "0.9999852272151186611602884841"), + ("0.99999999999999", 0x8000_8000_0000_0000, "0"), ]; for &(x, y, expected) in test_cases { let x = Decimal::from_str(x).unwrap(); @@ -3829,6 +3831,7 @@ mod maths { "0.1234567890123456789012345678", either!("0.0003533642875741443321850682", "0.0003305188683169079961720764"), ), + ("0.99999999999999", "1477289400", "0.9999852272151186611602884841"), ]; for &(x, y, expected) in test_cases { let x = Decimal::from_str(x).unwrap(); @@ -3965,43 +3968,28 @@ mod maths { } #[test] + #[cfg(not(feature = "legacy-ops"))] fn test_norm_cdf() { let test_cases = &[ ( Decimal::from_str("-0.4").unwrap(), - either!( - Decimal::from_str("0.3445781286821245037094401704").unwrap(), - Decimal::from_str("0.3445781286821245037094401728").unwrap() - ), + Decimal::from_str("0.3445781286821245037094401704").unwrap(), ), ( Decimal::from_str("-0.1").unwrap(), - either!( - Decimal::from_str("0.4601722899186706579921922696").unwrap(), - Decimal::from_str("0.4601722899186706579921922711").unwrap() - ), + Decimal::from_str("0.4601722899186706579921922696").unwrap(), ), ( Decimal::from_str("0.1").unwrap(), - Decimal::from_str(either!( - "0.5398277100813293420078077304", - "0.5398277100813293420078077290" - )) - .unwrap(), + Decimal::from_str("0.5398277100813293420078077304").unwrap(), ), ( Decimal::from_str("0.4").unwrap(), - either!( - Decimal::from_str("0.6554218713178754962905598296").unwrap(), - Decimal::from_str("0.6554218713178754962905598272").unwrap() - ), + Decimal::from_str("0.6554218713178754962905598296").unwrap(), ), ( Decimal::from_str("2.0").unwrap(), - either!( - Decimal::from_str("0.9772497381095865280953380673").unwrap(), - Decimal::from_str("0.9772497381095865280953380672").unwrap() - ), + Decimal::from_str("0.9772497381095865280953380673").unwrap(), ), ]; for case in test_cases { From aa2c3d3be8f7b2c96480f4b787240d531e3cfb93 Mon Sep 17 00:00:00 2001 From: Paul Mason Date: Sat, 13 Jan 2024 10:04:22 -0800 Subject: [PATCH 27/37] Fixes error handling when second decimal place at tail position (#636) * Fixes error handling when second decimal place at tail position * Added further bounds tests --- src/str.rs | 119 ++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 108 insertions(+), 11 deletions(-) diff --git a/src/str.rs b/src/str.rs index 94545732..b381b2ad 100644 --- a/src/str.rs +++ b/src/str.rs @@ -213,6 +213,14 @@ fn dispatch_next( let next = *next; if POINT && scale >= 28 { if ROUND { - maybe_round(data, next, scale, POINT, NEG) + // If it is an underscore at the rounding position we require slightly different handling to look ahead another digit + if next == b'_' { + if let Some((next, bytes)) = bytes.split_first() { + handle_full_128::(data, bytes, scale, *next) + } else { + handle_data::(data, scale) + } + } else { + // Otherwise, we round as usual + maybe_round(data, next, scale, POINT, NEG) + } } else { Err(Error::Underflow) } @@ -380,17 +398,11 @@ fn handle_full_128( #[inline(never)] #[cold] -fn maybe_round( - mut data: u128, - next_byte: u8, - mut scale: u8, - point: bool, - negative: bool, -) -> Result { +fn maybe_round(mut data: u128, next_byte: u8, mut scale: u8, point: bool, negative: bool) -> Result { let digit = match next_byte { b'0'..=b'9' => u32::from(next_byte - b'0'), - b'_' => 0, // this should be an invalid string? - b'.' if point => 0, + b'_' => 0, // This is perhaps an error case, but keep this here for compatibility + b'.' if !point => 0, b => return tail_invalid_digit(b), }; @@ -933,7 +945,6 @@ mod test { ); } - #[ignore] #[test] fn from_str_mantissa_overflow_4() { // Same test as above, however with underscores. This causes issues. @@ -945,6 +956,92 @@ mod test { ); } + #[test] + fn invalid_input_1() { + assert_eq!( + parse_str_radix_10("1.0000000000000000000000000000.5"), + Err(Error::from("Invalid decimal: two decimal points")) + ); + } + + #[test] + fn invalid_input_2() { + assert_eq!( + parse_str_radix_10("1.0.5"), + Err(Error::from("Invalid decimal: two decimal points")) + ); + } + + #[test] + fn character_at_rounding_position() { + let tests = [ + // digit is at the rounding position + ( + "1.000_000_000_000_000_000_000_000_000_04", + Ok(Decimal::from_i128_with_scale( + 1_000_000_000_000_000_000_000_000_000_0, + 28, + )), + ), + ( + "1.000_000_000_000_000_000_000_000_000_06", + Ok(Decimal::from_i128_with_scale( + 1_000_000_000_000_000_000_000_000_000_1, + 28, + )), + ), + // Decimal point is at the rounding position + ( + "1_000_000_000_000_000_000_000_000_000_0.4", + Ok(Decimal::from_i128_with_scale( + 1_000_000_000_000_000_000_000_000_000_0, + 0, + )), + ), + ( + "1_000_000_000_000_000_000_000_000_000_0.6", + Ok(Decimal::from_i128_with_scale( + 1_000_000_000_000_000_000_000_000_000_1, + 0, + )), + ), + // Placeholder is at the rounding position + ( + "1.000_000_000_000_000_000_000_000_000_0_4", + Ok(Decimal::from_i128_with_scale( + 1_000_000_000_000_000_000_000_000_000_0, + 28, + )), + ), + ( + "1.000_000_000_000_000_000_000_000_000_0_6", + Ok(Decimal::from_i128_with_scale( + 1_000_000_000_000_000_000_000_000_000_1, + 28, + )), + ), + // Multiple placeholders at rounding position + ( + "1.000_000_000_000_000_000_000_000_000_0__4", + Ok(Decimal::from_i128_with_scale( + 1_000_000_000_000_000_000_000_000_000_0, + 28, + )), + ), + ( + "1.000_000_000_000_000_000_000_000_000_0__6", + Ok(Decimal::from_i128_with_scale( + 1_000_000_000_000_000_000_000_000_000_1, + 28, + )), + ), + ]; + + for (input, expected) in tests.iter() { + assert_eq!(parse_str_radix_10(input), *expected, "Test input {}", input); + } + } + #[test] fn from_str_edge_cases_1() { assert_eq!(parse_str_radix_10(""), Err(Error::from("Invalid decimal: empty"))); From bacc60b5de57aaa23c082b0d6cad0830afabc644 Mon Sep 17 00:00:00 2001 From: Paul Mason Date: Wed, 31 Jan 2024 09:09:42 -0800 Subject: [PATCH 28/37] Version 1.34 (#640) * Version 1.34 * Corrected version in readme file --- .buildnumber | 4 ++-- CHANGELOG.md | 18 +++++++++++++++--- Cargo.toml | 2 +- README.md | 2 +- examples/serde-json-scenarios/Cargo.toml | 2 +- fuzz/Cargo.toml | 2 +- macros/Cargo.toml | 2 +- 7 files changed, 22 insertions(+), 10 deletions(-) diff --git a/.buildnumber b/.buildnumber index b122d9bc..dc6fe6e6 100644 --- a/.buildnumber +++ b/.buildnumber @@ -1,3 +1,3 @@ 1 -33 -1 +34 +0 diff --git a/CHANGELOG.md b/CHANGELOG.md index 6599743c..6032c3ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,14 +1,26 @@ # Version History -## 1.x +## 1.34.0 ### Added -* `rust_decimal_macros` can now be utilized using the `macros` feature flag. +* `rust_decimal_macros` can now be utilized using the `macros` feature flag. ([#628](https://github.com/paupino/rust-decimal/pull/628)) + +### Fixed + +* Reimplement `pow` function to more effectively handle larger exponents ([#638](https://github.com/paupino/rust-decimal/pull/638)) +* Fixes error handling when parsing a `Decimal` from a string when a decimal point or placeholder is at the rounding position ([#636](https://github.com/paupino/rust-decimal/pull/636)) ### Changed -* Added documentation for serde features as well as a few examples. +* Added documentation for serde features as well as a few examples. ([#630](https://github.com/paupino/rust-decimal/pull/630)) +* Documentation and rustdoc generation improvements ([#633](https://github.com/paupino/rust-decimal/pull/633), [#634](https://github.com/paupino/rust-decimal/pull/634)) + +### Credit + +Thank you to [@robjtede](https://github.com/robjtede) and [@schungx](https://github.com/schungx) for your contributions this release. + +Last but not least, a special thank you to [@Tony-Samuels](https://github.com/Tony-Samuels) for your help with managing `rust_decimal`! ## 1.33.1 diff --git a/Cargo.toml b/Cargo.toml index 5bf71999..ca327d6d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ name = "rust_decimal" readme = "./README.md" repository = "https://github.com/paupino/rust-decimal" rust-version = "1.60" -version = "1.33.1" +version = "1.34.0" [package.metadata.docs.rs] all-features = true diff --git a/README.md b/README.md index 626e5fdf..7b4dea87 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ Alternatively, you can edit your `Cargo.toml` directly and run `cargo update`: ```toml [dependencies] -rust_decimal = { version = "1.33", features = ["macros"] } +rust_decimal = { version = "1.34", features = ["macros"] } ``` ## Usage diff --git a/examples/serde-json-scenarios/Cargo.toml b/examples/serde-json-scenarios/Cargo.toml index 5d390481..1174190c 100644 --- a/examples/serde-json-scenarios/Cargo.toml +++ b/examples/serde-json-scenarios/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "serde-json-scenarios" -version = "0.0.0" +version = "1.34.0" edition = "2021" publish = false diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index dab106f8..e725f7f9 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -21,7 +21,7 @@ authors = ["Automatically generated"] edition = "2021" name = "rust-decimal-fuzz" publish = false -version = "1.33.1" +version = "1.34.0" [package.metadata] cargo-fuzz = true diff --git a/macros/Cargo.toml b/macros/Cargo.toml index e2796e69..e02fea6d 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rust_decimal_macros" -version = "1.33.1" +version = "1.34.0" authors = ["Paul Mason "] edition = "2021" description = "Shorthand macros to assist creating Decimal types." From 999bb0294f57e935046c7c8b949eb707482123cc Mon Sep 17 00:00:00 2001 From: Paul Mason Date: Thu, 1 Feb 2024 07:58:27 -0800 Subject: [PATCH 29/37] Force version number during macro resolution (#642) --- .buildnumber | 2 +- CHANGELOG.md | 6 ++++++ Cargo.toml | 4 ++-- examples/serde-json-scenarios/Cargo.toml | 2 +- fuzz/Cargo.toml | 2 +- macros/Cargo.toml | 5 +++-- 6 files changed, 14 insertions(+), 7 deletions(-) diff --git a/.buildnumber b/.buildnumber index dc6fe6e6..75cc69bf 100644 --- a/.buildnumber +++ b/.buildnumber @@ -1,3 +1,3 @@ 1 34 -0 +1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 6032c3ee..0bfb36f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Version History +## 1.34.1 + +### Fixed + +* Hotfix for circular dependency when using the `macros` feature. + ## 1.34.0 ### Added diff --git a/Cargo.toml b/Cargo.toml index ca327d6d..78651c0a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ name = "rust_decimal" readme = "./README.md" repository = "https://github.com/paupino/rust-decimal" rust-version = "1.60" -version = "1.34.0" +version = "1.34.1" [package.metadata.docs.rs] all-features = true @@ -32,7 +32,7 @@ proptest = { default-features = false, optional = true, features = ["std"], vers rand = { default-features = false, optional = true, version = "0.8" } rkyv = { default-features = false, features = ["size_32", "std"], optional = true, version = "0.7.42" } rocket = { default-features = false, optional = true, version = "0.5.0-rc.3" } -rust_decimal_macros = { default-features = false, optional = true, version = "1.33" } # This needs to be the n-1 published version +rust_decimal_macros = { default-features = false, optional = true, version = "1.34" } # This needs to a published version serde = { default-features = false, optional = true, version = "1.0" } serde_json = { default-features = false, optional = true, version = "1.0" } tokio-postgres = { default-features = false, optional = true, version = "0.7" } diff --git a/examples/serde-json-scenarios/Cargo.toml b/examples/serde-json-scenarios/Cargo.toml index 1174190c..5d390481 100644 --- a/examples/serde-json-scenarios/Cargo.toml +++ b/examples/serde-json-scenarios/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "serde-json-scenarios" -version = "1.34.0" +version = "0.0.0" edition = "2021" publish = false diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index e725f7f9..0cac371a 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -21,7 +21,7 @@ authors = ["Automatically generated"] edition = "2021" name = "rust-decimal-fuzz" publish = false -version = "1.34.0" +version = "0.0.0" [package.metadata] cargo-fuzz = true diff --git a/macros/Cargo.toml b/macros/Cargo.toml index e02fea6d..258c3b8f 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rust_decimal_macros" -version = "1.34.0" +version = "1.34.1" authors = ["Paul Mason "] edition = "2021" description = "Shorthand macros to assist creating Decimal types." @@ -12,7 +12,8 @@ categories = ["science","data-structures"] license = "MIT" [dependencies] -rust_decimal = { path = "..", version = "1.31", default-features = false } +# This needs to be a locked version until we can remove this dependency altogether +rust_decimal = { version = "=1.33.1", default-features = false } quote = "1.0" [dev-dependencies] From 179039a9a8db23ede432d4302d9c1201e5c4ab21 Mon Sep 17 00:00:00 2001 From: Paul Mason Date: Thu, 1 Feb 2024 08:18:15 -0800 Subject: [PATCH 30/37] Rollback macros feature flag due to dependency resolution issues (#643) --- .buildnumber | 2 +- Cargo.toml | 7 ++++--- README.md | 11 +++++------ examples/serde-json-scenarios/Cargo.toml | 3 ++- examples/serde-json-scenarios/src/main.rs | 1 + src/lib.rs | 8 ++++---- 6 files changed, 17 insertions(+), 15 deletions(-) diff --git a/.buildnumber b/.buildnumber index 75cc69bf..0357f4fe 100644 --- a/.buildnumber +++ b/.buildnumber @@ -1,3 +1,3 @@ 1 34 -1 +2 diff --git a/Cargo.toml b/Cargo.toml index 78651c0a..2292fa3c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ name = "rust_decimal" readme = "./README.md" repository = "https://github.com/paupino/rust-decimal" rust-version = "1.60" -version = "1.34.1" +version = "1.34.2" [package.metadata.docs.rs] all-features = true @@ -32,7 +32,7 @@ proptest = { default-features = false, optional = true, features = ["std"], vers rand = { default-features = false, optional = true, version = "0.8" } rkyv = { default-features = false, features = ["size_32", "std"], optional = true, version = "0.7.42" } rocket = { default-features = false, optional = true, version = "0.5.0-rc.3" } -rust_decimal_macros = { default-features = false, optional = true, version = "1.34" } # This needs to a published version +#rust_decimal_macros = { default-features = false, optional = true, version = "1.34" } # This needs to a published version serde = { default-features = false, optional = true, version = "1.0" } serde_json = { default-features = false, optional = true, version = "1.0" } tokio-postgres = { default-features = false, optional = true, version = "0.7" } @@ -52,7 +52,8 @@ version-sync = { default-features = false, features = ["html_root_url_updated", [features] default = ["serde", "std"] -macros = ["dep:rust_decimal_macros"] +# Removed in 1.34.2 due to an issue during version resolution +#macros = ["dep:rust_decimal_macros"] borsh = ["dep:borsh", "std"] c-repr = [] # Force Decimal to be repr(C) diff --git a/README.md b/README.md index 7b4dea87..1927044c 100644 --- a/README.md +++ b/README.md @@ -17,17 +17,18 @@ The binary representation consists of a 96 bit integer number, a scaling factor $ cargo add rust_decimal ``` -If you would like to use the optimized macro for convenient creation of decimals you can add `rust_decimal` with the `macros` feature flag: +In addition, if you would like to use the optimized macro for convenient creation of decimals: ```sh -$ cargo add rust_decimal --features macros +$ cargo add rust_decimal_macros ``` Alternatively, you can edit your `Cargo.toml` directly and run `cargo update`: ```toml [dependencies] -rust_decimal = { version = "1.34", features = ["macros"] } +rust_decimal = "1.34" +rust_decimal_macros = "1.34" ``` ## Usage @@ -35,9 +36,7 @@ rust_decimal = { version = "1.34", features = ["macros"] } Decimal numbers can be created in a few distinct ways. The easiest and most efficient method of creating a Decimal is to use the procedural macro that can be enabled using the `macros` feature: ```rust -// The macros feature exposes a `dec` macro which will parse the input into a raw decimal number at compile time. -// It is also exposed when using `rust_decimal::prelude::*`. That said, you can also import the -// `rust_decimal_macros` crate and use the macro directly from there. +// Import the `rust_decimal_macros` crate and use the macro directly from there. use rust_decimal_macros::dec; let number = dec!(-1.23) + dec!(3.45); diff --git a/examples/serde-json-scenarios/Cargo.toml b/examples/serde-json-scenarios/Cargo.toml index 5d390481..f6be4fc4 100644 --- a/examples/serde-json-scenarios/Cargo.toml +++ b/examples/serde-json-scenarios/Cargo.toml @@ -7,6 +7,7 @@ publish = false [workspace] [dependencies] -rust_decimal = { path = "../..", features = ["macros", "serde-with-arbitrary-precision"] } +rust_decimal = { path = "../..", features = ["serde-with-arbitrary-precision"] } +rust_decimal_macros = { path = "../../macros" } serde = { version = "1.0", features = ["derive"] } serde_json = { version = "1.0", features = ["arbitrary_precision"]} diff --git a/examples/serde-json-scenarios/src/main.rs b/examples/serde-json-scenarios/src/main.rs index 168950a0..a173e77e 100644 --- a/examples/serde-json-scenarios/src/main.rs +++ b/examples/serde-json-scenarios/src/main.rs @@ -1,4 +1,5 @@ use rust_decimal::prelude::*; +use rust_decimal_macros::dec; type ExampleResult = Result<(), Box>; diff --git a/src/lib.rs b/src/lib.rs index cb3c7dc2..d49db64b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -58,8 +58,8 @@ pub use error::Error; #[cfg(feature = "maths")] pub use maths::MathematicalOps; -#[cfg(feature = "macros")] -pub use rust_decimal_macros::dec; +// #[cfg(feature = "macros")] +// pub use rust_decimal_macros::dec; /// A convenience module appropriate for glob imports (`use rust_decimal::prelude::*;`). pub mod prelude { @@ -68,8 +68,8 @@ pub mod prelude { pub use crate::{Decimal, RoundingStrategy}; pub use core::str::FromStr; pub use num_traits::{FromPrimitive, One, Signed, ToPrimitive, Zero}; - #[cfg(feature = "macros")] - pub use rust_decimal_macros::dec; + // #[cfg(feature = "macros")] + // pub use rust_decimal_macros::dec; } #[cfg(all(feature = "diesel1", not(feature = "diesel2")))] From d72e7870b6f2ef8f6f671c866fb55f5478eea70e Mon Sep 17 00:00:00 2001 From: Paul Mason Date: Thu, 1 Feb 2024 08:21:45 -0800 Subject: [PATCH 31/37] Bump macros version --- CHANGELOG.md | 6 ++++++ macros/Cargo.toml | 5 ++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0bfb36f1..751b4789 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Version History +## 1.34.2 + +### Fixed + +* Deprecate `macros` feature until circular dependency issue is resolved properly. + ## 1.34.1 ### Fixed diff --git a/macros/Cargo.toml b/macros/Cargo.toml index 258c3b8f..16085bb0 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rust_decimal_macros" -version = "1.34.1" +version = "1.34.2" authors = ["Paul Mason "] edition = "2021" description = "Shorthand macros to assist creating Decimal types." @@ -12,8 +12,7 @@ categories = ["science","data-structures"] license = "MIT" [dependencies] -# This needs to be a locked version until we can remove this dependency altogether -rust_decimal = { version = "=1.33.1", default-features = false } +rust_decimal = { version = "1.33", default-features = false } quote = "1.0" [dev-dependencies] From 466655559e380cb562f0b2996bc9180e21461274 Mon Sep 17 00:00:00 2001 From: Gautham Date: Tue, 6 Sep 2022 13:01:02 +0530 Subject: [PATCH 32/37] Add parity scale codec --- Cargo.toml | 3 +++ src/decimal.rs | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 2292fa3c..7c8648ae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,8 @@ all-features = true rustdoc-args = ["--cfg", "docsrs"] [dependencies] +parity-scale-codec = {package = "parity-scale-codec", optional = true, version = "3.1.5"} +parity-scale-codec-derive = {package = "parity-scale-codec-derive", optional = true, version = "3.1.3"} arbitrary = { default-features = false, optional = true, version = "1.0" } arrayvec = { default-features = false, version = "0.7" } borsh = { default-features = false, features = ["derive", "unstable__schema"], optional = true, version = "1.1.1" } @@ -85,6 +87,7 @@ serde-with-float = ["serde"] serde-with-str = ["serde"] std = ["arrayvec/std", "borsh?/std", "bytes?/std", "rand?/std", "rkyv?/std", "serde?/std", "serde_json?/std"] tokio-pg = ["db-tokio-postgres"] # Backwards compatability +scale-codec = ["parity-scale-codec-derive","parity-scale-codec"] [[bench]] harness = false diff --git a/src/decimal.rs b/src/decimal.rs index 45f5661f..57969d2c 100644 --- a/src/decimal.rs +++ b/src/decimal.rs @@ -27,6 +27,8 @@ use num_traits::float::FloatCore; use num_traits::{FromPrimitive, Num, One, Signed, ToPrimitive, Zero}; #[cfg(feature = "rkyv")] use rkyv::{Archive, Deserialize, Serialize}; +#[cfg(feature = "scale-codec")] +use parity_scale_codec_derive::{Decode,Encode}; /// The smallest value that can be represented by this decimal type. const MIN: Decimal = Decimal { @@ -121,6 +123,10 @@ pub struct UnpackedDecimal { archive_attr(derive(Clone, Copy, Debug)) )] #[cfg_attr(feature = "rkyv-safe", archive(check_bytes))] +#[cfg_attr( +feature = "scale-codec", +derive(Decode, Encode), +)] pub struct Decimal { // Bits 0-15: unused // Bits 16-23: Contains "e", a value between 0-28 that indicates the scale From 65ec77a501992c5058f43e2e67faf1360df0da8a Mon Sep 17 00:00:00 2001 From: Gautham Date: Tue, 6 Sep 2022 19:24:24 +0530 Subject: [PATCH 33/37] Add scale-info too --- Cargo.toml | 3 ++- src/decimal.rs | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7c8648ae..9eb44e87 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,7 @@ rustdoc-args = ["--cfg", "docsrs"] [dependencies] parity-scale-codec = {package = "parity-scale-codec", optional = true, version = "3.1.5"} parity-scale-codec-derive = {package = "parity-scale-codec-derive", optional = true, version = "3.1.3"} +scale-info = {optional=true, version = "2.1.2", features = ["derive"]} arbitrary = { default-features = false, optional = true, version = "1.0" } arrayvec = { default-features = false, version = "0.7" } borsh = { default-features = false, features = ["derive", "unstable__schema"], optional = true, version = "1.1.1" } @@ -87,7 +88,7 @@ serde-with-float = ["serde"] serde-with-str = ["serde"] std = ["arrayvec/std", "borsh?/std", "bytes?/std", "rand?/std", "rkyv?/std", "serde?/std", "serde_json?/std"] tokio-pg = ["db-tokio-postgres"] # Backwards compatability -scale-codec = ["parity-scale-codec-derive","parity-scale-codec"] +scale-codec = ["parity-scale-codec-derive","parity-scale-codec","scale-info"] [[bench]] harness = false diff --git a/src/decimal.rs b/src/decimal.rs index 57969d2c..2a3dbc08 100644 --- a/src/decimal.rs +++ b/src/decimal.rs @@ -29,6 +29,8 @@ use num_traits::{FromPrimitive, Num, One, Signed, ToPrimitive, Zero}; use rkyv::{Archive, Deserialize, Serialize}; #[cfg(feature = "scale-codec")] use parity_scale_codec_derive::{Decode,Encode}; +#[cfg(feature = "scale-codec")] +use scale_info::TypeInfo; /// The smallest value that can be represented by this decimal type. const MIN: Decimal = Decimal { @@ -125,7 +127,7 @@ pub struct UnpackedDecimal { #[cfg_attr(feature = "rkyv-safe", archive(check_bytes))] #[cfg_attr( feature = "scale-codec", -derive(Decode, Encode), +derive(Decode, Encode, TypeInfo), )] pub struct Decimal { // Bits 0-15: unused From e7511dce875287c57af7070baad5b47f32117526 Mon Sep 17 00:00:00 2001 From: Gautham Date: Tue, 6 Sep 2022 19:27:25 +0530 Subject: [PATCH 34/37] Add scale-info too --- Cargo.toml | 6 +++--- src/decimal.rs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9eb44e87..65ff2aee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,9 +19,9 @@ all-features = true rustdoc-args = ["--cfg", "docsrs"] [dependencies] -parity-scale-codec = {package = "parity-scale-codec", optional = true, version = "3.1.5"} -parity-scale-codec-derive = {package = "parity-scale-codec-derive", optional = true, version = "3.1.3"} -scale-info = {optional=true, version = "2.1.2", features = ["derive"]} +parity-scale-codec = {package = "parity-scale-codec", optional = true, version = "3.1.5", features = ["max-encoded-len"], default-features = false} +parity-scale-codec-derive = {package = "parity-scale-codec-derive", optional = true, version = "3.1.3",default-features = false} +scale-info = {optional=true, version = "2.1.2", features = ["derive"],default-features = false} arbitrary = { default-features = false, optional = true, version = "1.0" } arrayvec = { default-features = false, version = "0.7" } borsh = { default-features = false, features = ["derive", "unstable__schema"], optional = true, version = "1.1.1" } diff --git a/src/decimal.rs b/src/decimal.rs index 2a3dbc08..cef707af 100644 --- a/src/decimal.rs +++ b/src/decimal.rs @@ -28,7 +28,7 @@ use num_traits::{FromPrimitive, Num, One, Signed, ToPrimitive, Zero}; #[cfg(feature = "rkyv")] use rkyv::{Archive, Deserialize, Serialize}; #[cfg(feature = "scale-codec")] -use parity_scale_codec_derive::{Decode,Encode}; +use parity_scale_codec_derive::{Decode,Encode, MaxEncodedLen}; #[cfg(feature = "scale-codec")] use scale_info::TypeInfo; @@ -127,7 +127,7 @@ pub struct UnpackedDecimal { #[cfg_attr(feature = "rkyv-safe", archive(check_bytes))] #[cfg_attr( feature = "scale-codec", -derive(Decode, Encode, TypeInfo), +derive(Decode, Encode, TypeInfo, MaxEncodedLen), )] pub struct Decimal { // Bits 0-15: unused From 62cf9120097afd71e702e76d422d9db98271a504 Mon Sep 17 00:00:00 2001 From: Gautham Date: Wed, 14 Sep 2022 17:26:18 +0530 Subject: [PATCH 35/37] Make from_parts_raw --- src/decimal.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/decimal.rs b/src/decimal.rs index cef707af..f90dd80d 100644 --- a/src/decimal.rs +++ b/src/decimal.rs @@ -556,7 +556,7 @@ impl Decimal { } #[must_use] - pub(crate) const fn from_parts_raw(lo: u32, mid: u32, hi: u32, flags: u32) -> Decimal { + pub const fn from_parts_raw(lo: u32, mid: u32, hi: u32, flags: u32) -> Decimal { if lo == 0 && mid == 0 && hi == 0 { Decimal { lo, From d778de739c7e18d59f7a62e5928840c3b51194b5 Mon Sep 17 00:00:00 2001 From: Gautham Date: Thu, 22 Jun 2023 07:47:56 +0530 Subject: [PATCH 36/37] Make deps in alphabetical order --- Cargo.toml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 65ff2aee..2b2c1db0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,9 +19,6 @@ all-features = true rustdoc-args = ["--cfg", "docsrs"] [dependencies] -parity-scale-codec = {package = "parity-scale-codec", optional = true, version = "3.1.5", features = ["max-encoded-len"], default-features = false} -parity-scale-codec-derive = {package = "parity-scale-codec-derive", optional = true, version = "3.1.3",default-features = false} -scale-info = {optional=true, version = "2.1.2", features = ["derive"],default-features = false} arbitrary = { default-features = false, optional = true, version = "1.0" } arrayvec = { default-features = false, version = "0.7" } borsh = { default-features = false, features = ["derive", "unstable__schema"], optional = true, version = "1.1.1" } @@ -30,12 +27,15 @@ diesel1 = { default-features = false, optional = true, package = "diesel", versi diesel2 = { default-features = false, optional = true, package = "diesel", version = "2.1" } ndarray = { default-features = false, optional = true, version = "0.15.6" } num-traits = { default-features = false, features = ["i128"], version = "0.2" } +parity-scale-codec = { optional = true, version = "3.1.5", features = ["max-encoded-len"], default-features = false} +parity-scale-codec-derive = { optional = true, version = "3.1.3",default-features = false} postgres = { default-features = false, optional = true, version = "0.19" } proptest = { default-features = false, optional = true, features = ["std"], version = "1.0" } rand = { default-features = false, optional = true, version = "0.8" } rkyv = { default-features = false, features = ["size_32", "std"], optional = true, version = "0.7.42" } rocket = { default-features = false, optional = true, version = "0.5.0-rc.3" } #rust_decimal_macros = { default-features = false, optional = true, version = "1.34" } # This needs to a published version +scale-info = {optional=true, version = "2.1.2", features = ["derive"], default-features = false} serde = { default-features = false, optional = true, version = "1.0" } serde_json = { default-features = false, optional = true, version = "1.0" } tokio-postgres = { default-features = false, optional = true, version = "0.7" } @@ -78,6 +78,7 @@ rkyv = ["dep:rkyv"] rkyv-safe = ["rkyv/validation"] rocket-traits = ["dep:rocket"] rust-fuzz = ["dep:arbitrary"] +scale-codec = ["parity-scale-codec-derive","parity-scale-codec","scale-info"] serde = ["dep:serde"] serde-arbitrary-precision = ["serde-with-arbitrary-precision"] serde-bincode = ["serde-str"] # Backwards compatability @@ -86,9 +87,8 @@ serde-str = ["serde-with-str"] serde-with-arbitrary-precision = ["serde", "serde_json/arbitrary_precision", "serde_json/std"] serde-with-float = ["serde"] serde-with-str = ["serde"] -std = ["arrayvec/std", "borsh?/std", "bytes?/std", "rand?/std", "rkyv?/std", "serde?/std", "serde_json?/std"] +std = ["arrayvec/std", "borsh?/std", "bytes?/std", "rand?/std", "rkyv?/std", "serde?/std", "serde_json?/std", "parity-scale-codec?/std","scale-info?/std"] tokio-pg = ["db-tokio-postgres"] # Backwards compatability -scale-codec = ["parity-scale-codec-derive","parity-scale-codec","scale-info"] [[bench]] harness = false From e1b54cd7ee92e818bb454d00bae3e27f981f907b Mon Sep 17 00:00:00 2001 From: Gautham Date: Thu, 8 Feb 2024 14:04:26 +0530 Subject: [PATCH 37/37] Update hashbrown --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 2b2c1db0..d4ccc561 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,7 @@ parity-scale-codec-derive = { optional = true, version = "3.1.3",default-feature postgres = { default-features = false, optional = true, version = "0.19" } proptest = { default-features = false, optional = true, features = ["std"], version = "1.0" } rand = { default-features = false, optional = true, version = "0.8" } -rkyv = { default-features = false, features = ["size_32", "std"], optional = true, version = "0.7.42" } +rkyv = { default-features = false, features = ["size_32", "std"], optional = true, version = "0.7.44" } rocket = { default-features = false, optional = true, version = "0.5.0-rc.3" } #rust_decimal_macros = { default-features = false, optional = true, version = "1.34" } # This needs to a published version scale-info = {optional=true, version = "2.1.2", features = ["derive"], default-features = false}