diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 953e7e8..0f1a61f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -19,7 +19,8 @@ jobs: - name: Install latest stable Rust run: rustup install stable - name: Check crate builds on stable - run: cargo build --features serde,numtraits,rand,arbitrary && cargo build + # NOTE: consider using https://github.com/frewsxcv/cargo-all-features, because all features != their arbitrary combinations + run: cargo build --features serde,numtraits,rand,arbitrary,borsh && cargo build test_nightly: runs-on: ubuntu-latest steps: diff --git a/Cargo.toml b/Cargo.toml index 02cc1bf..5d5987d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bnum" -version = "0.11.0" +version = "0.12.0" authors = ["isaac-holt "] edition = "2021" license = "MIT OR Apache-2.0" @@ -33,6 +33,7 @@ quickcheck = { version = "1.0", optional = true, default-features = false } # proptest = { version = "1.2", optional = true, default-features = false } valuable = { version = "0.1", optional = true, features = ["derive"], default-features = false } # lit-parser = { path = "./lit-parser/", optional = true } +borsh = { version = "^1.5", optional = true, default-features = false, features = ["unstable__schema"] } [dev-dependencies] quickcheck = "1.0" @@ -44,4 +45,7 @@ lto = true # enable link-time optimisation for faster runtime, but slower compil opt-level = 3 # maximum optimisation level for faster runtime, but slower compile time [package.metadata.docs.rs] -all-features = true \ No newline at end of file +all-features = true + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(test_int_bits, values("64", "128"))'] } \ No newline at end of file diff --git a/README.md b/README.md index 8c6c6db..bcfb131 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ This crate uses Rust's const generics to allow creation of integers of arbitrary - **Zero dependencies by default**: `bnum` does not depend on any other crates by default. Support for crates such as [`rand`](https://docs.rs/rand/latest/rand/) and [`serde`](https://docs.rs/serde/latest/serde/) can be enabled with crate [features](#features). - **`no-std` compatible**: `bnum` can be used in `no_std` environments, provided that the [`arbitrary`](#fuzzing) and [`quickcheck`](#quickcheck) features are not enabled. -- **Compile-time integer parsing**: the `from_str_radix` and `parse_str_radix` methods on `bnum` integers are `const`, which allows parsing of integers from string slices at compile time. Note that this is more powerful than compile-time parsing of integer literals. This is because it allows parsing of strings in all radices from `2` to `36` inclusive instead of just `2`, `8`, `10` and `16`. Additionally, the string to be parsed does not have to be a literal: it could, for example, be obtained via [`include_str!`](https://doc.rust-lang.org/core/macro.include_str.html), or [`env!`](https://doc.rust-lang.org/core/macro.env.html)`. +- **Compile-time integer parsing**: the `from_str_radix` and `parse_str_radix` methods on `bnum` integers are `const`, which allows parsing of integers from string slices at compile time. Note that this is more powerful than compile-time parsing of integer literals. This is because it allows parsing of strings in all radices from `2` to `36` inclusive instead of just `2`, `8`, `10` and `16`. Additionally, the string to be parsed does not have to be a literal: it could, for example, be obtained via [`include_str!`](https://doc.rust-lang.org/core/macro.include_str.html), or [`env!`](https://doc.rust-lang.org/core/macro.env.html). - **`const` evaluation**: nearly all methods defined on `bnum` integers are `const`, which allows complex compile-time calculations. ## Installation @@ -32,13 +32,13 @@ This crate uses Rust's const generics to allow creation of integers of arbitrary To install and use `bnum`, simply add the following line to your `Cargo.toml` file in the `[dependencies]` section: ```toml -bnum = "0.11.0" +bnum = "0.12.0" ``` Or, to enable various `bnum` features as well, add for example this line instead: ```toml -bnum = { version = "0.11.0", features = ["rand"] } # enables the "rand" feature +bnum = { version = "0.12.0", features = ["rand"] } # enables the "rand" feature ``` ## Example Usage @@ -113,6 +113,8 @@ The `rand` feature allows creation of random `bnum` integers via the [`rand`](ht The `serde` feature enables serialization and deserialization of `bnum` integers via the [`serde`](https://docs.rs/serde/latest/serde/) and [`serde_big_array`](https://docs.rs/serde-big-array/latest/serde_big_array/) crates. +The `borsh` feature enables serialization and deserialization of `bnum` integers via the [`borsh`](https://docs.rs/borsh/latest/borsh/) crate. + ### `num_traits` and `num_integer` trait implementations The `numtraits` feature includes implementations of traits from the [`num_traits`](https://docs.rs/num-traits/latest/num_traits/) and [`num_integer`](https://docs.rs/num-integer/latest/num_integer/) crates, e.g. [`AsPrimitive`](https://docs.rs/num-traits/latest/num_traits/cast/trait.AsPrimitive.html), [`Signed`](https://docs.rs/num-traits/latest/num_traits/sign/trait.Signed.html), [`Integer`](https://docs.rs/num-integer/latest/num_integer/trait.Integer.html) and [`Roots`](https://docs.rs/num-integer/latest/num_integer/trait.Roots.html). @@ -131,7 +133,7 @@ The `valuable` feature enables the [`Valuable`](https://docs.rs/valuable/latest/ ### Nightly features -Activating the `nightly` feature will enable the `from_be_bytes`, `from_le_bytes`, `from_ne_bytes`, `to_be_bytes`, `to_le_bytes` and `to_ne_bytes` methods on `bnum`'s unsigned and signed integers and will make the `unchecked_...` methods `const`. This comes at the cost of only being able to compile on nightly. The nightly features that this uses are [`generic_const_exprs`](https://github.com/rust-lang/rust/issues/76560), [`const_trait_impl`](https://github.com/rust-lang/rust/issues/67792) and [`const_option_ext`](https://github.com/rust-lang/rust/issues/91930). +Activating the `nightly` feature will enable the `from_be_bytes`, `from_le_bytes`, `from_ne_bytes`, `to_be_bytes`, `to_le_bytes` and `to_ne_bytes` methods on `bnum`'s unsigned and signed integers and will make the `unchecked_...` methods `const`. This comes at the cost of only being able to compile on nightly. The nightly features that this uses are [`generic_const_exprs`](https://github.com/rust-lang/rust/issues/76560), [`const_trait_impl`](https://github.com/rust-lang/rust/issues/67792), [`effects`](https://github.com/rust-lang/rust/issues/102090) and [`const_option`](https://github.com/rust-lang/rust/issues/67441). ## Testing diff --git a/changes/v0.12.0 b/changes/v0.12.0 new file mode 100644 index 0000000..4086aab --- /dev/null +++ b/changes/v0.12.0 @@ -0,0 +1,9 @@ +- Add optional borsh support +- Add `digits_mut` method to unsigned integers. +- `As` and `CastFrom` traits no longer const, as const trait support removed from latest nightly. +- `cast_signed` method for unsigned integers. +- `cast_unsigned` method for signed integers. +- `midpoint` method for integers. +- `carrying_add` and `borrowing_sub` methods for signed integers. +- strict methods added. +- added `(bnum) ` prefix to error messages that were lacking it. \ No newline at end of file diff --git a/src/bint/bigint_helpers.rs b/src/bint/bigint_helpers.rs new file mode 100644 index 0000000..c535ce7 --- /dev/null +++ b/src/bint/bigint_helpers.rs @@ -0,0 +1,20 @@ +macro_rules! bigint_helpers { + ($BUint: ident, $BInt: ident, $Digit: ident) => { + #[doc = doc::bigint_helpers::impl_desc!()] + impl $BInt { + crate::int::bigint_helpers::impls!(I); + } + + #[cfg(test)] + paste::paste! { + mod [<$Digit _digit_tests>] { + use crate::test::types::big_types::$Digit::*; + crate::int::bigint_helpers::tests!(itest); + } + } + }; +} + +use crate::doc; + +crate::macro_impl!(bigint_helpers); diff --git a/src/bint/const_trait_fillers.rs b/src/bint/const_trait_fillers.rs index 1384a50..8654669 100644 --- a/src/bint/const_trait_fillers.rs +++ b/src/bint/const_trait_fillers.rs @@ -56,7 +56,7 @@ macro_rules! const_trait_fillers { #[inline] pub const fn neg(self) -> Self { #[cfg(debug_assertions)] - return crate::errors::option_expect!(self.checked_neg(), crate::errors::err_msg!("attempt to negate with overflow")); + return self.strict_neg(); #[cfg(not(debug_assertions))] self.wrapping_neg() diff --git a/src/bint/mod.rs b/src/bint/mod.rs index e681875..644af1d 100644 --- a/src/bint/mod.rs +++ b/src/bint/mod.rs @@ -18,9 +18,6 @@ macro_rules! ilog { } } -#[cfg(debug_assertions)] -use crate::errors::option_expect; - use crate::digit; use crate::ExpType; use crate::{doc, errors}; @@ -28,6 +25,12 @@ use crate::{doc, errors}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; +#[cfg(feature = "borsh")] +use ::{ + alloc::string::ToString, + borsh::{BorshDeserialize, BorshSchema, BorshSerialize}, +}; + use core::default::Default; use core::iter::{Iterator, Product, Sum}; @@ -46,6 +49,7 @@ macro_rules! mod_impl { #[derive(Clone, Copy, Hash, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] + #[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize, BorshSchema))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[cfg_attr(feature = "valuable", derive(valuable::Valuable))] #[repr(transparent)] @@ -99,6 +103,13 @@ macro_rules! mod_impl { self.bits.trailing_ones() } + #[doc = doc::cast_unsigned!(I)] + #[must_use = doc::must_use_op!()] + #[inline] + pub const fn cast_unsigned(self) -> $BUint { + self.to_bits() + } + #[doc = doc::rotate_left!(I 256, "i")] #[must_use = doc::must_use_op!()] #[inline] @@ -143,7 +154,7 @@ macro_rules! mod_impl { #[inline] pub const fn pow(self, exp: ExpType) -> Self { #[cfg(debug_assertions)] - return option_expect!(self.checked_pow(exp), errors::err_msg!("attempt to calculate power with overflow")); + return self.strict_pow(exp); #[cfg(not(debug_assertions))] self.wrapping_pow(exp) @@ -170,7 +181,7 @@ macro_rules! mod_impl { #[inline] pub const fn abs(self) -> Self { #[cfg(debug_assertions)] - return option_expect!(self.checked_abs(), errors::err_msg!("attempt to negate with overflow")); + return self.strict_abs(); #[cfg(not(debug_assertions))] match self.checked_abs() { @@ -223,6 +234,20 @@ macro_rules! mod_impl { !self.is_negative() &&self.bits.is_power_of_two() } + #[doc = doc::midpoint!(I)] + #[must_use = doc::must_use_op!()] + #[inline] + pub const fn midpoint(self, rhs: Self) -> Self { + let m = Self::from_bits(self.to_bits().midpoint(rhs.to_bits())); + if self.is_negative() == rhs.is_negative() { + // signs agree. in the positive case, we can just compute as if they were unsigned. in the negative case, we compute as if unsigned, and the result is 2^(type bits) too large, but this is 0 (modulo 2^(type bits)) so does not affect the bits + m + } else { + // result is 2^(type bits - 1) too large, so subtract 2^(type bits - 1) by applying xor + m.bitxor(Self::MIN) + } + } + ilog!(ilog, base: Self); ilog!(ilog2); ilog!(ilog10); @@ -420,6 +445,9 @@ macro_rules! mod_impl { test_bignum! { function: ::is_negative(a: itest) } + test_bignum! { + function: ::cast_unsigned(a: itest) + } #[test] fn bit() { @@ -483,6 +511,7 @@ macro_rules! mod_impl { crate::macro_impl!(mod_impl); +mod bigint_helpers; pub mod cast; mod checked; mod cmp; @@ -497,5 +526,6 @@ mod ops; mod overflowing; mod radix; mod saturating; +mod strict; mod unchecked; mod wrapping; diff --git a/src/bint/strict.rs b/src/bint/strict.rs new file mode 100644 index 0000000..02420ea --- /dev/null +++ b/src/bint/strict.rs @@ -0,0 +1,63 @@ +macro_rules! strict { + ($BUint: ident, $BInt: ident, $Digit: ident) => { + #[doc = doc::strict::impl_desc!()] + impl $BInt { + crate::int::strict::impls!(I); + + #[doc = doc::strict::strict_abs!(I)] + #[must_use = doc::must_use_op!()] + #[inline] + pub const fn strict_abs(self) -> Self { + crate::errors::option_expect!( + self.checked_abs(), + crate::errors::err_msg!("attempt to negate with overflow") + ) + } + + #[doc = doc::strict::strict_add_unsigned!(I)] + #[must_use = doc::must_use_op!()] + #[inline] + pub const fn strict_add_unsigned(self, rhs: $BUint) -> Self { + crate::errors::option_expect!( + self.checked_add_unsigned(rhs), + crate::errors::err_msg!("attempt to add with overflow") + ) + } + + #[doc = doc::strict::strict_sub_unsigned!(I)] + #[must_use = doc::must_use_op!()] + #[inline] + pub const fn strict_sub_unsigned(self, rhs: $BUint) -> Self { + crate::errors::option_expect!( + self.checked_sub_unsigned(rhs), + crate::errors::err_msg!("attempt to subtract with overflow") + ) + } + } + + #[cfg(test)] + paste::paste! { + mod [<$Digit _digit_tests>] { + use crate::test::types::big_types::$Digit::*; + crate::int::strict::tests!(itest); + + test_bignum! { + function: ::strict_abs(a: itest), + skip: a.checked_abs().is_none() + } + test_bignum! { + function: ::strict_add_unsigned(a: itest, b: utest), + skip: a.checked_add_unsigned(b).is_none() + } + test_bignum! { + function: ::strict_sub_unsigned(a: itest, b: utest), + skip: a.checked_sub_unsigned(b).is_none() + } + } + } + }; +} + +use crate::doc; + +crate::macro_impl!(strict); diff --git a/src/buint/as_float.rs b/src/buint/as_float.rs index 389886a..3261564 100644 --- a/src/buint/as_float.rs +++ b/src/buint/as_float.rs @@ -1,7 +1,7 @@ use crate::ExpType; use crate::cast::CastFrom; -#[cfg_attr(feature = "nightly", const_trait)] +// #[cfg_attr(feature = "nightly", const_trait)] pub trait CastToFloatHelper: Copy { const ZERO: Self; const BITS: ExpType; @@ -93,7 +93,7 @@ pub(crate) use impl_helper_buint; crate::macro_impl!(impl_helper_buint); -#[cfg_attr(feature = "nightly", const_trait)] +// #[cfg_attr(feature = "nightly", const_trait)] pub trait CastToFloatConsts { type M: Mantissa; @@ -131,7 +131,7 @@ macro_rules! cast_to_float_consts { cast_to_float_consts!(f32; u32, f64; u64); -#[cfg_attr(feature = "nightly", const_trait)] +// #[cfg_attr(feature = "nightly", const_trait)] pub trait Mantissa { const ONE: Self; const TWO: Self; @@ -144,7 +144,7 @@ pub trait Mantissa { fn add(self, rhs: Self) -> Self; fn sub(self, rhs: Self) -> Self; fn leading_zeros(self) -> ExpType; - fn bitand(self, rhs: Self) -> Self; + // fn bitand(self, rhs: Self) -> Self; fn gt(&self, rhs: &Self) -> bool; } @@ -188,10 +188,10 @@ macro_rules! impl_mantissa_for_uint { Self::leading_zeros(self) as ExpType } - #[inline] - fn bitand(self, rhs: Self) -> Self { - self & rhs - } + // #[inline] + // fn bitand(self, rhs: Self) -> Self { + // self & rhs + // } #[inline] fn gt(&self, rhs: &Self) -> bool { diff --git a/src/buint/bigint_helpers.rs b/src/buint/bigint_helpers.rs index 14fc9b8..54bf1bc 100644 --- a/src/buint/bigint_helpers.rs +++ b/src/buint/bigint_helpers.rs @@ -3,32 +3,9 @@ use crate::doc; macro_rules! bigint_helpers { ($BUint: ident, $BInt: ident, $Digit: ident) => { + #[doc = doc::bigint_helpers::impl_desc!()] impl $BUint { - #[doc = doc::bigint_helpers::carrying_add!(U)] - #[must_use = doc::must_use_op!()] - #[inline] - pub const fn carrying_add(self, rhs: Self, carry: bool) -> (Self, bool) { - let (s1, o1) = self.overflowing_add(rhs); - if carry { - let (s2, o2) = s1.overflowing_add(Self::ONE); - (s2, o1 || o2) - } else { - (s1, o1) - } - } - - #[doc = doc::bigint_helpers::borrowing_sub!(U)] - #[must_use = doc::must_use_op!()] - #[inline] - pub const fn borrowing_sub(self, rhs: Self, borrow: bool) -> (Self, bool) { - let (s1, o1) = self.overflowing_sub(rhs); - if borrow { - let (s2, o2) = s1.overflowing_sub(Self::ONE); - (s2, o1 || o2) - } else { - (s1, o1) - } - } + crate::int::bigint_helpers::impls!(U); #[doc = doc::bigint_helpers::widening_mul!(U)] #[must_use = doc::must_use_op!()] @@ -84,26 +61,12 @@ macro_rules! bigint_helpers { #[cfg(test)] paste::paste! { mod [<$Digit _digit_tests>] { - use crate::test::{test_bignum, types::*}; + // use crate::test::{test_bignum, types::*}; use crate::test::types::big_types::$Digit::*; type U64 = crate::$BUint::<{64 / $Digit::BITS as usize}>; - test_bignum! { - function: ::carrying_add(a: utest, rhs: utest, carry: bool), - cases: [ - (utest::MAX, 1u8, true), - (utest::MAX, 1u8, false) - ] - } - - test_bignum! { - function: ::borrowing_sub(a: utest, rhs: utest, carry: bool), - cases: [ - (0u8, 1u8, false), - (0u8, 1u8, true) - ] - } + crate::int::bigint_helpers::tests!(utest); test_bignum! { function: ::widening_mul(a: u64, b: u64), diff --git a/src/buint/endian.rs b/src/buint/endian.rs index e484b7f..532f847 100644 --- a/src/buint/endian.rs +++ b/src/buint/endian.rs @@ -233,7 +233,7 @@ macro_rules! endian { #[inline] pub const fn from_be_bytes(bytes: [u8; N * digit::$Digit::BYTES as usize]) -> Self { let mut out = Self::ZERO; - let arr_ptr = bytes.as_ptr(); + // let arr_ptr = bytes.as_ptr(); let mut i = 0; while i < N { let mut digit_bytes = [0u8; digit::$Digit::BYTES as usize]; @@ -256,7 +256,7 @@ macro_rules! endian { #[inline] pub const fn from_le_bytes(bytes: [u8; N * digit::$Digit::BYTES as usize]) -> Self { let mut out = Self::ZERO; - let arr_ptr = bytes.as_ptr(); + // let arr_ptr = bytes.as_ptr(); let mut i = 0; while i < N { let mut digit_bytes = [0u8; digit::$Digit::BYTES as usize]; diff --git a/src/buint/mod.rs b/src/buint/mod.rs index 3d00417..ecec99a 100644 --- a/src/buint/mod.rs +++ b/src/buint/mod.rs @@ -11,6 +11,12 @@ use ::{ serde_big_array::BigArray, }; +#[cfg(feature = "borsh")] +use ::{ + alloc::string::ToString, + borsh::{BorshDeserialize, BorshSchema, BorshSerialize}, +}; + use core::default::Default; use core::iter::{Iterator, Product, Sum}; @@ -29,6 +35,7 @@ macro_rules! mod_impl { #[derive(Clone, Copy, Hash, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] + #[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize, BorshSchema))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[cfg_attr(feature = "valuable", derive(valuable::Valuable))] #[repr(transparent)] @@ -135,6 +142,13 @@ macro_rules! mod_impl { ones } + #[doc = doc::cast_signed!(U)] + #[must_use = doc::must_use_op!()] + #[inline] + pub const fn cast_signed(self) -> $BInt { + $BInt::::from_bits(self) + } + #[inline] const unsafe fn rotate_digits_left(self, n: usize) -> Self { let mut out = Self::ZERO; @@ -231,10 +245,8 @@ macro_rules! mod_impl { #[inline] pub const fn pow(self, exp: ExpType) -> Self { #[cfg(debug_assertions)] - return option_expect!( - self.checked_pow(exp), - errors::err_msg!("attempt to calculate power with overflow") - ); + return self.strict_pow(exp); + #[cfg(not(debug_assertions))] self.wrapping_pow(exp) } @@ -291,6 +303,13 @@ macro_rules! mod_impl { self.wrapping_next_power_of_two() } + #[doc = doc::midpoint!(U)] + #[must_use = doc::must_use_op!()] + #[inline] + pub const fn midpoint(self, rhs: Self) -> Self { + (self.bitxor(rhs).shr(1)).add(self.bitand(rhs)) + } + #[doc = doc::ilog2!(U)] #[must_use = doc::must_use_op!()] #[inline] @@ -455,6 +474,18 @@ macro_rules! mod_impl { digit & (1 << (index & digit::$Digit::BITS_MINUS_1)) != 0 } + #[doc = doc::set_bit!(U 256)] + #[inline] + pub fn set_bit(&mut self, index: ExpType, value: bool) { + let digit = &mut self.digits[index as usize >> digit::$Digit::BIT_SHIFT]; + let shift = index & digit::$Digit::BITS_MINUS_1; + if value { + *digit |= (1 << shift); + } else { + *digit &= !(1 << shift); + } + } + /// Returns an integer whose value is `2^power`. This is faster than using a shift left on `Self::ONE`. /// /// # Panics @@ -484,6 +515,13 @@ macro_rules! mod_impl { &self.digits } + /// Returns the digits stored in `self` as a mutable array. Digits are little endian (least significant digit first). + #[must_use] + #[inline(always)] + pub fn digits_mut(&mut self) -> &mut [$Digit; N] { + &mut self.digits + } + /// Creates a new unsigned integer from the given array of digits. Digits are stored as little endian (least significant digit first). #[must_use] #[inline(always)] @@ -611,10 +649,12 @@ macro_rules! mod_impl { function: ::next_power_of_two(a: utest), skip: debug_skip!(a.checked_next_power_of_two().is_none()) } - test_bignum! { function: ::is_power_of_two(a: utest) } + test_bignum! { + function: ::cast_signed(a: utest) + } #[test] fn digits() { @@ -683,7 +723,7 @@ macro_rules! mod_impl { }; } -crate::main_impl!(mod_impl); +crate::macro_impl!(mod_impl); pub mod float_as; mod bigint_helpers; @@ -702,5 +742,6 @@ mod ops; mod overflowing; mod radix; mod saturating; +mod strict; mod unchecked; mod wrapping; diff --git a/src/buint/strict.rs b/src/buint/strict.rs new file mode 100644 index 0000000..395d920 --- /dev/null +++ b/src/buint/strict.rs @@ -0,0 +1,36 @@ +macro_rules! strict { + ($BUint: ident, $BInt: ident, $Digit: ident) => { + #[doc = doc::strict::impl_desc!()] + impl $BUint { + crate::int::strict::impls!(U); + + #[doc = doc::strict::strict_add_signed!(U)] + #[must_use = doc::must_use_op!()] + #[inline] + pub const fn strict_add_signed(self, rhs: $BInt) -> Self { + crate::errors::option_expect!( + self.checked_add_signed(rhs), + crate::errors::err_msg!("attempt to add with overflow") + ) + } + } + + #[cfg(test)] + paste::paste! { + mod [<$Digit _digit_tests>] { + use crate::test::types::big_types::$Digit::*; + + crate::int::strict::tests!(utest); + + test_bignum! { + function: ::strict_add_signed(a: utest, b: itest), + skip: a.checked_add_signed(b).is_none() + } + } + } + }; +} + +use crate::doc; + +crate::macro_impl!(strict); diff --git a/src/buint/unchecked.rs b/src/buint/unchecked.rs index 3a1d606..0601a54 100644 --- a/src/buint/unchecked.rs +++ b/src/buint/unchecked.rs @@ -1,6 +1,14 @@ macro_rules! unchecked { ($BUint: ident, $BInt: ident, $Digit: ident) => { - crate::int::unchecked::impls!($BUint, I); + crate::int::unchecked::impls!($BUint, U); + + #[cfg(test)] + paste::paste! { + mod [<$Digit _digit_tests>] { + use crate::test::types::big_types::$Digit::*; + crate::int::unchecked::tests!(utest); + } + } }; } diff --git a/src/cast.rs b/src/cast.rs index c64b5ad..74b9a46 100644 --- a/src/cast.rs +++ b/src/cast.rs @@ -2,12 +2,13 @@ /// Backend implementation trait for panic-free casting between numeric types. -#[cfg_attr(feature = "nightly", const_trait)] +// #[cfg_attr(feature = "nightly", const_trait)] pub trait CastFrom { fn cast_from(from: T) -> Self; } -#[cfg_attr(feature = "nightly", const_trait)] +// #[cfg_attr(feature = "nightly", const_trait)] +#[cfg(test)] pub(crate) trait CastTo { fn cast_to(self) -> U; } @@ -54,9 +55,12 @@ assert_eq!(b, f.as_()); #[cfg(feature = "nightly")] macro_rules! as_trait { () => { - impl const CastTo for T + // impl const CastTo for T + #[cfg(test)] + impl CastTo for T where - U: ~const CastFrom, + // U: ~const CastFrom, + U: CastFrom, { fn cast_to(self) -> U { U::cast_from(self) @@ -64,7 +68,7 @@ macro_rules! as_trait { } #[doc = as_trait_doc!()] - #[const_trait] + // #[const_trait] pub trait As { #[doc = as_method_doc!()] fn as_(self) -> T @@ -73,11 +77,13 @@ macro_rules! as_trait { Self: Sized; } - impl const As for U { + // impl const As for U { + impl As for U { #[inline] fn as_(self) -> T where - T: ~const CastFrom, + // T: ~const CastFrom, + T: CastFrom, Self: Sized, { T::cast_from(self) @@ -89,6 +95,7 @@ macro_rules! as_trait { #[cfg(not(feature = "nightly"))] macro_rules! as_trait { () => { + #[cfg(test)] impl CastTo for T where U: CastFrom, diff --git a/src/doc/bigint_helpers.rs b/src/doc/bigint_helpers.rs index 33c40e8..c979f61 100644 --- a/src/doc/bigint_helpers.rs +++ b/src/doc/bigint_helpers.rs @@ -1,3 +1,11 @@ use crate::doc; +macro_rules! impl_desc { + () => { + "Bigint helper methods: common functions used to implement big integer arithmetic." + }; +} + +pub(crate) use impl_desc; + doc::link_doc_comment!(carrying_add, borrowing_sub, widening_mul, carrying_mul); diff --git a/src/doc/mod.rs b/src/doc/mod.rs index 333869d..9ab2334 100644 --- a/src/doc/mod.rs +++ b/src/doc/mod.rs @@ -6,8 +6,8 @@ pub mod endian; pub mod overflowing; pub mod radix; pub mod saturating; +pub mod strict; pub mod unchecked; - pub mod wrapping; macro_rules! arithmetic_doc { @@ -333,7 +333,7 @@ macro_rules! bit { ($sign: ident $bits: literal) => { doc::doc_comment! { $sign $bits, - "Returns a boolean representing the bit in the given position (`true` if the bit is set). The least significant bit is at index `0`, the most significant bit is at index `Self::BITS - 1`", + "Returns a boolean representing the bit in the given position (`true` if the bit is set). The least significant bit is at index `0`, the most significant bit is at index `Self::BITS - 1`.", "let n = " doc::type_str!($sign $bits) "::from(0b001010100101010101u32);\n" "assert!(n.bit(0));\n" @@ -345,6 +345,23 @@ macro_rules! bit { pub(crate) use bit; +macro_rules! set_bit { + ($sign: ident $bits: literal) => { + doc::doc_comment! { + $sign $bits, + "Sets/unsets the bit in the given position (`1` if value is true). The least significant bit is at index `0`, the most significant bit is at index `Self::BITS - 1`.", + + "let mut n = " doc::type_str!($sign $bits) "::from(0b001010100101010101u32);\n" + "assert!(n.bit(2));\n" + "n.set_bit(2, false);\n" + "assert!(!n.bit(2));\n" + "assert_eq!(n, " doc::type_str!($sign $bits) "::from(0b001010100101010001u32));" + } + }; +} + +pub(crate) use set_bit; + macro_rules! is_zero { ($sign: ident $bits: literal) => { doc::doc_comment! { @@ -387,5 +404,8 @@ crate::doc::link_doc_comment! { abs, signum, is_positive, - is_negative + is_negative, + cast_signed, + cast_unsigned, + midpoint } diff --git a/src/doc/strict.rs b/src/doc/strict.rs new file mode 100644 index 0000000..c306961 --- /dev/null +++ b/src/doc/strict.rs @@ -0,0 +1,25 @@ +macro_rules! impl_desc { + () => { + doc::arithmetic_impl_desc!("Strict", "strict", "Each method will always panic if overflow/underflow occurs (i.e. when the checked equivalent would return `None`), regardless of whether overflow checks are enabled.") + }; +} + +pub(crate) use impl_desc; + +crate::doc::link_doc_comment!( + strict_abs, + strict_add, + strict_add_signed, + strict_add_unsigned, + strict_div, + strict_div_euclid, + strict_mul, + strict_neg, + strict_pow, + strict_rem, + strict_rem_euclid, + strict_shl, + strict_shr, + strict_sub, + strict_sub_unsigned +); diff --git a/src/doc/unchecked.rs b/src/doc/unchecked.rs index 0c10bd8..409cf1a 100644 --- a/src/doc/unchecked.rs +++ b/src/doc/unchecked.rs @@ -1,6 +1,6 @@ macro_rules! impl_desc { () => { - doc::arithmetic_impl_desc!("Unchecked", "unchecked", "Each method results in undefined behavior if overflow/underflow occurs, i.e. when the checked equivalent would return `None`.") + doc::arithmetic_impl_desc!("Unchecked", "unchecked", "Each method results in undefined behavior if overflow/underflow occurs (i.e. when the checked equivalent would return `None`).") }; } diff --git a/src/doc/wrapping.rs b/src/doc/wrapping.rs index a44c2a8..c8a80a5 100644 --- a/src/doc/wrapping.rs +++ b/src/doc/wrapping.rs @@ -1,6 +1,6 @@ macro_rules! impl_desc { () => { - doc::arithmetic_impl_desc!("Wrapping", "wrapping", "Each method returns of the calculation truncated to the number of bits of `self`, i.e. they each return the first item in the tuple returned by their overflowing equivalent.") + doc::arithmetic_impl_desc!("Wrapping", "wrapping", "Each method returns of the calculation truncated to the number of bits of `self` (i.e. they each return the first item in the tuple returned by their overflowing equivalent).") }; } diff --git a/src/int/bigint_helpers.rs b/src/int/bigint_helpers.rs new file mode 100644 index 0000000..7e387da --- /dev/null +++ b/src/int/bigint_helpers.rs @@ -0,0 +1,56 @@ +macro_rules! impls { + ($sign: ident) => { + #[doc = doc::bigint_helpers::carrying_add!($sign)] + #[must_use = doc::must_use_op!()] + #[inline] + pub const fn carrying_add(self, rhs: Self, carry: bool) -> (Self, bool) { + let (s1, o1) = self.overflowing_add(rhs); + if carry { + let (s2, o2) = s1.overflowing_add(Self::ONE); + (s2, o1 ^ o2) + } else { + (s1, o1) + } + } + + #[doc = doc::bigint_helpers::borrowing_sub!($sign)] + #[must_use = doc::must_use_op!()] + #[inline] + pub const fn borrowing_sub(self, rhs: Self, borrow: bool) -> (Self, bool) { + let (s1, o1) = self.overflowing_sub(rhs); + if borrow { + let (s2, o2) = s1.overflowing_sub(Self::ONE); + (s2, o1 ^ o2) + } else { + (s1, o1) + } + } + }; +} + +pub(crate) use impls; + +#[cfg(test)] +macro_rules! tests { + ($int: ty) => { + use crate::test::{test_bignum, types::*}; + + test_bignum! { + function: <$int>::carrying_add(a: $int, b: $int, carry: bool), + cases: [ + (<$int>::MAX, 1u8, true), + (<$int>::MAX, 1u8, false) + ] + } + test_bignum! { + function: <$int>::borrowing_sub(a: $int, b: $int, borrow: bool), + cases: [ + (<$int>::MIN, 1u8, false), + (<$int>::MIN, 1u8, true) + ] + } + }; +} + +#[cfg(test)] +pub(crate) use tests; diff --git a/src/int/mod.rs b/src/int/mod.rs index 7756d71..d8c026e 100644 --- a/src/int/mod.rs +++ b/src/int/mod.rs @@ -1,3 +1,4 @@ +pub mod bigint_helpers; pub mod cast; pub mod checked; pub mod cmp; @@ -9,6 +10,7 @@ pub mod numtraits; pub mod ops; pub mod radix; +pub mod strict; pub mod unchecked; #[cfg(test)] @@ -59,6 +61,9 @@ macro_rules! tests { test_bignum! { function: <$int>::abs_diff(a: $int, b: $int) } + test_bignum! { + function: <$int>::midpoint(a: $int, b: $int) + } test_bignum! { function: <$int>::ilog(a: $int, base: $int), skip: a <= 0 || base <= 1 diff --git a/src/int/numtraits.rs b/src/int/numtraits.rs index 5ae28b3..e6d885e 100644 --- a/src/int/numtraits.rs +++ b/src/int/numtraits.rs @@ -297,24 +297,9 @@ macro_rules! prim_int_methods { fn to_be(self) -> Self; fn to_le(self) -> Self; fn pow(self, exp: u32) -> Self; - } - - #[cfg(has_leading_trailing_ones)] - #[inline] - fn leading_ones(self) -> u32 { - Self::leading_ones(self) - } - - #[cfg(has_leading_trailing_ones)] - #[inline] - fn trailing_ones(self) -> u32 { - Self::trailing_ones(self) - } - - #[cfg(has_reverse_bits)] - #[inline] - fn reverse_bits(self) -> Self { - Self::reverse_bits(self) + fn leading_ones(self) -> u32; + fn trailing_ones(self) -> u32; + fn reverse_bits(self) -> Self; } }; } diff --git a/src/int/ops.rs b/src/int/ops.rs index 799ff8c..c660e3d 100644 --- a/src/int/ops.rs +++ b/src/int/ops.rs @@ -200,37 +200,10 @@ macro_rules! all_shift_impls { i128 ); - #[cfg(feature = "usize_exptype")] - crate::int::ops::try_shift_impl!( - $Struct, $BUint, $BInt; - Shl, - shl, - ShlAssign, - shl_assign, - "attempt to shift left with overflow", - u32, - u64, - u128 - ); - - #[cfg(feature = "usize_exptype")] - crate::int::ops::try_shift_impl!( - $Struct, $BUint, $BInt; - Shr, - shr, - ShrAssign, - shr_assign, - "attempt to shift right with overflow", - u32, - u64, - u128 - ); - crate::int::ops::shift_impl!($Struct, Shl, shl, ShlAssign, shl_assign, u8, u16); crate::int::ops::shift_impl!($Struct, Shr, shr, ShrAssign, shr_assign, u8, u16); - #[cfg(not(feature = "usize_exptype"))] crate::int::ops::try_shift_impl!( $Struct, $BUint, $BInt; Shl, @@ -243,7 +216,6 @@ macro_rules! all_shift_impls { u128 ); - #[cfg(not(feature = "usize_exptype"))] crate::int::ops::try_shift_impl!( $Struct, $BUint, $BInt; Shr, @@ -310,10 +282,7 @@ macro_rules! trait_fillers { #[inline] pub const fn add(self, rhs: Self) -> Self { #[cfg(debug_assertions)] - return crate::errors::option_expect!( - self.checked_add(rhs), - "attempt to add with overflow" - ); + return self.strict_add(rhs); #[cfg(not(debug_assertions))] self.wrapping_add(rhs) @@ -322,10 +291,7 @@ macro_rules! trait_fillers { #[inline] pub const fn mul(self, rhs: Self) -> Self { #[cfg(debug_assertions)] - return crate::errors::option_expect!( - self.checked_mul(rhs), - "attempt to multiply with overflow" - ); + return self.strict_mul(rhs); #[cfg(not(debug_assertions))] self.wrapping_mul(rhs) @@ -334,10 +300,7 @@ macro_rules! trait_fillers { #[inline] pub const fn shl(self, rhs: ExpType) -> Self { #[cfg(debug_assertions)] - return crate::errors::option_expect!( - self.checked_shl(rhs), - "attempt to shift left with overflow" - ); + return self.strict_shl(rhs); #[cfg(not(debug_assertions))] self.wrapping_shl(rhs) @@ -346,10 +309,7 @@ macro_rules! trait_fillers { #[inline] pub const fn shr(self, rhs: ExpType) -> Self { #[cfg(debug_assertions)] - return crate::errors::option_expect!( - self.checked_shr(rhs), - "attempt to shift left with overflow" - ); + return self.strict_shr(rhs); #[cfg(not(debug_assertions))] self.wrapping_shr(rhs) @@ -358,10 +318,7 @@ macro_rules! trait_fillers { #[inline] pub const fn sub(self, rhs: Self) -> Self { #[cfg(debug_assertions)] - return crate::errors::option_expect!( - self.checked_sub(rhs), - "attempt to subtract with overflow" - ); + return self.strict_sub(rhs); #[cfg(not(debug_assertions))] self.wrapping_sub(rhs) diff --git a/src/int/strict.rs b/src/int/strict.rs new file mode 100644 index 0000000..05b1b4a --- /dev/null +++ b/src/int/strict.rs @@ -0,0 +1,157 @@ +macro_rules! impls { + ($sign: ident) => { + #[doc = doc::strict::strict_add!($sign)] + #[must_use = doc::must_use_op!()] + #[inline] + pub const fn strict_add(self, rhs: Self) -> Self { + crate::errors::option_expect!( + self.checked_add(rhs), + crate::errors::err_msg!("attempt to add with overflow") + ) + } + + #[doc = doc::strict::strict_sub!($sign)] + #[must_use = doc::must_use_op!()] + #[inline] + pub const fn strict_sub(self, rhs: Self) -> Self { + crate::errors::option_expect!( + self.checked_sub(rhs), + crate::errors::err_msg!("attempt to subtract with overflow") + ) + } + + #[doc = doc::strict::strict_mul!($sign)] + #[must_use = doc::must_use_op!()] + #[inline] + pub const fn strict_mul(self, rhs: Self) -> Self { + crate::errors::option_expect!( + self.checked_mul(rhs), + crate::errors::err_msg!("attempt to multiply with overflow") + ) + } + + #[doc = doc::strict::strict_div!($sign)] + #[must_use = doc::must_use_op!()] + #[inline] + pub const fn strict_div(self, rhs: Self) -> Self { + self.div(rhs) + } + + #[doc = doc::strict::strict_div_euclid!($sign)] + #[must_use = doc::must_use_op!()] + #[inline] + pub const fn strict_div_euclid(self, rhs: Self) -> Self { + self.div_euclid(rhs) + } + + #[doc = doc::strict::strict_rem!($sign)] + #[must_use = doc::must_use_op!()] + #[inline] + pub const fn strict_rem(self, rhs: Self) -> Self { + self.rem(rhs) + } + + #[doc = doc::strict::strict_rem_euclid!($sign)] + #[must_use = doc::must_use_op!()] + #[inline] + pub const fn strict_rem_euclid(self, rhs: Self) -> Self { + self.rem_euclid(rhs) + } + + #[doc = doc::strict::strict_neg!($sign)] + #[must_use = doc::must_use_op!()] + #[inline] + pub const fn strict_neg(self) -> Self { + crate::errors::option_expect!( + self.checked_neg(), crate::errors::err_msg!("attempt to negate with overflow") + ) + } + + #[doc = doc::strict::strict_shl!($sign)] + #[must_use = doc::must_use_op!()] + #[inline] + pub const fn strict_shl(self, rhs: crate::ExpType) -> Self { + crate::errors::option_expect!( + self.checked_shl(rhs), + crate::errors::err_msg!("attempt to shift left with overflow") + ) + } + + #[doc = doc::strict::strict_shr!($sign)] + #[must_use = doc::must_use_op!()] + #[inline] + pub const fn strict_shr(self, rhs: crate::ExpType) -> Self { + crate::errors::option_expect!( + self.checked_shr(rhs), + crate::errors::err_msg!("attempt to shift right with overflow") + ) + } + + #[doc = doc::strict::strict_pow!($sign)] + #[must_use = doc::must_use_op!()] + #[inline] + pub const fn strict_pow(self, exp: crate::ExpType) -> Self { + crate::errors::option_expect!( + self.checked_pow(exp), + crate::errors::err_msg!("attempt to calculate power with overflow") + ) + } + } +} + +pub(crate) use impls; + +#[cfg(test)] +macro_rules! tests { + ($int: ty) => { + use crate::test::{test_bignum, types::*}; + + test_bignum! { + function: <$int>::strict_add(a: $int, b: $int), + skip: a.checked_add(b).is_none() + } + test_bignum! { + function: <$int>::strict_sub(a: $int, b: $int), + skip: a.checked_sub(b).is_none() + } + test_bignum! { + function: <$int>::strict_mul(a: $int, b: $int), + skip: a.checked_mul(b).is_none() + } + test_bignum! { + function: <$int>::strict_div(a: $int, b: $int), + skip: a.checked_div(b).is_none() + } + test_bignum! { + function: <$int>::strict_div_euclid(a: $int, b: $int), + skip: a.checked_div_euclid(b).is_none() + } + test_bignum! { + function: <$int>::strict_rem(a: $int, b: $int), + skip: a.checked_rem(b).is_none() + } + test_bignum! { + function: <$int>::strict_rem_euclid(a: $int, b: $int), + skip: a.checked_rem_euclid(b).is_none() + } + test_bignum! { + function: <$int>::strict_neg(a: $int), + skip: a.checked_neg().is_none() + } + test_bignum! { + function: <$int>::strict_shl(a: $int, b: u8), + skip: a.checked_shl(b as u32).is_none() + } + test_bignum! { + function: <$int>::strict_shr(a: $int, b: u8), + skip: a.checked_shr(b as u32).is_none() + } + test_bignum! { + function: <$int>::strict_pow(a: $int, b: u8), + skip: a.checked_pow(b as u32).is_none() + } + } +} + +#[cfg(test)] +pub(crate) use tests; diff --git a/src/lib.rs b/src/lib.rs index 3e48a9b..3204455 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,7 +4,8 @@ feature( generic_const_exprs, const_trait_impl, - const_option_ext + const_option, + // effects, ) )] #![cfg_attr( @@ -15,8 +16,10 @@ float_minimum_maximum, wrapping_next_power_of_two, float_next_up_down, - unchecked_math, unchecked_shifts, + integer_sign_cast, + num_midpoint, + strict_overflow_ops, ) )] #![doc = include_str!("../README.md")] @@ -56,6 +59,13 @@ use test::types::*; type ExpType = u32; +mod bigints { + pub use crate::bint::{BInt, BIntD16, BIntD32, BIntD8}; + pub use crate::buint::{BUint, BUintD16, BUintD32, BUintD8}; +} + +pub use bigints::*; + macro_rules! macro_impl { ($name: ident) => { use crate::bigints::*; @@ -90,13 +100,6 @@ bint_as_different_digit_bigint!(BUintD8, BIntD8, u8; (BInt, u64), (BIntD32, u32) pub(crate) use main_impl; -mod bigints { - pub use crate::bint::{BInt, BIntD16, BIntD32, BIntD8}; - pub use crate::buint::{BUint, BUintD16, BUintD32, BUintD8}; -} - -pub use bigints::*; - /// Trait for fallible conversions between `bnum` integer types. /// /// Unfortunately, [`TryFrom`] cannot currently be used for conversions between `bnum` integers, since [`TryFrom for T`](https://doc.rust-lang.org/std/convert/trait.TryFrom.html#impl-TryFrom%3CU%3E-for-T) is already implemented by the standard library (and so it is not possible to implement `TryFrom> for BUint`). When the [`generic_const_exprs`](https://github.com/rust-lang/rust/issues/76560) feature becomes stabilised, it may be possible to use [`TryFrom`] instead of `BTryFrom`. `BTryFrom` is designed to have the same behaviour as [`TryFrom`] for conversions between two primitive types, and conversions between a primitive type and a bnum type. `BTryFrom` is a workaround for the issue described above, and so you should not implement it yourself. It should only be used for conversions between `bnum` integers. diff --git a/src/nightly.rs b/src/nightly.rs index 13b6673..da2d4fa 100644 --- a/src/nightly.rs +++ b/src/nightly.rs @@ -51,7 +51,7 @@ pub(crate) use impl_const; #[cfg(feature = "nightly")] macro_rules! const_impl { { impl $(<$(const $C: ident : $ty: ty), +>)? const $($tt: tt) + } => { - impl $(<$(const $C: $ty), +>)? const $($tt) + + impl $(<$(const $C: $ty), +>)? $($tt) + } }