Skip to content

Commit

Permalink
fix midpoint method, get things ready for v0.12.1
Browse files Browse the repository at this point in the history
  • Loading branch information
isaacholt100 committed Jan 1, 2025
1 parent 5f2826b commit 60a4eda
Show file tree
Hide file tree
Showing 21 changed files with 137 additions and 139 deletions.
8 changes: 6 additions & 2 deletions TODO.txt
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ Floats:
* 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)
* big idea: could only use u8 digits, but for calculations (would need to do this as BUintD8 is noticeably slower than BUint), use u64s or u128s, 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
Expand All @@ -78,6 +78,8 @@ Ints:
* Faster division algorithms for larger integers
* Update serde to use decimal string instead of struct debug - but CHECK that all serde options serialise primitive ints as decimal strings
* FromBytes and ToBytes num_traits traits - only when can implement without "unconstrained generic constant" error
* do we need the from_be_slice and from_le_slice methods? (think we can just call from_radix_{b, l}e with radix 256)
* create more efficient implementation of ilog10 (see e.g. Hacker's Delight book)

Other stuff:
* Think about removing BTryFrom and just implementing TryFrom (no From for now), then can use CastFrom/As trait for Result-less conversions
Expand All @@ -92,4 +94,6 @@ Other stuff:
* 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)
* 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)
* test using stable, only use nightly when need to test be_bytes methods
* check you're happy with the layout of the random crate-level module
4 changes: 2 additions & 2 deletions bench/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ license = "MIT OR Apache-2.0"

[dev-dependencies]
criterion = { version = "0.5" }
bnum = { path = "../", features = ["rand", "nightly", "float"] }
bnum = { path = "../", features = ["rand", "float"] }
num-traits = "0.2"
uint = "0.9"
rand = { version = "0.8", features = ["std", "std_rng"] }
Expand All @@ -19,7 +19,7 @@ name = "float"
harness = false

[[bench]]
name = "benchmark"
name = "uint"
harness = false

[profile.release]
Expand Down
12 changes: 6 additions & 6 deletions bench/benches/float.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ type F64 = Float<8, 48>;
type F32 = Float<4, 23>;

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


fn bench_fibs(c: &mut Criterion) {
Expand Down Expand Up @@ -50,10 +50,10 @@ 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)))
));
.map(|_| rng.gen::<(U1024, u32)>());
// .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(|| {
Expand All @@ -63,7 +63,7 @@ fn bench_add(c: &mut Criterion) {
// }));
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));
let _ = black_box(black_box(a).overflowing_shl(black_box(b)));
})
}));
group.finish();
Expand Down
20 changes: 11 additions & 9 deletions bench/benches/benchmark.rs → bench/benches/uint.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#![feature(wrapping_next_power_of_two, int_roundings)]
// #![feature(wrapping_next_power_of_two, int_roundings)]

use bnum::types::{U128, U512};
// use bnum::types::{U128, U512};
// use bnum::prelude::*;
use core::iter::Iterator;
use criterion::black_box;
Expand All @@ -9,6 +9,8 @@ use rand::prelude::*;
mod unzip;
use unzip::unzip2;

type U128 = bnum::BUintD8::<16>;

// use super::unzip2;
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};

Expand Down Expand Up @@ -139,12 +141,12 @@ bench_against_primitive! {
from_le(a: u128);
to_be(a: u128);
to_le(a: u128);
to_be_bytes(a: u128);
to_le_bytes(a: u128);
to_ne_bytes(a: u128);
from_be_bytes(a: [u8; 128 / 8]);
from_le_bytes(a: [u8; 128 / 8]);
from_ne_bytes(a: [u8; 128 / 8]);
// to_be_bytes(a: u128);
// to_le_bytes(a: u128);
// to_ne_bytes(a: u128);
// from_be_bytes(a: [u8; 128 / 8]);
// from_le_bytes(a: [u8; 128 / 8]);
// from_ne_bytes(a: [u8; 128 / 8]);

overflowing_add(a: u128, b: u128);
overflowing_add_signed(a: u128, b: i128);
Expand Down Expand Up @@ -178,7 +180,7 @@ bench_against_primitive! {
wrapping_shl(a: u128, rhs: u32);
wrapping_shr(a: u128, rhs: u32);
wrapping_pow(a: u128, exp: u32);
wrapping_next_power_of_two(a: u128);
// wrapping_next_power_of_two(a: u128);

count_ones(a: u128);
count_zeros(a: u128);
Expand Down
1 change: 0 additions & 1 deletion changes/v0.13.0
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
Add ConstZero and ConstOne (from num_traits) for ints
(maybe?) implementation of int cast to float was incorrect, now fixed
12 changes: 6 additions & 6 deletions src/bint/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -238,13 +238,13 @@ macro_rules! mod_impl {
#[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
// see section 2.5: Average of Two Integers in Hacker's Delight
let x = self.bitxor(rhs);
let t = self.bitand(rhs).add(x.shr(1));
if t.is_negative() && x.bits.digits[0] & 1 == 1 { // t is negative and
t.add($BInt::ONE)
} else {
// result is 2^(type bits - 1) too large, so subtract 2^(type bits - 1) by applying xor
m.bitxor(Self::MIN)
t
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/bint/numtraits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,9 +160,9 @@ use crate::ExpType;
use num_integer::{Integer, Roots};
use num_traits::{
AsPrimitive, Bounded, CheckedAdd, CheckedDiv, CheckedMul, CheckedNeg, CheckedRem, CheckedShl,
CheckedShr, CheckedSub, CheckedEuclid, Euclid, FromPrimitive, MulAdd, MulAddAssign, Num, One, ConstOne, Pow, PrimInt,
CheckedShr, CheckedSub, CheckedEuclid, Euclid, FromPrimitive, MulAdd, MulAddAssign, Num, One, /*ConstOne,*/ Pow, PrimInt,
Saturating, SaturatingAdd, SaturatingMul, SaturatingSub, Signed, ToPrimitive, WrappingAdd,
WrappingMul, WrappingNeg, WrappingShl, WrappingShr, WrappingSub, Zero, ConstZero
WrappingMul, WrappingNeg, WrappingShl, WrappingShr, WrappingSub, Zero, //ConstZero
};

use crate::cast::CastFrom;
Expand Down
11 changes: 6 additions & 5 deletions src/buint/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,8 @@ macro_rules! mod_impl {
#[must_use = doc::must_use_op!()]
#[inline]
pub const fn midpoint(self, rhs: Self) -> Self {
(self.bitxor(rhs).shr(1)).add(self.bitand(rhs))
// see section 2.5: Average of Two Integers in Hacker's Delight
self.bitand(rhs).add(self.bitxor(rhs).shr(1))
}

#[doc = doc::ilog2!(U)]
Expand Down Expand Up @@ -508,10 +509,10 @@ macro_rules! mod_impl {
out
}

#[inline(always)]
pub(crate) const fn digit(&self, index: usize) -> $Digit {
self.digits[index]
}
// #[inline(always)]
// pub(crate) const fn digit(&self, index: usize) -> $Digit {
// self.digits[index]
// }

/// Returns the digits stored in `self` as an array. Digits are little endian (least significant digit first).
#[must_use]
Expand Down
2 changes: 1 addition & 1 deletion src/buint/numtraits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ use crate::ExpType;
use num_integer::{Integer, Roots};
use num_traits::{
AsPrimitive, Bounded, CheckedAdd, CheckedDiv, CheckedMul, CheckedNeg, CheckedRem, CheckedShl,
CheckedShr, CheckedSub, CheckedEuclid, Euclid, FromPrimitive, MulAdd, MulAddAssign, Num, One, ConstOne, Pow, PrimInt,
CheckedShr, CheckedSub, CheckedEuclid, Euclid, FromPrimitive, MulAdd, MulAddAssign, Num, One, /*ConstOne,*/ Pow, PrimInt,
Saturating, SaturatingAdd, SaturatingMul, SaturatingSub, ToPrimitive, Unsigned, WrappingAdd,
WrappingMul, WrappingNeg, WrappingShl, WrappingShr, WrappingSub, Zero, ConstZero
};
Expand Down
1 change: 1 addition & 0 deletions src/buint/overflowing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ macro_rules! overflowing {
#[must_use = doc::must_use_op!()]
#[inline]
pub const fn overflowing_pow(mut self, mut pow: ExpType) -> (Self, bool) {
// exponentiation by squaring
if pow == 0 {
return (Self::ONE, false);
}
Expand Down
2 changes: 1 addition & 1 deletion src/cast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,4 +152,4 @@ primitive_cast_impl!(char as [u8, u16, u32, u64, u128, usize, i8, i16, i32, i64,
primitive_cast_impl!(u8 as [u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize, f32, f64, char]);
multiple_impls!(u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize, f32, f64);

pub mod float;
pub(crate) mod float;
33 changes: 17 additions & 16 deletions src/doc/consts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,19 +92,20 @@ macro_rules! value_desc {

pub(crate) use value_desc;

crate::doc::link_doc_comment_constant!(
RADIX,
MANTISSA_DIGITS,
DIGITS,
EPSILON,
MIN,
MIN_POSITIVE,
MAX,
MIN_EXP,
MAX_EXP,
// MIN_10_EXP,
// MAX_10_EXP,
NAN,
INFINITY,
NEG_INFINITY
);
// #[cfg(feature = "float")]
// crate::doc::link_doc_comment_constant!(
// RADIX,
// MANTISSA_DIGITS,
// DIGITS,
// EPSILON,
// MIN,
// MIN_POSITIVE,
// MAX,
// MIN_EXP,
// MAX_EXP,
// // MIN_10_EXP,
// // MAX_10_EXP,
// NAN,
// INFINITY,
// NEG_INFINITY
// );
12 changes: 8 additions & 4 deletions src/doc/mod.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
pub mod bigint_helpers;
pub mod checked;
pub mod classify;
pub mod cmp;
// #[cfg(feature = "float")]
// pub mod classify;
// #[cfg(feature = "float")]
// pub mod cmp;
pub mod const_trait_fillers;
pub mod consts;
pub mod endian;
pub mod math;
// #[cfg(feature = "float")]
// pub mod math;
pub mod overflowing;
pub mod radix;
pub mod rounding;
// #[cfg(feature = "float")]
// pub mod rounding;
pub mod saturating;
pub mod strict;
pub mod unchecked;
Expand Down
2 changes: 2 additions & 0 deletions src/float/math/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ impl<const W: usize, const MB: usize> Float<W, MB> {
self.sqrt_internal()
}

#[cfg(feature = "nightly")]
#[doc = doc::math::div_euclid!(F)]
#[must_use = doc::must_use_op!(float)]
#[inline]
Expand Down Expand Up @@ -61,6 +62,7 @@ impl<const W: usize, const MB: usize> Float<W, MB> {
}
}

#[cfg(feature = "nightly")]
#[doc = doc::math::powi!(F)]
#[must_use = doc::must_use_op!(float)]
#[inline]
Expand Down
43 changes: 16 additions & 27 deletions src/float/ops/add.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,36 +20,25 @@ impl<const W: usize, const MB: usize> Float<W, MB> {
let exp_diff = exp_diff as ExpType;
match b_mant.checked_shr(exp_diff) { // shift b_mant so it is aligned (in terms of exponents) with a_mant, so we can add them
Some(shifted) => {
if exp_diff == 0 {
let mut mant = a_mant + shifted; // result must have overflowed, since both shifted and a_mant have bit at index Self::MB set to 1
let round_up = mant.digits[0] & 0b11 == 0b11; // round by ties-to-even
mant = mant >> 1;
let mut mant = a_mant + shifted;
if mant.bit(Self::MB + 1) { // overflow occurred
let mut shifted_mant: BUintD8<W> = mant >> 1;
let gte_half = mant.is_odd(); // if discarded bits are at least a half
let round_up = gte_half && !(b_mant.trailing_zeros() >= exp_diff && shifted_mant.is_even()); // round by ties-to-even
if round_up {
mant += BUintD8::ONE; // note this cannot overflow now, since if there was round up, then the last bit of the sum is one, meaning that a_mant and shifted can't both be their maximum value (which would be required for overflow here)
shifted_mant += BUintD8::ONE;
}
(a_exp + 1, shifted_mant)
} else { // no overflow yet, but still need to check for overflow when performing ties-to-even rounding
let round_up = b_mant.bit(exp_diff - 1) && !(b_mant.trailing_zeros() >= exp_diff - 1 && mant.is_even()); // round according to ties-to-even. exp_diff - 1 will be non-negative, since if exp_diff = 0, then we would have had the overflow condition earlier
if round_up {
mant += BUintD8::ONE;
}
(a_exp + 1, mant)
} else {
let mut mant = a_mant + shifted;
let discarded_bits = b_mant & (BUintD8::MAX >> (BUintD8::<W>::BITS - exp_diff));
if mant.bit(Self::MB + 1) { // overflow occurred
let mut shifted_mant: BUintD8<W> = mant >> 1;
let gte_half = mant.is_odd(); // if discarded bits are at least a half
let round_up = gte_half && !(discarded_bits.is_zero() && shifted_mant.is_even()); // round by ties-to-even
if round_up {
shifted_mant += BUintD8::ONE;
}
(a_exp + 1, shifted_mant)
} else { // no overflow yet, but still need to check for overflow when performing ties-to-even rounding
let round_up = discarded_bits.bit(exp_diff - 1) && !(discarded_bits.is_power_of_two() && mant.is_even()); // round according to ties-to-even. exp_diff - 1 will be non-negative, since if exp_diff = 0, then we would have had the overflow condition earlier
if round_up {
mant += BUintD8::ONE;
}
if mant.bit(Self::MB + 1) { // overflow occurred
debug_assert!(mant.is_even()); // since overflow occurred and we added one, the result must be even
(a_exp + 1, mant >> 1) // don't need to worry about checking for round up here, as mantissa is even, so when right shifted by 1, the discarded bits will be less than half (i.e. no round up)
} else {
(a_exp, mant)
}
debug_assert!(mant.is_even()); // since overflow occurred and we added one, the result must be even
(a_exp + 1, mant >> 1) // don't need to worry about checking for round up here, as mantissa is even, so when right shifted by 1, the discarded bits will be less than half (i.e. no round up)
} else {
(a_exp, mant)
}
}
},
Expand Down
12 changes: 4 additions & 8 deletions src/float/ops/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use core::ops::{Add, Div, Mul, Neg, Rem, Sub, AddAssign, SubAssign, MulAssign, R
mod add;
mod sub;
mod mul;
#[cfg(feature = "nightly")]
mod div;
mod rem;

Expand Down Expand Up @@ -75,26 +76,21 @@ impl<const W: usize, const MB: usize> Mul for Float<W, MB> {

crate::int::ops::op_ref_impl!(Mul<Float<N, MB>> for Float<N, MB>, mul);

impl<const W: usize, const MB: usize> Product for Float<W, MB>
where
[(); W * 2]:,
{
impl<const W: usize, const MB: usize> Product for Float<W, MB> {
#[inline]
fn product<I: Iterator<Item = Self>>(iter: I) -> Self {
iter.fold(Self::ONE, |a, b| a * b)
}
}

impl<'a, const W: usize, const MB: usize> Product<&'a Self> for Float<W, MB>
where
[(); W * 2]:,
{
impl<'a, const W: usize, const MB: usize> Product<&'a Self> for Float<W, MB> {
#[inline]
fn product<I: Iterator<Item = &'a Self>>(iter: I) -> Self {
iter.fold(Self::ONE, |a, b| a * *b)
}
}

#[cfg(feature = "nightly")]
impl<const W: usize, const MB: usize> Div for Float<W, MB>
where
[(); W * 2]:,
Expand Down
Loading

0 comments on commit 60a4eda

Please sign in to comment.