diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e587cc3..59f8827 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -94,7 +94,7 @@ jobs: uses: actions-rs/cargo@v1 with: command: test - args: --no-default-features --features "colored, float-cmp, num-bigint, rust-decimal" + args: --no-default-features --features "colored, float-cmp, num-bigint, rust-decimal, bigdecimal" msrv: name: Build with MSRV diff --git a/Cargo.toml b/Cargo.toml index 4f63753..c08d333 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,23 +20,25 @@ rustdoc-args = ["--cfg", "docsrs"] [features] default = ["std", "colored", "float-cmp", "panic", "regex"] +bigdecimal = ["dep:bigdecimal"] colored = ["dep:sdiff"] float-cmp = ["dep:float-cmp"] num-bigint = ["dep:num-bigint", "dep:lazy_static"] rust-decimal = ["dep:rust_decimal"] panic = ["std"] regex = ["dep:regex"] -std = [] +std = ["bigdecimal/std", "rust_decimal/std"] [dependencies] hashbrown = "0.15" # optional +bigdecimal = { version = "0.4", optional = true, default-features = false } float-cmp = { version = "0.10", optional = true } lazy_static = { version = "1", optional = true } -num-bigint = { version = "0.4", default-features = false, optional = true } -rust_decimal = { version = "1", default-features = false, optional = true } +num-bigint = { version = "0.4", optional = true, default-features = false } regex = { version = "1", optional = true } +rust_decimal = { version = "1", optional = true, default-features = false } sdiff = { version = "0.1", optional = true } [dev-dependencies] diff --git a/README.md b/README.md index cbfa6be..c9ab5e9 100644 --- a/README.md +++ b/README.md @@ -168,6 +168,8 @@ for numbers of types * integer primitives: `i8`, `i16`, `i32`, `i64`, `i128` and `isize` * floating point numbers: `f32` and `f64` * `num_bigint::BigInt` (requires crate feature `num-bigint`) +* `bigdecimal:BigDecimal` and `bigdecimal:BigDecimalRef` (requires crate feature `bigdecimal`) +* `rust_decimal::Decimal` (requires crate feature `rust-decimal`) | assertion | description | |-----------------|------------------------------------------------------| @@ -182,6 +184,7 @@ for numbers of types and `usize` * floating point numbers: `f32` and `f64` * `num_bigint::BigInt` and `num_bigint::BigUint` (requires crate feature `num-bigint`) +* `bigdecimal:BigDecimal` and `bigdecimal:BigDecimalRef` (requires crate feature `bigdecimal`) * `rust_decimal::Decimal` (requires crate feature `rust-decimal`) | assertion | description | diff --git a/examples/fixture/mod.rs b/examples/fixture/mod.rs index 8229bfb..1416ce9 100644 --- a/examples/fixture/mod.rs +++ b/examples/fixture/mod.rs @@ -2,6 +2,8 @@ // Rust issue [#95513](https://github.com/rust-lang/rust/issues/95513) is fixed mod dummy_extern_uses { use anyhow as _; + #[cfg(feature = "bigdecimal")] + use bigdecimal as _; #[cfg(feature = "float-cmp")] use float_cmp as _; use hashbrown as _; diff --git a/justfile b/justfile index b57c821..426239b 100644 --- a/justfile +++ b/justfile @@ -37,7 +37,7 @@ lint-all-features: # linting code using Clippy for no-std environment lint-no-std: - cargo clippy --all-targets --no-default-features --features "colored, float-cmp, num-bigint, rust-decimal" + cargo clippy --all-targets --no-default-features --features "colored, float-cmp, num-bigint, rust-decimal, bigdecimal" # linting code using Clippy with no features enabled lint-no-features: @@ -55,7 +55,7 @@ test-all-features: # run tests for no-std environment test-no-std: - cargo test --no-default-features --features "colored, float-cmp, num-bigint, rust-decimal" + cargo test --no-default-features --features "colored, float-cmp, num-bigint, rust-decimal, bigdecimal" # run tests with no features enabled test-no-features: diff --git a/src/bigdecimal/mod.rs b/src/bigdecimal/mod.rs new file mode 100644 index 0000000..71089ef --- /dev/null +++ b/src/bigdecimal/mod.rs @@ -0,0 +1,78 @@ +use crate::properties::{AdditiveIdentityProperty, MultiplicativeIdentityProperty, SignumProperty}; +use bigdecimal::num_bigint::Sign; +use bigdecimal::{BigDecimal, BigDecimalRef, One, Zero}; +use lazy_static::lazy_static; + +lazy_static! { + static ref BIGDECIMAL_ZERO: BigDecimal = bigdecimal_zero(); + static ref BIGDECIMAL_ONE: BigDecimal = bigdecimal_one(); +} + +#[inline] +fn bigdecimal_zero() -> BigDecimal { + BigDecimal::zero() +} + +#[inline] +fn bigdecimal_one() -> BigDecimal { + BigDecimal::one() +} + +impl SignumProperty for BigDecimal { + fn is_negative_property(&self) -> bool { + self.sign() == Sign::Minus + } + + fn is_positive_property(&self) -> bool { + self.sign() == Sign::Plus + } +} + +impl AdditiveIdentityProperty for BigDecimal { + fn additive_identity() -> Self { + bigdecimal_zero() + } +} + +impl AdditiveIdentityProperty for &BigDecimal { + fn additive_identity() -> Self { + &BIGDECIMAL_ZERO + } +} + +impl MultiplicativeIdentityProperty for BigDecimal { + fn multiplicative_identity() -> Self { + bigdecimal_one() + } +} + +impl MultiplicativeIdentityProperty for &BigDecimal { + fn multiplicative_identity() -> Self { + &BIGDECIMAL_ONE + } +} + +impl SignumProperty for BigDecimalRef<'_> { + fn is_negative_property(&self) -> bool { + self.sign() == Sign::Minus + } + + fn is_positive_property(&self) -> bool { + self.sign() == Sign::Plus + } +} + +impl AdditiveIdentityProperty for BigDecimalRef<'_> { + fn additive_identity() -> Self { + BIGDECIMAL_ZERO.to_ref() + } +} + +impl MultiplicativeIdentityProperty for BigDecimalRef<'_> { + fn multiplicative_identity() -> Self { + BIGDECIMAL_ONE.to_ref() + } +} + +#[cfg(test)] +mod tests; diff --git a/src/bigdecimal/tests.rs b/src/bigdecimal/tests.rs new file mode 100644 index 0000000..b8d2713 --- /dev/null +++ b/src/bigdecimal/tests.rs @@ -0,0 +1,241 @@ +use crate::prelude::*; +use bigdecimal::num_bigint::BigInt; +use bigdecimal::BigDecimal; + +#[test] +fn bigdecimal_is_equal_to_other() { + let subject = BigDecimal::new(BigInt::from(42_831), 3); + + assert_that(subject).is_equal_to(BigDecimal::new(BigInt::from(42_831), 3)); + + assert_that(BigDecimal::new(BigInt::from(42_831), 3)) + .is_equal_to(BigDecimal::new(BigInt::from(428_310), 4)); + assert_that(BigDecimal::new(BigInt::from(0), 0)) + .is_equal_to(BigDecimal::new(BigInt::from(0), 2)); + assert_that(BigDecimal::new(BigInt::from(-0), 0)) + .is_equal_to(BigDecimal::new(BigInt::from(0), 0)); +} + +#[test] +fn verify_bigdecimal_is_equal_to_other_fails() { + let subject = BigDecimal::new(BigInt::from(42_831), 3); + + let failures = verify_that(subject) + .is_equal_to(BigDecimal::new(BigInt::from(-42_831), 3)) + .display_failures(); + + assert_eq!( + failures, + &[ + r"assertion failed: expected subject is equal to BigDecimal(sign=Minus, scale=3, digits=[42831]) + but was: BigDecimal(sign=Plus, scale=3, digits=[42831]) + expected: BigDecimal(sign=Minus, scale=3, digits=[42831]) +" + ] + ); +} + +#[test] +fn bigdecimal_is_not_equal_to_other() { + let subject = BigDecimal::new(BigInt::from(42_831), 3); + + assert_that(subject).is_not_equal_to(BigDecimal::new(BigInt::from(42_831), 2)); +} + +#[test] +fn borrowed_bigdecimal_is_equal_to_other() { + let subject = BigDecimal::new(BigInt::from(-42_831), 3); + + assert_that(&subject).is_equal_to(&BigDecimal::new(BigInt::from(-42_831), 3)); +} + +#[test] +fn bigdecimal_is_less_than_other() { + let subject = BigDecimal::new(BigInt::from(42_831), 3); + + assert_that(&subject).is_less_than(&BigDecimal::new(BigInt::from(1_592_834), 3)); + assert_that(subject).is_less_than(BigDecimal::new(BigInt::from(42_832), 3)); +} + +#[test] +fn bigdecimal_is_greater_than_other() { + let subject = BigDecimal::new(BigInt::from(42_831), 3); + + assert_that(&subject).is_greater_than(&BigDecimal::new(BigInt::from(-232_199), 3)); + assert_that(subject).is_greater_than(BigDecimal::new(BigInt::from(42_830), 3)); +} + +#[test] +fn bigdecimal_is_at_least_other() { + let subject = BigDecimal::new(BigInt::from(42_831), 3); + + assert_that(&subject).is_at_least(&BigDecimal::new(BigInt::from(42_831), 3)); + assert_that(&subject).is_at_least(&BigDecimal::new(BigInt::from(42_830), 3)); + assert_that(subject).is_at_least(BigDecimal::new(BigInt::from(-332), 3)); +} + +#[test] +fn bigdecimal_is_at_most_other() { + let subject = BigDecimal::new(BigInt::from(42_831), 3); + + assert_that(&subject).is_at_most(&BigDecimal::new(BigInt::from(42_831), 3)); + assert_that(&subject).is_at_most(&BigDecimal::new(BigInt::from(42_832), 3)); + assert_that(subject).is_at_most(BigDecimal::new(BigInt::from(65_587_929), 3)); +} + +#[test] +fn bigdecimal_is_negative() { + let subject = BigDecimal::new(BigInt::from(-42_831), 3); + + assert_that(&subject).is_negative(); +} + +#[test] +fn bigdecimal_is_not_negative() { + assert_that(&BigDecimal::new(BigInt::from(42_831), 3)).is_not_negative(); + assert_that(BigDecimal::new(BigInt::from(0), 0)).is_not_negative(); +} + +#[test] +fn bigdecimal_is_positive() { + let subject = BigDecimal::new(BigInt::from(42_831), 3); + + assert_that(&subject).is_positive(); +} + +#[test] +fn bigdecimal_is_not_positive() { + assert_that(&BigDecimal::new(BigInt::from(-42_831), 3)).is_not_positive(); + assert_that(BigDecimal::new(BigInt::from(0), 0)).is_not_positive(); +} + +#[test] +fn bigdecimal_signum_of_zero() { + assert_that(BigDecimal::new(BigInt::from(0), 0)).is_zero(); +} + +#[test] +fn borrowed_bigdecimal_is_negative() { + assert_that(&BigDecimal::new(BigInt::from(-42_831), 3)).is_negative(); +} + +#[test] +fn borrowed_bigdecimal_is_positive() { + assert_that(&BigDecimal::new(BigInt::from(42_831), 3)).is_positive(); +} + +#[test] +fn mutable_borrowed_bigdecimal_is_negative() { + assert_that(&mut BigDecimal::new(BigInt::from(-42_831), 3)).is_negative(); +} + +#[test] +fn mutable_borrowed_bigdecimal_is_positive() { + assert_that(&mut BigDecimal::new(BigInt::from(42_831), 3)).is_positive(); +} + +#[test] +fn bigdecimal_is_zero() { + assert_that(BigDecimal::new(BigInt::from(0), 0)).is_zero(); + assert_that(BigDecimal::new(BigInt::from(-0), 0)).is_zero(); + assert_that(BigDecimal::new(BigInt::from(0), 2)).is_zero(); +} + +#[test] +fn bigdecimal_is_one() { + assert_that(BigDecimal::new(BigInt::from(1), 0)).is_one(); +} + +#[test] +fn borrowed_bigdecimal_is_zero() { + assert_that(&BigDecimal::new(BigInt::from(0), 0)).is_zero(); +} + +#[test] +fn borrowed_bigdecimal_is_one() { + assert_that(&BigDecimal::new(BigInt::from(1), 0)).is_one(); +} + +#[test] +fn bigdecimalref_is_equal_to_other() { + let subject = BigDecimal::new(BigInt::from(42_831), 3); + + assert_that(subject.to_ref()).is_equal_to(BigDecimal::new(BigInt::from(42_831), 3).to_ref()); + + assert_that(BigDecimal::new(BigInt::from(42_831), 3).to_ref()) + .is_equal_to(BigDecimal::new(BigInt::from(428_310), 4).to_ref()); + assert_that(BigDecimal::new(BigInt::from(0), 0).to_ref()) + .is_equal_to(BigDecimal::new(BigInt::from(0), 2).to_ref()); + assert_that(BigDecimal::new(BigInt::from(-0), 0).to_ref()) + .is_equal_to(BigDecimal::new(BigInt::from(0), 0).to_ref()); +} + +#[test] +fn verify_bigdecimalref_is_equal_to_other_fails() { + let subject = BigDecimal::new(BigInt::from(42_831), 3); + + let failures = verify_that(subject.to_ref()) + .is_equal_to(BigDecimal::new(BigInt::from(-42_831), 3).to_ref()) + .display_failures(); + + assert_eq!( + failures, + &[ + r"assertion failed: expected subject is equal to BigDecimalRef { sign: Minus, digits: 42831, scale: 3 } + but was: BigDecimalRef { sign: Plus, digits: 42831, scale: 3 } + expected: BigDecimalRef { sign: Minus, digits: 42831, scale: 3 } +" + ] + ); +} + +#[test] +fn bigdecimalref_is_not_equal_to_other() { + let subject = BigDecimal::new(BigInt::from(42_831), 3); + + assert_that(subject.to_ref()) + .is_not_equal_to(BigDecimal::new(BigInt::from(42_831), 2).to_ref()); +} + +#[test] +fn bigdecimalref_is_negative() { + let subject = BigDecimal::new(BigInt::from(-42_831), 3); + + assert_that(subject.to_ref()).is_negative(); +} + +#[test] +fn bigdecimalref_is_not_negative() { + assert_that(BigDecimal::new(BigInt::from(42_831), 3).to_ref()).is_not_negative(); + assert_that(BigDecimal::new(BigInt::from(0), 0).to_ref()).is_not_negative(); +} + +#[test] +fn bigdecimalref_is_positive() { + let subject = BigDecimal::new(BigInt::from(42_831), 3); + + assert_that(subject.to_ref()).is_positive(); +} + +#[test] +fn bigdecimalref_is_not_positive() { + assert_that(BigDecimal::new(BigInt::from(-42_831), 3).to_ref()).is_not_positive(); + assert_that(BigDecimal::new(BigInt::from(0), 0).to_ref()).is_not_positive(); +} + +#[test] +fn bigdecimalref_signum_of_zero() { + assert_that(BigDecimal::new(BigInt::from(0), 0).to_ref()).is_zero(); +} + +#[test] +fn bigdecimalref_is_zero() { + assert_that(BigDecimal::new(BigInt::from(0), 0).to_ref()).is_zero(); + assert_that(BigDecimal::new(BigInt::from(-0), 0).to_ref()).is_zero(); + assert_that(BigDecimal::new(BigInt::from(0), 2).to_ref()).is_zero(); +} + +#[test] +fn bigdecimalref_is_one() { + assert_that(BigDecimal::new(BigInt::from(1), 0).to_ref()).is_one(); +} diff --git a/src/lib.rs b/src/lib.rs index 50bc9bb..ab92785 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -671,6 +671,8 @@ pub mod prelude; pub mod properties; pub mod spec; +#[cfg(feature = "bigdecimal")] +mod bigdecimal; mod boolean; mod c_string; mod char; diff --git a/tests/version_numbers.rs b/tests/version_numbers.rs index 46e42aa..84ecc1c 100644 --- a/tests/version_numbers.rs +++ b/tests/version_numbers.rs @@ -6,6 +6,8 @@ mod dummy_extern_uses { use anyhow as _; use asserting as _; + #[cfg(feature = "bigdecimal")] + use bigdecimal as _; #[cfg(feature = "float-cmp")] use float_cmp as _; use hashbrown as _;