FBig add/sub rounding fixes, BMI2 addmul kernels, rand 0.9/0.10 support - #81
Merged
Conversation
Estimate capacity from the number's length (logarithmic in the length ratio, since each successive power is built by repeated squaring) and use Vec::with_capacity instead of growing the vectors push-by-push. Co-Authored-By: Claude <noreply@anthropic.com>
The add_mul_dword_same_len_in_place and sub_mul_dword_same_len_in_place kernels (the inner loop of schoolbook multiplication, and the base case for Karatsuba/Toom-3) now dispatch at runtime to a BMI2 build on x86-64 when the CPU supports bmi2 (requires the std feature). The arithmetic is shared verbatim between the portable and BMI2 builds via an #[inline(always)] body, and the BMI2 wrapper is #[target_feature(enable = "bmi2")], so LLVM lowers the widening multiplies to the flag-free mulx and unrolls -- no hand-written intrinsics, so the two builds cannot diverge. ~4-5% faster on the kernel in isolation. When the crate is already built with bmi2 (e.g. -C target-cpu=native or x86-64-v3), the portable build itself uses mulx, so the runtime check is compiled out and the change is a no-op for such users -- the runtime dispatch only matters for portable baseline binaries. Also adds a guide page (guide/src/performance.md) recommending -C target-cpu=native for maximum performance, plus a parity test and an ignored micro-benchmark. Co-Authored-By: Claude <noreply@anthropic.com>
The forwarding macros already use the same 4-arg (sign, mag, sign, mag) $impl signature -- forward_ubig_ibig_binop_to_repr just passes lhs_sign = Positive -- so a single $impl definition is already shared across IBig op IBig, UBig op IBig, and IBig op UBig wherever the output type matches. The TODO is satisfied; drop it. Co-Authored-By: Claude <noreply@anthropic.com>
Context::add/sub misrounds when a subtraction's result lands just below a round number (opposite-sign summands, |large| - eps): the discarded low part can have the opposite sign of the significand, which the exponent-alignment + repr_round_sum logic doesn't handle, so the result can be off by up to 1 ulp (and Zero/directed modes can report an impossible rounding flag). No library behavior change in this commit; it only adds regression coverage: - fuzz/tests/add_random.rs: rand-driven differential test (all 6 rounding modes, bases 2 and 10) comparing Context::add/sub against an independent oracle (exact sum at unlimited precision, re-rounded via with_precision). - fuzz/Cargo.toml: add dashu-int dep and a [workspace] table so the excluded fuzz package builds standalone. - float/tests/add.rs: a focused, #[ignore] reproducer with 5 concrete failing cases for debugging. A correct fix was prototyped but regressed DBig add ~3x (it removed the negligible-small shortcut for subtractions and added an expensive borrow conversion); the fix is deferred and these tests are the regression net. Co-Authored-By: Claude <noreply@anthropic.com>
The `bench_dword_kernel_bmi2_vs_portable` test was a scratch micro-benchmark comparing portable vs BMI2 addmul-2 kernel throughput. The kernels it exercised are already covered by `dword_kernel_edge_cases` and the bmi2/portable parity tests. Co-Authored-By: Claude <noreply@anthropic.com>
* Add optional `rand_v09` (rand 0.9) and `rand_v010` (rand 0.10) features mirroring `rand_v08` across dashu-int/float/ratio (plus root forwarding). The default `rand` feature remains `rand_v08`. The v08 source files are renamed to `rand_v08.rs` for parallel naming with `rand_v09.rs`/`rand_v010.rs`, with a `rand` re-export alias preserving the existing `dashu::*::rand` paths. * Consolidate the standalone `add_sub_oracle` example into the fuzz differential test: draw random significands directly via `UniformBits` (no per-iteration string allocation/parsing), add a deterministic small-precision sweep, and use a guard-digit-tolerant oracle (accept the result matching rounding to p or p+1). * Fix `Context::sub(0, b)` mis-rounding under the asymmetric directed modes `Up` (toward +inf) and `Down` (toward -inf): the zero-left path rounded `b` and then negated (`-round(b)`), but `round(-x) != -round(x)` for those modes, so the result could land one ULP off. The negated operand is now rounded directly. Add a regression test; remove the obsolete strict- precision borrow-rounding known-failures test. Co-Authored-By: Claude <noreply@anthropic.com>
The rand distributions (UniformBits/Uniform01/UniformFBig/...) and their sampling algorithms now live once in a version-agnostic `rand` module per crate, generic over a `BitRng` trait (defined in dashu-int). The `rand_v08`/`rand_v09`/`rand_v010` modules are reduced to private per-version trait bindings. - dashu-int::rand exposes the `BitRng` trait plus `bridge_v08`/`bridge_v09`/ `bridge_v010` constructors, so float/ratio and end users can adapt any rand version's RNG (or implement `BitRng` directly). - Version modules are now private; the distributions are accessed via `dashu_*::rand`. The common module docs live in `rand.rs`, and a runnable rand-0.8 example lives in `float/examples/random_fbig.rs`. Co-Authored-By: Claude <noreply@anthropic.com>
The test module used `vec!`/`Vec` without imports (broke the no_std CI job) and hardcoded 64-bit `Word` (`u64`/`u128`/`>> 64`/`u64::MAX`) in the addmul-2 reference + tests (broke the 16/32-bit `force_bits` CI matrix). - Import `alloc::vec` / `alloc::vec::Vec` (matching other test modules), and gate the BMI2-only carry bindings so they aren't unused on non-x86_64 / no_std. - Make `add_mul_dword_ref` and the dword-kernel tests `Word`-generic: shift by `Word::BITS`, use `Word::MAX`, and cast the PRNG output `as Word`. Co-Authored-By: Claude <noreply@anthropic.com>
…6-bit Word `gen_ubig` took `num_words: u16` and pushed `i.into()`; on a 16-bit Word target (Word = u16) that is a useless u16 -> u16 conversion. Widen the parameter to `usize` and cast the loop index `as Word`, so the conversion is a genuine cross-type cast at every Word width (and `as Word` can't trip `unnecessary_cast`, since `usize` is never the same type as `Word`). Co-Authored-By: Claude <noreply@anthropic.com>
The section describing the precision of each distribution (Uniform01 / builtin Standard* / UniformFBig) was dropped during the rand-module de-dup. Add it back to the version-agnostic rand.rs core, keeping the [Uniform01]/[UniformFBig]/[DoubleWord]/[FBig] intra-doc links and rendering the version-specific `Standard`/`StandardUniform` and `Uniform` as plain text (the core is version-agnostic). Co-Authored-By: Claude <noreply@anthropic.com>
Remove `dword_kernel_edge_cases` (trivial all-ones/zero-multiplier cases already covered by the two fuzz tests), and replace the hand-rolled SplitMix64 PRNG (`next_rand`) with `rand_v08`'s `StdRng`, dropping the manual helper and the now-unused `alloc::vec` macro import. Co-Authored-By: Claude <noreply@anthropic.com>
Several rand `Distribution::sample` impls were missing `#[inline]` (`UniformBits`/`UniformBelow` for integers, `UniformFBig`/`Uniform01`/ `Standard` for floats, `Uniform01<Repr>` for rationals) while others (`Open01`, `OpenClosed01`, the rational builtins) already had it. Add it consistently across all three rand versions so the delegating `sample` methods inline into the caller. Co-Authored-By: Claude <noreply@anthropic.com>
The section describing the denominator of each distribution (Uniform01 / builtin Standard* / UniformRBig, plus the closed-interval off-by-one note) was dropped during the rand-module de-dup. Add it back to the version-agnostic rand.rs core, rendering the version-specific `Standard`/`StandardUniform` and `DoubleWord::MAX` as plain text. Co-Authored-By: Claude <noreply@anthropic.com>
rand 0.10 requires Rust 1.85 and its feature table (getrandom as an optional dep with the `dep:` syntax) breaks the resolver under the 1.68 build, even though only `rand` (== rand_v08) is enabled there. Extend `drop_incompatible_deps_for_msrv.py` to also remove `rand_v09`/`rand_v010` (their deps + feature lines) from the manifests for the MSRV build, so only rand 0.8 is resolved. rand_v09/v010 remain covered by the stable and 1.85 `--all-features` jobs. Co-Authored-By: Claude <noreply@anthropic.com>
CokieMiner
pushed a commit
to CokieMiner/dashu
that referenced
this pull request
Jun 25, 2026
…rt (cmpute#81) * Preallocate radix_powers/big_chunks in non-power-of-2 radix formatting Estimate capacity from the number's length (logarithmic in the length ratio, since each successive power is built by repeated squaring) and use Vec::with_capacity instead of growing the vectors push-by-push. Co-Authored-By: Claude <noreply@anthropic.com> * Use BMI2 mulx in the basecase addmul/submul-2 kernels The add_mul_dword_same_len_in_place and sub_mul_dword_same_len_in_place kernels (the inner loop of schoolbook multiplication, and the base case for Karatsuba/Toom-3) now dispatch at runtime to a BMI2 build on x86-64 when the CPU supports bmi2 (requires the std feature). The arithmetic is shared verbatim between the portable and BMI2 builds via an multiplies to the flag-free mulx and unrolls -- no hand-written intrinsics, so the two builds cannot diverge. ~4-5% faster on the kernel in isolation. When the crate is already built with bmi2 (e.g. -C target-cpu=native or x86-64-v3), the portable build itself uses mulx, so the runtime check is compiled out and the change is a no-op for such users -- the runtime dispatch only matters for portable baseline binaries. Also adds a guide page (guide/src/performance.md) recommending -C target-cpu=native for maximum performance, plus a parity test and an ignored micro-benchmark. Co-Authored-By: Claude <noreply@anthropic.com> * Remove stale TODO about sharing $impl between ibig/ubig binops The forwarding macros already use the same 4-arg (sign, mag, sign, mag) $impl signature -- forward_ubig_ibig_binop_to_repr just passes lhs_sign = Positive -- so a single $impl definition is already shared across IBig op IBig, UBig op IBig, and IBig op UBig wherever the output type matches. The TODO is satisfied; drop it. Co-Authored-By: Claude <noreply@anthropic.com> * Add tests documenting FBig add/sub borrow-rounding bug Context::add/sub misrounds when a subtraction's result lands just below a round number (opposite-sign summands, |large| - eps): the discarded low part can have the opposite sign of the significand, which the exponent-alignment + repr_round_sum logic doesn't handle, so the result can be off by up to 1 ulp (and Zero/directed modes can report an impossible rounding flag). No library behavior change in this commit; it only adds regression coverage: - fuzz/tests/add_random.rs: rand-driven differential test (all 6 rounding modes, bases 2 and 10) comparing Context::add/sub against an independent oracle (exact sum at unlimited precision, re-rounded via with_precision). - fuzz/Cargo.toml: add dashu-int dep and a [workspace] table so the excluded fuzz package builds standalone. - float/tests/add.rs: a focused, #[ignore] reproducer with 5 concrete failing cases for debugging. A correct fix was prototyped but regressed DBig add ~3x (it removed the negligible-small shortcut for subtractions and added an expensive borrow conversion); the fix is deferred and these tests are the regression net. Co-Authored-By: Claude <noreply@anthropic.com> * Fix cancellation by do full subtraction * Fix bugs * Remove unused BMI2 addmul-2 micro-benchmark The `bench_dword_kernel_bmi2_vs_portable` test was a scratch micro-benchmark comparing portable vs BMI2 addmul-2 kernel throughput. The kernels it exercised are already covered by `dword_kernel_edge_cases` and the bmi2/portable parity tests. Co-Authored-By: Claude <noreply@anthropic.com> * Add rand_v09/v010 support; consolidate add/sub differential test * Add optional `rand_v09` (rand 0.9) and `rand_v010` (rand 0.10) features mirroring `rand_v08` across dashu-int/float/ratio (plus root forwarding). The default `rand` feature remains `rand_v08`. The v08 source files are renamed to `rand_v08.rs` for parallel naming with `rand_v09.rs`/`rand_v010.rs`, with a `rand` re-export alias preserving the existing `dashu::*::rand` paths. * Consolidate the standalone `add_sub_oracle` example into the fuzz differential test: draw random significands directly via `UniformBits` (no per-iteration string allocation/parsing), add a deterministic small-precision sweep, and use a guard-digit-tolerant oracle (accept the result matching rounding to p or p+1). * Fix `Context::sub(0, b)` mis-rounding under the asymmetric directed modes `Up` (toward +inf) and `Down` (toward -inf): the zero-left path rounded `b` and then negated (`-round(b)`), but `round(-x) != -round(x)` for those modes, so the result could land one ULP off. The negated operand is now rounded directly. Add a regression test; remove the obsolete strict- precision borrow-rounding known-failures test. Co-Authored-By: Claude <noreply@anthropic.com> * De-duplicate rand modules: core in rand.rs, private version modules The rand distributions (UniformBits/Uniform01/UniformFBig/...) and their sampling algorithms now live once in a version-agnostic `rand` module per crate, generic over a `BitRng` trait (defined in dashu-int). The `rand_v08`/`rand_v09`/`rand_v010` modules are reduced to private per-version trait bindings. - dashu-int::rand exposes the `BitRng` trait plus `bridge_v08`/`bridge_v09`/ `bridge_v010` constructors, so float/ratio and end users can adapt any rand version's RNG (or implement `BitRng` directly). - Version modules are now private; the distributions are accessed via `dashu_*::rand`. The common module docs live in `rand.rs`, and a runnable rand-0.8 example lives in `float/examples/random_fbig.rs`. Co-Authored-By: Claude <noreply@anthropic.com> * Fix mul::simple tests for no_std and non-64-bit Word targets The test module used `vec!`/`Vec` without imports (broke the no_std CI job) and hardcoded 64-bit `Word` (`u64`/`u128`/`>> 64`/`u64::MAX`) in the addmul-2 reference + tests (broke the 16/32-bit `force_bits` CI matrix). - Import `alloc::vec` / `alloc::vec::Vec` (matching other test modules), and gate the BMI2-only carry bindings so they aren't unused on non-x86_64 / no_std. - Make `add_mul_dword_ref` and the dword-kernel tests `Word`-generic: shift by `Word::BITS`, use `Word::MAX`, and cast the PRNG output `as Word`. Co-Authored-By: Claude <noreply@anthropic.com> * Fix pre-existing clippy::useless_conversion in ubig buffer tests on 16-bit Word `gen_ubig` took `num_words: u16` and pushed `i.into()`; on a 16-bit Word target (Word = u16) that is a useless u16 -> u16 conversion. Widen the parameter to `usize` and cast the loop index `as Word`, so the conversion is a genuine cross-type cast at every Word width (and `as Word` can't trip `unnecessary_cast`, since `usize` is never the same type as `Word`). Co-Authored-By: Claude <noreply@anthropic.com> * Restore "Precision and rounding" section to dashu-float rand module docs The section describing the precision of each distribution (Uniform01 / builtin Standard* / UniformFBig) was dropped during the rand-module de-dup. Add it back to the version-agnostic rand.rs core, keeping the [Uniform01]/[UniformFBig]/[DoubleWord]/[FBig] intra-doc links and rendering the version-specific `Standard`/`StandardUniform` and `Uniform` as plain text (the core is version-agnostic). Co-Authored-By: Claude <noreply@anthropic.com> * Simplify mul::simple dword tests: drop edge-cases, use rand Remove `dword_kernel_edge_cases` (trivial all-ones/zero-multiplier cases already covered by the two fuzz tests), and replace the hand-rolled SplitMix64 PRNG (`next_rand`) with `rand_v08`'s `StdRng`, dropping the manual helper and the now-unused `alloc::vec` macro import. Co-Authored-By: Claude <noreply@anthropic.com> * Add #[inline] to all Distribution::sample impls Several rand `Distribution::sample` impls were missing `#[inline]` (`UniformBits`/`UniformBelow` for integers, `UniformFBig`/`Uniform01`/ `Standard` for floats, `Uniform01<Repr>` for rationals) while others (`Open01`, `OpenClosed01`, the rational builtins) already had it. Add it consistently across all three rand versions so the delegating `sample` methods inline into the caller. Co-Authored-By: Claude <noreply@anthropic.com> * Restore Denominator section to dashu-rational rand module docs The section describing the denominator of each distribution (Uniform01 / builtin Standard* / UniformRBig, plus the closed-interval off-by-one note) was dropped during the rand-module de-dup. Add it back to the version-agnostic rand.rs core, rendering the version-specific `Standard`/`StandardUniform` and `DoubleWord::MAX` as plain text. Co-Authored-By: Claude <noreply@anthropic.com> * Strip rand_v09/v010 from the MSRV (1.68) build rand 0.10 requires Rust 1.85 and its feature table (getrandom as an optional dep with the `dep:` syntax) breaks the resolver under the 1.68 build, even though only `rand` (== rand_v08) is enabled there. Extend `drop_incompatible_deps_for_msrv.py` to also remove `rand_v09`/`rand_v010` (their deps + feature lines) from the manifests for the MSRV build, so only rand 0.8 is resolved. rand_v09/v010 remain covered by the stable and 1.85 `--all-features` jobs. Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Jacob Zhong <jacob@rimbot.com> Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A batch of correctness fixes and performance work across
dashu-intanddashu-float, plus optional support for rand 0.9 / 0.10. Several of the float fixes were surfaced and are now permanentlyguarded by a differential fuzz test.
dashu-floatadd/sub correctnessFour rounding defects in the limited-precision alignment/rounding path:
1.00 − 0.99999999at p=3returned
0instead of1e-8). Such cases now form the exact difference at full width and round once.precision − digits, letting it land on a rounding tie (e.g.1 + 2⁻¹⁰⁰at p=10 returned513·2⁻⁹instead of1). It isnow placed at the operand's real magnitude.
0.5 − 0.4375at p=1 returned0). It now fires on>=.Context::sub(0, b)under directed modes — the zero-left path did-round(b)instead ofround(-b), mis-rounding under the asymmetric modesUp/Down. It now rounds the negated operanddirectly.
By design, an inexact addition/subtraction may now carry a single guard digit (up to
precision + 1digits); this is documented inAGENTS.md.dashu-intperformanceadd_mul_dword_same_len_in_place,sub_mul_dword_same_len_in_place) dispatch at runtime to a BMI2 build on x86-64 +std(flag-freemulxwideningmultiply, unrolled loop), ~4–5% faster in isolation. The portable path,
no_std, and other targets are unchanged.radix_powers/big_chunksinstead of growing them push-by-push.guide/src/performance.md) coveringtarget-cpu=nativeand related build flags.rand 0.9 / 0.10 support
New optional
rand_v09(rand 0.9) andrand_v010(rand 0.10) features acrossdashu-int/-float/-ratio(plus root forwarding), mirroringrand_v08. The v08 source files are renamed torand_v08.rsfor parallel naming, with arandre-export alias preserving the existingdashu::*::randpaths. The defaultrandfeature staysrand_v08.Tests
add_sub_oracleexample into the fuzz differential test: significands drawn directly viaUniformBits, a deterministic small-precision sweep, and aguard-digit-tolerant oracle (accept rounding to
porp+1).Context::sub(0, b)directed-rounding fix.Cleanup
Removed an unused BMI2 addmul-2 micro-benchmark and a stale
$impl-sharing TODO.