diff --git a/Cargo.toml b/Cargo.toml index 596962a..b9a9bbf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bnum" -version = "0.9.1" +version = "0.10.0" authors = ["isaac-holt "] edition = "2021" license = "MIT OR Apache-2.0" diff --git a/README.md b/README.md index 39e730b..522f59d 100644 --- a/README.md +++ b/README.md @@ -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.9.1" +bnum = "0.10.0" ``` Or, to enable various `bnum` features as well, add for example this line instead: ```toml -bnum = { version = "0.9.1", features = ["rand"] } # enables the "rand" feature +bnum = { version = "0.10.0", features = ["rand"] } # enables the "rand" feature ``` ## Example Usage diff --git a/bench/benches/benchmark.rs b/bench/benches/benchmark.rs index ef431fe..5fb96cd 100644 --- a/bench/benches/benchmark.rs +++ b/bench/benches/benchmark.rs @@ -1,6 +1,6 @@ #![feature(wrapping_next_power_of_two, int_roundings)] -use bnum::types::{U128 as BU128, U512}; +use bnum::types::{U128, U512}; // use bnum::prelude::*; use core::iter::Iterator; use criterion::black_box; @@ -34,17 +34,17 @@ macro_rules! unzip { // unzip!(fn unzip3); unzip!(fn unzip2); -mod uuint { - uint::construct_uint! { - pub struct UU128(2); - } +// mod uuint { +// uint::construct_uint! { +// pub struct UU128(2); +// } - uint::construct_uint! { - pub struct UU512(8); - } -} +// uint::construct_uint! { +// pub struct UU512(8); +// } +// } -use uuint::*; +// use uuint::*; macro_rules! bench_against_primitive { { $primitive: ty; $($method: ident ($($param: ident : $(ref $re: tt)? $ty: ty), *);) * } => { @@ -75,7 +75,7 @@ macro_rules! bench_against_primitive { group.bench_with_input(BenchmarkId::new("core", SIZE_ID), &prim_inputs, |b, inputs| { b.iter(|| { for ($($param), *, ()) in inputs.iter().cloned() { - let _ = []::$method($($($re)? black_box($param)), *); + let _ = [<$primitive>]::$method($($($re)? black_box($param)), *); } }) }); @@ -106,66 +106,67 @@ trait Format { fn lower_exp(self) -> String; } -macro_rules! impl_format { - ($($ty: ty), *) => { - $( - impl Format for $ty { - fn display(self) -> String { - format!("{}", self) - } - fn debug(self) -> String { - format!("{:?}", self) - } - fn binary(self) -> String { - format!("{:b}", self) - } - fn upper_hex(self) -> String { - format!("{:X}", self) - } - fn lower_hex(self) -> String { - format!("{:x}", self) - } - fn octal(self) -> String { - format!("{:o}", self) - } - fn upper_exp(self) -> String { - format!("{:E}", self) - } - fn lower_exp(self) -> String { - format!("{:e}", self) - } - } - )* - }; -} - -impl_format!(u128, BU128); - -use core::cmp::{PartialEq, PartialOrd}; -use core::ops::{BitAnd, BitOr, BitXor, Not}; - -// use num_traits::PrimInt; - -trait BenchFrom { - fn from(value: T) -> Self; -} - -impl<'a> BenchFrom<&'a BU128> for &'a UU128 { - fn from(value: &'a BU128) -> Self { - unsafe { - &*(value as *const BU128 as *const UU128) - } - } -} - -impl BenchFrom for UU128 { - fn from(value: u128) -> Self { - From::from(value) - } -} +// macro_rules! impl_format { +// ($($ty: ty), *) => { +// $( +// impl Format for $ty { +// fn display(self) -> String { +// format!("{}", self) +// } +// fn debug(self) -> String { +// format!("{:?}", self) +// } +// fn binary(self) -> String { +// format!("{:b}", self) +// } +// fn upper_hex(self) -> String { +// format!("{:X}", self) +// } +// fn lower_hex(self) -> String { +// format!("{:x}", self) +// } +// fn octal(self) -> String { +// format!("{:o}", self) +// } +// fn upper_exp(self) -> String { +// format!("{:E}", self) +// } +// fn lower_exp(self) -> String { +// format!("{:e}", self) +// } +// } +// )* +// }; +// } + +// impl_format!(u128, BU128); + +// use core::cmp::{PartialEq, PartialOrd}; +// use core::ops::{BitAnd, BitOr, BitXor, Not}; + +// // use num_traits::PrimInt; + +// trait BenchFrom { +// fn from(value: T) -> Self; +// } + +// impl<'a> BenchFrom<&'a BU128> for &'a UU128 { +// fn from(value: &'a BU128) -> Self { +// unsafe { +// &*(value as *const BU128 as *const UU128) +// } +// } +// } + +// impl BenchFrom for UU128 { +// fn from(value: u128) -> Self { +// From::from(value) +// } +// } bench_against_primitive! { - u512; + u128; + from_be_bytes(a: [u8; 16]); checked_add(a: u128, b: u128); // checked_add_signed(a: u128, b: i128); checked_sub(a: u128, b: u128); @@ -241,13 +242,13 @@ bench_against_primitive! { // reverse_bits(a: u128); // is_power_of_two(a: u128); - bitand(a: u128, b: u128); - bitor(a: u128, b: u128); - bitxor(a: u128, b: u128); - not(a: u128); + // bitand(a: u128, b: u128); + // bitor(a: u128, b: u128); + // bitxor(a: u128, b: u128); + // not(a: u128); - eq(a: ref &u128, b: ref &u128); - partial_cmp(a: ref &u128, b: ref &u128); + // eq(a: ref &u128, b: ref &u128); + // partial_cmp(a: ref &u128, b: ref &u128); } -criterion_main!(u512_benches); +criterion_main!(u128_benches); diff --git a/changes/prior-bugs.md b/changes/prior-bugs.md index ad1f55b..9f53bef 100644 --- a/changes/prior-bugs.md +++ b/changes/prior-bugs.md @@ -1,4 +1,5 @@ - `<= v0.1.0`: incorrect implementation of `from_be`, `to_be` on all integers. -- `<= v0.8.0`: `unchecked_shl`, `unchecked_shr` (only for `BUint` with `64` bit digits). +- `<= v0.8.0`: incorrect implementation of `unchecked_shl`, `unchecked_shr` for `BUint` with `64` bit digits. - `<= v0.8.0`: incorrect implementation of `Add<$Digit>` for all `BUint`s (where `$Digit` is the underlying digit type). -- `<= v0.8.0`: `lcm` incorrectly panics when `self` is zero. \ No newline at end of file +- `<= v0.8.0`: `lcm` incorrectly panics when `self` is zero. +- `v0.7.0 - v0.9.1`: the implementations of `shr, shl, wrapping_{shr, shl}, overflowing_{shr, shl}, checked_{shr, shl}, rotate_left, rotate_right, from_be_slice, from_le_slice` methods and any methods that involved left-shift or right-shift operations, contained undefined behaviour (this was due to a premature stabilisation of a feature in the Rust compiler - see and ). Note that all tests for these methods were still passing for each relevant version, so it is very likely that the code was still working correctly. However, as of the time of writing, the code that they relied on will soon be rejected by the compiler. They have now been replaced with safe implementations which are just as fast. \ No newline at end of file diff --git a/src/bint/endian.rs b/src/bint/endian.rs index e8b63bc..a1807f2 100644 --- a/src/bint/endian.rs +++ b/src/bint/endian.rs @@ -77,21 +77,16 @@ macro_rules! endian { } else { [0; N] }; - let slice_ptr = slice.as_ptr(); let mut i = 0; let exact = len >> digit::$Digit::BYTE_SHIFT; while i < exact { - let uninit = MaybeUninit::<[u8; digit::$Digit::BYTES as usize]>::uninit(); - let ptr = uninit.as_ptr().cast_mut() as *mut u8; - let digit_bytes = unsafe { - slice_ptr - .add( - len - digit::$Digit::BYTES as usize - - (i << digit::$Digit::BYTE_SHIFT), - ) - .copy_to_nonoverlapping(ptr, digit::$Digit::BYTES as usize); - uninit.assume_init() - }; + let mut digit_bytes = [0u8; digit::$Digit::BYTES as usize]; + let init_index = len - digit::$Digit::BYTES as usize; + let mut j = init_index; + while j < slice.len() { + digit_bytes[j - init_index] = slice[j - (i << digit::$Digit::BYTE_SHIFT)]; + j += 1; + } let digit = $Digit::from_be_bytes(digit_bytes); set_digit!(out_digits, i, digit, is_negative, sign_bits); i += 1; @@ -140,14 +135,14 @@ macro_rules! endian { let mut i = 0; let exact = len >> digit::$Digit::BYTE_SHIFT; while i < exact { - let uninit = MaybeUninit::<[u8; digit::$Digit::BYTES as usize]>::uninit(); - let ptr = uninit.as_ptr().cast_mut() as *mut u8; // TODO: can change to as_mut_ptr() when const_mut_refs is stabilised - let digit_bytes = unsafe { - slice_ptr - .add(i << digit::$Digit::BYTE_SHIFT) - .copy_to_nonoverlapping(ptr, digit::$Digit::BYTES as usize); - uninit.assume_init() - }; + let mut digit_bytes = [0u8; digit::$Digit::BYTES as usize]; + let init_index = i << digit::$Digit::BYTE_SHIFT; + let mut j = init_index; + while j < init_index + digit::$Digit::BYTES as usize { + digit_bytes[j - init_index] = slice[j]; + j += 1; + } + let digit = $Digit::from_le_bytes(digit_bytes); set_digit!(out_digits, i, digit, is_negative, sign_bits); i += 1; diff --git a/src/buint/cast.rs b/src/buint/cast.rs index 340c2bd..d9d7f47 100644 --- a/src/buint/cast.rs +++ b/src/buint/cast.rs @@ -132,26 +132,26 @@ macro_rules! cast { crate::nightly::const_fn! { #[inline] const fn cast_up(self, digit: $Digit) -> $BUint { - let digits = [digit; M]; - let digits_ptr = digits.as_ptr().cast_mut() as *mut $Digit; // TODO: can change to as_mut_ptr() when const_mut_refs is stabilised - let self_ptr = self.digits.as_ptr(); - unsafe { - self_ptr.copy_to_nonoverlapping(digits_ptr, N); - $BUint::from_digits(digits) + let mut digits = [digit; M]; + let mut i = M - N; + while i < M { + let index = i - (M - N); + digits[index] = self.digits[index]; + i += 1; } + $BUint::from_digits(digits) } } crate::nightly::const_fn! { #[inline] const fn cast_down(self) -> $BUint { - let digits = MaybeUninit::<[$Digit; M]>::uninit(); - let digits_ptr = digits.as_ptr().cast_mut() as *mut $Digit; // TODO: can change to as_mut_ptr() when const_mut_refs is stabilised - let self_ptr = self.digits.as_ptr(); - - unsafe { - self_ptr.copy_to_nonoverlapping(digits_ptr, M); - $BUint::from_digits(digits.assume_init()) + let mut out = $BUint::ZERO; + let mut i = 0; + while i < M { + out.digits[i] = self.digits[i]; + i += 1; } + out } } } diff --git a/src/buint/endian.rs b/src/buint/endian.rs index 0d57dc9..732ebff 100644 --- a/src/buint/endian.rs +++ b/src/buint/endian.rs @@ -70,17 +70,13 @@ macro_rules! endian { let mut i = 0; let exact = len >> digit::$Digit::BYTE_SHIFT; while i < exact { - let uninit = MaybeUninit::<[u8; digit::$Digit::BYTES as usize]>::uninit(); - let ptr = uninit.as_ptr().cast_mut() as *mut u8; // TODO: can change to as_mut_ptr() when const_mut_refs is stabilised - let digit_bytes = unsafe { - slice_ptr - .add( - len - digit::$Digit::BYTES as usize - - (i << digit::$Digit::BYTE_SHIFT), - ) - .copy_to_nonoverlapping(ptr, digit::$Digit::BYTES as usize); - uninit.assume_init() - }; + let mut digit_bytes = [0u8; digit::$Digit::BYTES as usize]; + let init_index = len - digit::$Digit::BYTES as usize; + let mut j = init_index; + while j < slice.len() { + digit_bytes[j - init_index] = slice[j - (i << digit::$Digit::BYTE_SHIFT)]; + j += 1; + } let digit = $Digit::from_be_bytes(digit_bytes); if i < N { out.digits[i] = digit; @@ -139,14 +135,13 @@ macro_rules! endian { let mut i = 0; let exact = len >> digit::$Digit::BYTE_SHIFT; while i < exact { - let uninit = MaybeUninit::<[u8; digit::$Digit::BYTES as usize]>::uninit(); - let ptr = uninit.as_ptr().cast_mut() as *mut u8; - let digit_bytes = unsafe { - slice_ptr - .add(i << digit::$Digit::BYTE_SHIFT) - .copy_to_nonoverlapping(ptr, digit::$Digit::BYTES as usize); - uninit.assume_init() - }; + let mut digit_bytes = [0u8; digit::$Digit::BYTES as usize]; + let init_index = i << digit::$Digit::BYTE_SHIFT; + let mut j = init_index; + while j < init_index + digit::$Digit::BYTES as usize { + digit_bytes[j - init_index] = slice[j]; + j += 1; + } let digit = $Digit::from_le_bytes(digit_bytes); if i < N { out.digits[i] = digit; @@ -241,14 +236,13 @@ macro_rules! endian { let arr_ptr = bytes.as_ptr(); let mut i = 0; while i < N { - let uninit = MaybeUninit::<[u8; digit::$Digit::BYTES as usize]>::uninit(); - let ptr = uninit.as_ptr().cast_mut() as *mut u8; // TODO: can change to as_mut_ptr() when const_mut_refs is stabilised - let digit_bytes = unsafe { - arr_ptr - .add((N - 1 - i) << digit::$Digit::BYTE_SHIFT) - .copy_to_nonoverlapping(ptr, digit::$Digit::BYTES as usize); - uninit.assume_init() - }; + let mut digit_bytes = [0u8; digit::$Digit::BYTES as usize]; + let init_index = N * digit::$Digit::BYTES as usize - digit::$Digit::BYTES as usize; + let mut j = init_index; + while j < N * digit::$Digit::BYTES as usize { + digit_bytes[j - init_index] = bytes[j - (i << digit::$Digit::BYTE_SHIFT)]; + j += 1; + } out.digits[i] = $Digit::from_be_bytes(digit_bytes); i += 1; } @@ -265,14 +259,13 @@ macro_rules! endian { let arr_ptr = bytes.as_ptr(); let mut i = 0; while i < N { - let uninit = MaybeUninit::<[u8; digit::$Digit::BYTES as usize]>::uninit(); - let ptr = uninit.as_ptr().cast_mut() as *mut u8; // TODO: can change to as_mut_ptr() when const_mut_refs is stabilised - let digit_bytes = unsafe { - arr_ptr - .add(i << digit::$Digit::BYTE_SHIFT) - .copy_to_nonoverlapping(ptr, digit::$Digit::BYTES as usize); - uninit.assume_init() - }; + let mut digit_bytes = [0u8; digit::$Digit::BYTES as usize]; + let init_index = i << digit::$Digit::BYTE_SHIFT; + let mut j = init_index; + while j < init_index + digit::$Digit::BYTES as usize { + digit_bytes[j - init_index] = bytes[j]; + j += 1; + } out.digits[i] = $Digit::from_le_bytes(digit_bytes); i += 1; } diff --git a/src/buint/mod.rs b/src/buint/mod.rs index 189bcb5..513d3c1 100644 --- a/src/buint/mod.rs +++ b/src/buint/mod.rs @@ -138,13 +138,20 @@ macro_rules! mod_impl { #[inline] const unsafe fn rotate_digits_left(self, n: usize) -> Self { - let uninit = MaybeUninit::<[$Digit; N]>::uninit(); - let digits_ptr = self.digits.as_ptr(); - let uninit_ptr = uninit.as_ptr().cast_mut() as *mut $Digit; // TODO: can change to as_mut_ptr() when const_mut_refs is stabilised + let mut out = Self::ZERO; + let mut i = n; + while i < N { + out.digits[i] = self.digits[i - n]; + i += 1; + } + let init_index = N - n; + let mut i = init_index; + while i < N { + out.digits[i - init_index] = self.digits[i]; + i += 1; + } - digits_ptr.copy_to_nonoverlapping(uninit_ptr.add(n), N - n); - digits_ptr.add(N - n).copy_to_nonoverlapping(uninit_ptr, n); - Self::from_digits(uninit.assume_init()) + out } #[inline] @@ -372,33 +379,37 @@ macro_rules! mod_impl { impl $BUint { #[inline] - pub(crate) const unsafe fn unchecked_shl_internal(self, rhs: ExpType) -> $BUint { + pub(crate) const unsafe fn unchecked_shl_internal(self, rhs: ExpType) -> Self { let mut out = $BUint::ZERO; let digit_shift = (rhs >> digit::$Digit::BIT_SHIFT) as usize; let bit_shift = rhs & digit::$Digit::BITS_MINUS_1; let num_copies = N.saturating_sub(digit_shift); // TODO: use unchecked_ methods from primitives when these are stablised and constified - self.digits.as_ptr().copy_to_nonoverlapping(out.digits.as_ptr().cast_mut().add(digit_shift), num_copies); // TODO: can change to out.digits.as_mut_ptr() when const_mut_refs is stabilised - if bit_shift != 0 { let carry_shift = digit::$Digit::BITS - bit_shift; let mut carry = 0; let mut i = digit_shift; while i < N { - let current_digit = out.digits[i]; + let current_digit = self.digits[i - digit_shift]; out.digits[i] = (current_digit << bit_shift) | carry; carry = current_digit >> carry_shift; i += 1; } + } else { + let mut i = digit_shift; + while i < N { // we start i at digit_shift, not 0, since the compiler can elide bounds checks when i < N + out.digits[i] = self.digits[i - digit_shift]; + i += 1; + } } out } #[inline] - pub(crate) const unsafe fn unchecked_shr_pad_internal(u: $BUint, rhs: ExpType) -> $BUint { + pub(crate) const unsafe fn unchecked_shr_pad_internal(self, rhs: ExpType) -> Self { let mut out = if NEG { $BUint::MAX } else { @@ -409,8 +420,6 @@ macro_rules! mod_impl { let num_copies = N.saturating_sub(digit_shift); // TODO: use unchecked_ methods from primitives when these are stablised and constified - u.digits.as_ptr().add(digit_shift).copy_to_nonoverlapping(out.digits.as_ptr().cast_mut(), num_copies); // TODO: can change to out.digits.as_mut_ptr() when const_mut_refs is stabilised - if bit_shift != 0 { let carry_shift = digit::$Digit::BITS - bit_shift; let mut carry = 0; @@ -418,7 +427,7 @@ macro_rules! mod_impl { let mut i = digit_shift; while i < N { // we use an increment while loop because the compiler can elide the array bounds check, which results in big performance gains let index = N - 1 - i; - let current_digit = out.digits[index]; + let current_digit = self.digits[index + digit_shift]; out.digits[index] = (current_digit >> bit_shift) | carry; carry = current_digit << carry_shift; i += 1; @@ -427,6 +436,12 @@ macro_rules! mod_impl { if NEG { out.digits[num_copies - 1] |= $Digit::MAX << carry_shift; } + } else { + let mut i = digit_shift; + while i < N { // we start i at digit_shift, not 0, since the compiler can elide bounds checks when i < N + out.digits[i - digit_shift] = self.digits[i]; + i += 1; + } } out diff --git a/src/lib.rs b/src/lib.rs index 241f2cc..3e48a9b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,6 +16,7 @@ wrapping_next_power_of_two, float_next_up_down, unchecked_math, + unchecked_shifts, ) )] #![doc = include_str!("../README.md")]