Skip to content

Commit

Permalink
fix float add algorithm, tidy up a lot of code
Browse files Browse the repository at this point in the history
  • Loading branch information
isaacholt100 committed Dec 31, 2024
1 parent 159f7ad commit 5f2826b
Show file tree
Hide file tree
Showing 34 changed files with 1,022 additions and 1,039 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,4 @@ opt-level = 3 # maximum optimisation level for faster runtime, but slower compil
all-features = true

[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(test_int_bits, values("64", "128"))'] }
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(test_int_bits, values("8", "16", "32", "64", "128"))'] }
21 changes: 15 additions & 6 deletions TODO.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
Floats:
* Conversions from and to:
* primitive floats
* bnum floats
* FromStr trait: REMEMBER: the num_traits crate has a general from_str_radix method for floats, could use if stuck
* Display, debug, upper exp, lower exp traits
* Transcendental functions:
Expand Down Expand Up @@ -66,13 +63,16 @@ Floats:
* FloatToInt trait
* From/TryFrom trait for ints, other floats
* Float type aliases from IEEE standard: f16, f32, f64, f80, f128. (Include f32 and f64 as allows const methods which aren't available on the primitives)
* Serde
* Rand:
* gen_range stuff
* num_traits::{Bounded, Float, FloatConst, FloatCore, AsPrimitive, FromPrimitive, ToPrimitive, ConstZero, ConstOne, One, Zero, FromBytes, ToBytes, Inv, MulAdd, MulAddAssign, Pow, Signed, Euclid, Num}
* num_traits::{Bounded, Float, FloatConst, FloatCore, AsPrimitive, FromPrimitive, ToPrimitive, FromBytes, ToBytes, Inv, MulAdd, MulAddAssign, Pow, Signed, Euclid, Num}
* Division algorithm which doesn't need where clause

Ints:
* big idea: could only use u8 digits, but for calculations (if this is faster than just using BUintD8 and isn't much slower than using larger digits), use u64s, e.g. when iterating, iterate in batches of 8, use u64::from_ne_bytes/u64::from_le_bytes - this is a transmute so should be very small overhead (this might sacrifice some code readability)
* unsigned_signed_diff methods
* isqrt methods
* unbounded shr, shl methods
* Use new Rust const capabilities to write more idiomatic code (e.g. we don't need the option_expect! macro anymore). Note though that maybe we should wait a bit to keep the MSRV not too recent
* Faster mulitplication algorithm for larger integers
* Faster division algorithms for larger integers
Expand All @@ -83,4 +83,13 @@ Other stuff:
* Think about removing BTryFrom and just implementing TryFrom (no From for now), then can use CastFrom/As trait for Result-less conversions
* Replace bitors, bitands, shifts, masks etc. with more efficient implementations (e.g. using set_bit, flip_bit, one-less-than-power-of-two methods, methods for efficiently generating masks/getting certain range of bits of integer)
* Consider putting floats and signed integers behind optional features (which are enabled by default)
* Add 16 bit and 32 bit width types to the test widths, so test u16, u32, f16, f32 as well (just make the digit sizes that are too wide not do anything for those tests)
* Add 16 bit and 32 bit width types to the test widths, so test u16, u32, f16, f32 as well (just make the digit sizes that are too wide not do anything for those tests)
* Consider removing implementation of AsPrimitive<BUint> for primitive ints
* Consider removing Add<Digit> and Div<Digit> impls
* Rewrite README
* consider removing parse_str_radix method, not really necessary now Option::expect and unwrap are const
* consider raising issue in num_traits crate about PrimInt dependency on NumCast
* consider using Rust's ParseIntError and TryFromIntError instead of own types, would have to do this by deliberating doing a calculation resulting in error (e.g. u8::from_str_radix(" ", 10)). this might be not be very good practice though, and would have to do for ParseFloatError eventually as well, which could be trickier to do this way
* consider splitting off allow-based methods into gated "alloc" feature
* work out and add assertions about sizes of e.g. int widths (should be <= u32::MAX), and float mantissa and exponent widths, etc.
* include list of difference with primitives in README, e.g. overflow_checks not detected yet, serde implementation different, memory layout different (always little endian - although maybe this could be changed? probably not a good idea though)
49 changes: 40 additions & 9 deletions bench/benches/float.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,24 @@ const SAMPLE_SIZE: usize = 10000;

use bnum::Float;

type F64 = Float<8, 52>;
type F256 = Float<32, 236>;
type F128 = Float<32, 236>;
type F64 = Float<8, 48>;
type F32 = Float<4, 23>;

type U256 = bnum::BUintD8<32>;
type U64 = bnum::BUintD8<8>;


fn bench_fibs(c: &mut Criterion) {
let mut group = c.benchmark_group("round");
let mut rng = rand::rngs::StdRng::seed_from_u64(0);
let inputs = (0..SAMPLE_SIZE)
.map(|_| rng.gen::<f64>())
.map(|a| a.to_bits())
.map(|a| (f64::from_bits(a), F64::from_bits(a.into())));
.map(|_| rng.gen::<(u64, u64)>())
.map(|(a, b)| (
(f64::from_bits(a >> 1), f64::from_bits(b >> 1)),
(F64::from_bits((a >> 1).into()), F64::from_bits((b >> 1).into()))
));
let (prim_inputs, big_inputs) = unzip::unzip2(inputs);

// group.bench_with_input(BenchmarkId::new("Recursive", "new"), &big_inputs, |b, inputs| b.iter(|| {
Expand All @@ -26,17 +33,41 @@ fn bench_fibs(c: &mut Criterion) {
// }
// }));
group.bench_with_input(BenchmarkId::new("Iterative", "old"), &big_inputs, |b, inputs| b.iter(|| {
inputs.iter().cloned().for_each(|a| {
let _ = black_box(a).trunc();
inputs.iter().cloned().for_each(|(a, b)| {
let _ = black_box(a) + black_box(b);
})
}));
group.bench_with_input(BenchmarkId::new("Iterative", "prim"), &prim_inputs, |b, inputs| b.iter(|| {
inputs.iter().cloned().for_each(|a| {
let _ = black_box(a).trunc();
inputs.iter().cloned().for_each(|(a, b)| {
let _ = black_box(a) + black_box(b);
})
}));
group.finish();
}


fn bench_add(c: &mut Criterion) {
let mut group = c.benchmark_group("round");
let mut rng = rand::rngs::StdRng::seed_from_u64(0);
let big_inputs = (0..SAMPLE_SIZE)
.map(|_| rng.gen::<(U64, U64)>())
.map(|(a, b)| (
(F64::from_bits((a >> 1)), F64::from_bits((b >> 1)))
));
let big_inputs: Vec<_> = big_inputs.collect();

// group.bench_with_input(BenchmarkId::new("Recursive", "new"), &big_inputs, |b, inputs| b.iter(|| {
// for a in inputs.iter().cloned() {
// let _ = black_box(a).floor();
// }
// }));
group.bench_with_input(BenchmarkId::new("Iterative", "old"), &big_inputs, |b, inputs| b.iter(|| {
inputs.iter().cloned().for_each(|(a, b)| {
let _ = black_box(black_box(a) + black_box(b));
})
}));
group.finish();
}

criterion_group!(benches, bench_fibs);
criterion_group!(benches, bench_add);
criterion_main!(benches);
2 changes: 1 addition & 1 deletion scripts/run_all_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ run_test () {
for flags in "" "--all-features"
do
echo "\n${CYAN_COLOR}info${RESET_FORMAT}: running tests with flags '$flags'..."
for bits in 64 128
for bits in 8 16 32 64 128
do
run_test $bits $flags
done
Expand Down
147 changes: 68 additions & 79 deletions src/bint/cast.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,20 @@
macro_rules! bint_as {
($BInt: ident, $Digit: ident; $($int: ty), *) => {
$(
impl_const! {
impl<const N: usize> const CastFrom<$BInt<N>> for $int {
#[inline]
fn cast_from(from: $BInt<N>) -> Self {
if from.is_negative() {
let digits = from.bits.digits;
let mut out = !0;
let mut i = 0;
while i << digit::$Digit::BIT_SHIFT < <$int>::BITS as usize && i < N {
out &= !((!digits[i]) as $int << (i << digit::$Digit::BIT_SHIFT));
i += 1;
}
out
} else {
<$int>::cast_from(from.bits)
impl<const N: usize> CastFrom<$BInt<N>> for $int {
#[inline]
fn cast_from(from: $BInt<N>) -> Self {
if from.is_negative() {
let digits = from.bits.digits;
let mut out = !0;
let mut i = 0;
while i << digit::$Digit::BIT_SHIFT < <$int>::BITS as usize && i < N {
out &= !((!digits[i]) as $int << (i << digit::$Digit::BIT_SHIFT));
i += 1;
}
out
} else {
<$int>::cast_from(from.bits)
}
}
}
Expand All @@ -26,14 +24,14 @@ macro_rules! bint_as {

macro_rules! as_bint {
($BInt: ident, $BUint: ident; $($ty: ty), *) => {
$(impl_const! {
impl<const N: usize> const CastFrom<$ty> for $BInt<N> {
$(
impl<const N: usize> CastFrom<$ty> for $BInt<N> {
#[inline]
fn cast_from(from: $ty) -> Self {
Self::from_bits($BUint::cast_from(from))
}
}
})*
)*
}
}

Expand Down Expand Up @@ -65,7 +63,6 @@ pub(crate) use bint_cast_from_float;

use crate::cast::CastFrom;
use crate::digit;
use crate::nightly::impl_const;

macro_rules! cast {
($BUint: ident, $BInt: ident, $Digit: ident) => {
Expand Down Expand Up @@ -97,21 +94,17 @@ macro_rules! cast {

as_bint!($BInt, $BUint; u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize, bool, char);

impl_const! {
impl<const N: usize, const M: usize> const CastFrom<$BUint<M>> for $BInt<N> {
#[inline]
fn cast_from(from: $BUint<M>) -> Self {
Self::from_bits($BUint::cast_from(from))
}
impl<const N: usize, const M: usize> CastFrom<$BUint<M>> for $BInt<N> {
#[inline]
fn cast_from(from: $BUint<M>) -> Self {
Self::from_bits($BUint::cast_from(from))
}
}

impl_const! {
impl<const N: usize, const M: usize> const CastFrom<$BInt<M>> for $BInt<N> {
#[inline]
fn cast_from(from: $BInt<M>) -> Self {
Self::from_bits($BUint::cast_from(from))
}
impl<const N: usize, const M: usize> CastFrom<$BInt<M>> for $BInt<N> {
#[inline]
fn cast_from(from: $BInt<M>) -> Self {
Self::from_bits($BUint::cast_from(from))
}
}

Expand All @@ -138,62 +131,58 @@ crate::macro_impl!(cast);
macro_rules! bint_as_different_digit_bigint {
($BUint: ident, $BInt: ident, $Digit: ident; $(($OtherBInt: ident, $OtherDigit: ident)), *) => {
$(
crate::nightly::const_impl! {
impl<const N: usize, const M: usize> const crate::cast::CastFrom<$OtherBInt<M>> for $BUint<N> {
#[must_use = doc::must_use_op!()]
#[inline]
fn cast_from(from: $OtherBInt<M>) -> Self {
if !from.is_negative() || M * $OtherDigit::BITS as usize >= N * $Digit::BITS as usize { // $OtherBInt::BITS <= $Int::BITS
Self::cast_from(from.to_bits())
impl<const N: usize, const M: usize> crate::cast::CastFrom<$OtherBInt<M>> for $BUint<N> {
#[must_use = doc::must_use_op!()]
#[inline]
fn cast_from(from: $OtherBInt<M>) -> Self {
if !from.is_negative() || M * $OtherDigit::BITS as usize >= N * $Digit::BITS as usize { // $OtherBInt::BITS <= $Int::BITS
Self::cast_from(from.to_bits())
} else {
let mut out = Self::MAX;
if $Digit::BITS < $OtherDigit::BITS {
const DIVIDE_COUNT: usize = ($OtherDigit::BITS / $Digit::BITS) as usize;
let stop_index: usize = if <$OtherBInt<M>>::BITS > <$BUint<N>>::BITS {
N
} else {
M * DIVIDE_COUNT
};
let mut i = 0;
while i < stop_index {
let wider_digit = from.bits.digits[i / DIVIDE_COUNT];
let mini_shift = i % DIVIDE_COUNT;
let digit = (wider_digit >> (mini_shift << digit::$Digit::BIT_SHIFT)) as $Digit;
out.digits[i] = digit;
i += 1;
}
} else {
let mut out = Self::MAX;
if $Digit::BITS < $OtherDigit::BITS {
const DIVIDE_COUNT: usize = ($OtherDigit::BITS / $Digit::BITS) as usize;
let stop_index: usize = if <$OtherBInt<M>>::BITS > <$BUint<N>>::BITS {
N
} else {
M * DIVIDE_COUNT
};
let mut i = 0;
while i < stop_index {
let wider_digit = from.bits.digits[i / DIVIDE_COUNT];
let mini_shift = i % DIVIDE_COUNT;
let digit = (wider_digit >> (mini_shift << digit::$Digit::BIT_SHIFT)) as $Digit;
out.digits[i] = digit;
i += 1;
}
const DIVIDE_COUNT: usize = ($Digit::BITS / $OtherDigit::BITS) as usize;
let stop_index: usize = if <$OtherBInt<M>>::BITS > <$BUint<N>>::BITS {
N * DIVIDE_COUNT
} else {
const DIVIDE_COUNT: usize = ($Digit::BITS / $OtherDigit::BITS) as usize;
let stop_index: usize = if <$OtherBInt<M>>::BITS > <$BUint<N>>::BITS {
N * DIVIDE_COUNT
} else {
M
};
let mut current_digit: $Digit = $Digit::MAX;
let mut i = 0;
while i < stop_index {
let mini_shift = i % DIVIDE_COUNT;
current_digit &= !((!from.bits.digits[i] as $Digit) << (mini_shift << digit::$OtherDigit::BIT_SHIFT));
if mini_shift == DIVIDE_COUNT - 1 || i == stop_index - 1 {
out.digits[i / DIVIDE_COUNT] = current_digit;
current_digit = $Digit::MAX;
}
i += 1;
M
};
let mut current_digit: $Digit = $Digit::MAX;
let mut i = 0;
while i < stop_index {
let mini_shift = i % DIVIDE_COUNT;
current_digit &= !((!from.bits.digits[i] as $Digit) << (mini_shift << digit::$OtherDigit::BIT_SHIFT));
if mini_shift == DIVIDE_COUNT - 1 || i == stop_index - 1 {
out.digits[i / DIVIDE_COUNT] = current_digit;
current_digit = $Digit::MAX;
}
i += 1;
}
out
}
out
}
}
}

crate::nightly::const_impl! {
impl<const N: usize, const M: usize> const crate::cast::CastFrom<$OtherBInt<M>> for $BInt<N> {
#[must_use = doc::must_use_op!()]
#[inline]
fn cast_from(from: $OtherBInt<M>) -> Self {
Self::from_bits($BUint::<N>::cast_from(from))
}

impl<const N: usize, const M: usize> crate::cast::CastFrom<$OtherBInt<M>> for $BInt<N> {
#[must_use = doc::must_use_op!()]
#[inline]
fn cast_from(from: $OtherBInt<M>) -> Self {
Self::from_bits($BUint::<N>::cast_from(from))
}
}
)*
Expand Down
2 changes: 1 addition & 1 deletion src/bint/checked.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ macro_rules! checked_ilog {
}

use crate::doc;
use crate::int::checked::tuple_to_option;
use crate::helpers::tuple_to_option;
use crate::ExpType;

macro_rules! checked {
Expand Down
Loading

0 comments on commit 5f2826b

Please sign in to comment.