42: Add a special case for single-digit divisors r=cuviper a=cuviper
It was pointed out in the blog post [Big Integers in Zig] that we don't
have a special case in `num-bigint` for single-digit divisors. While
you can already get this optimization by dividing directly by `u32`,
it's easy to make small `BigUint` divisors work like this too.
$ cargo benchcmp baseline single-div
name baseline ns/iter single-div ns/iter diff ns/iter diff % speedup
factorial_div_biguint 5,638,353 1,005,488 -4,632,865 -82.17% x 5.61
`BigInt` will also gain from this, since it uses `BigUint` division
internally.
Running [zig-bn's facdiv-rs] shows a nice improvement too. My i7-7700K
with Rust 1.26 goes from 4.15 seconds to just 0.65 -- a 6.38x speedup!
[Big Integers in Zig]: https://tiehuis.github.io/big-integers-in-zig#division-test-single-limb
[zig-bn's facdiv-rs]: https://github.com/tiehuis/zig-bn/tree/master/bench/facdiv/crate-facdiv-rs
Co-authored-by: Josh Stone <cuviper@gmail.com>
It was pointed out in the blog post [Big Integers in Zig] that we don't
have a special case in `num-bigint` for single-digit divisors. While
you can already get this optimization by dividing directly by `u32`,
it's easy to make small `BigUint` divisors work like this too.
$ cargo benchcmp baseline single-div
name baseline ns/iter single-div ns/iter diff ns/iter diff % speedup
factorial_div_biguint 5,638,353 1,005,488 -4,632,865 -82.17% x 5.61
`BigInt` will also gain from this, since it uses `BigUint` division
internally.
Running [zig-bn's facdiv-rs] shows a nice improvement too. My i7-7700K
with Rust 1.26 goes from 4.15 seconds to just 0.65 -- a 6.38x speedup!
[Big Integers in Zig]: https://tiehuis.github.io/big-integers-in-zig#division-test-single-limb
[zig-bn's facdiv-rs]: https://github.com/tiehuis/zig-bn/tree/master/bench/facdiv/crate-facdiv-rs
41: Implement assignment operators for BigInt r=cuviper a=cuviper
All of these operators were already implemented for `BigUint`, so
now `BigInt` support the same use.
Closes#7.
Co-authored-by: Josh Stone <cuviper@gmail.com>
39: Make it explicit that serialization is in u32 r=cuviper a=cuviper
Add explicit type annotations to make sure we serialize `BigUint` like a
`Vec<u32>`. If we ever change the internal representation, we still
need to remain compatible with serialized data.
Also, deserializing `BigUint` and `BigInt` now uses their normal
constructor methods, to make sure they are normalized. We shouldn't
assume that all incoming data is already well-formed.
Co-authored-by: Josh Stone <cuviper@gmail.com>
Add explicit type annotations to make sure we serialize `BigUint` like a
`Vec<u32>`. If we ever change the internal representation, we still
need to remain compatible with serialized data.
Also, deserializing `BigUint` and `BigInt` now uses their normal
constructor methods, to make sure they are normalized. We shouldn't
assume that all incoming data is already well-formed.
38: Remove big_digit::* from the public API r=cuviper a=cuviper
The *idea* of `big_digit` and its type aliases is that we may someday
use something other than `u32` in the representation, perhaps even
different sizes for different targets. That's still a possibility, but
I think it's not really feasible to expose this variation in the public
API. Calling `BigUint::from_slice([1, 2, 3])` is only meaningful if you
know what that size is, and users can't really alternate this kind of
thing based on a type definition. So for now, we just commit to `u32`
units in the public API, no matter what we may do internally.
This removal is a breaking change, part of the 0.2 semver bump. If I'm
wrong and somebody can show a compelling use case for `big_digit`, we
can always add things back into the public API later.
Co-authored-by: Josh Stone <cuviper@gmail.com>
The *idea* of `big_digit` and its type aliases is that we may someday
use something other than `u32` in the representation, perhaps even
different sizes for different targets. That's still a possibility, but
I think it's not really feasible to expose this variation in the public
API. Calling `BigUint::from_slice([1, 2, 3])` is only meaningful if you
know what that size is, and users can't really alternate this kind of
thing based on a type definition. So for now, we just commit to `u32`
units in the public API, no matter what we may do internally.
This removal is a breaking change, part of the 0.2 semver bump. If I'm
wrong and somebody can show a compelling use case for `big_digit`, we
can always add things back into the public API later.
26: two's-complement logic operations on BigInt r=cuviper a=tspiteri
This commit adds the following impls, giving results equivalent to what would be expected with two's complement.
* `impl Not for BigInt`
* `impl BitAnd<&BigInt> for BigInt`, `impl BitAndAssign<&BigInt> for BigInt`
* `impl BitOr<&BigInt> for BigInt`, `impl BitOrAssign<&BigInt> for BigInt`
* `impl BitXor<&BigInt> for BigInt`, `impl BitXorAssign<&BigInt> for BigInt`
I also added some tests. There is most probably some room for optimization, but the bitwise operations all operate in place using a single pass.
I did not add other implementations such as `impl BitAnd<BigInt> for BigInt`, those can be done later.
Edit: Fixes#13
35: avoid over-allocation for bitwise and of references r=cuviper a=tspiteri
For `&BigUint & &BigUint`, clone the smaller value to avoid over-allocation, as the result will have a length less than or equal to the minimum length of the operands.
34: do not zero-extend for addition when val is shorter than ref r=cuviper a=tspiteri
For `short_val + long_ref`, `long_ref + short_val` and `long_ref - short_val`, the current code extends the short value with zeros and then performs the operation. Instead, this commit splits the addition/subtraction into two parts, the lower and the higher parts. After performing the addition/subtraction on the low part, the short value is extended with the high part of the long reference, and the intermediate carry/borrow is then added to/subtracted from the high part.
28: Implement is_one for BigUint and BigInt. r=cuviper a=clarcharr
Won't work until a newer version of `num-traits` is pushed, but I figured I'd offer these now.
27: Chop the tests up r=cuviper a=cuviper
The test modules were getting huge, and some of its functions were
actually a huge amount of code due to macros, causing tests to take a
long time just to compile. They are now separated into a few different
tests, and the scalar macros especially are now expanded more sparingly
in just a few `check()` functions.
Test compile times for me went from about 25 seconds to 1.5s in debug
mode, and from 300 seconds (!) to about 8s in release mode.
The test modules were getting huge, and some of its functions were
actually a huge amount of code due to macros, causing tests to take a
long time just to compile. They are now separated into a few different
tests, and the scalar macros especially are now expanded more sparingly
in just a few `check()` functions.
Test compile times for me went from about 25 seconds to 1.5s in debug
mode, and from 300 seconds (!) to about 8s in release mode.
22: Implementation of std::iter::{Product, Sum} r=cuviper a=dodomorandi
This PR is relative to the [issue 4](https://github.com/rust-num/num-bigint/issues/4). This is a
very simple implementation, based on two macros. Maybe a more elegant
solution is possible for these, feel free to suggest.
Two tests for both `BigInt` and `BigUInt` have been added, one relative
to the `Sum`, the other to the `Product`.
The succestion came from @cupiver, and it was simple and elegant. Now it
should be possible to use `std::iter::{Sum, Product}` with everything
that can be Add`-ed and `Mul`-tiplied _against_ `BigInt` and `BigUint`
This is relative to the
[issue 4](https://github.com/rust-num/num-bigint/issues/4). This is a
very simple implementation, based on two macros. Maybe a more elegant
solution is possible for these, feel free to suggest.
Two tests for both `BigInt` and `BigUInt` have been added, one relative
to the `Sum`, the other to the `Product`.
25: do not convert carry/borrow to/from DoubleBigDigit r=cuviper a=tspiteri
Currently `adc`, `sbb`, `mac_with_carry` and `mul_with_carry` in *algorithms.rs* take the carry/borrow as a `BigDigit`, then convert it every iteration to `DoubleBigDigit` for the addition/subtraction. With this commit, a kind of accumulator takes place of the carry and borrow. It is stored as a `DoubleBigDigit` and not converted to/from `BigDigit` at all. For subtraction, the accumulator is stored as a `SignedDoubleBigDigit` in order to enable arithmetic right shift and to be able to easily handle subtraction.
I'm not very familiar with the crate's benchmarking methods. I wrote some hacky benchmarks for a quick comparison. Basically, just have two vectors `[10000000, 0, !0, 1, 29999999]` and `[10, !0, 5, 234, 23]`, and add/subtract/multiply a couple hundred times. I saw these gains:
```
running 6 tests
test tests::bench_changed_add ... bench: 992 ns/iter (+/- 58)
test tests::bench_changed_mul ... bench: 9,667 ns/iter (+/- 185)
test tests::bench_changed_sub ... bench: 1,497 ns/iter (+/- 38)
test tests::bench_original_add ... bench: 1,085 ns/iter (+/- 50)
test tests::bench_original_mul ... bench: 9,838 ns/iter (+/- 284)
test tests::bench_original_sub ... bench: 1,642 ns/iter (+/- 14)
```
The gains are not very large, after all only a couple of assembly instructions per iteration are saved. I also do not know whether other architectures than x86_64 are affected similarly or in the opposite direction.
The change in the parameter from `&mut BigDigit` to `&mut DoubleBigDigit` is automatically handled in other files, as carry is simply initialized as zero and compared to zero at the end, with the type being inferred.
8: Make Shr for negative BigInt round down, like primitives do r=cuviper a=cuviper
Primitive integers always round down when shifting right, but `BigInt`
was effectively rounding toward zero, because it just kept its sign and
used the `BigUint` magnitude rounded down (always toward zero).
Now we adjust the result of shifting negative values, and explicitly
test that it matches the result for primitive integers.
Fixes#1.
Primitive integers always round down when shifting right, but `BigInt`
was effectively rounding toward zero, because it just kept its sign and
used the `BigUint` magnitude rounded down (always toward zero).
Now we adjust the result of shifting negative values, and explicitly
test that it matches the result for primitive integers.
The ApInt crate is a pure rust implementation for arbitrary fixed bitwidth modulo arithmetics of integers with a focus on correctness and efficiency - especially for smaller bit widths. It features a generic signless `ApInt` as well as signed counterparts `Int` (signed int) and `UInt` (unsigned int) that are thin wrappers around it. The implementation is unfinished in some parts but that should be fixed over time.
20: Note a few alternative crates in README.md r=cuviper a=cuviper
This presents a table of some common alternatives to this crate which may have better performance. I'm not including any specific performance comparisons, since that will surely vary on different workloads, and will change as each crate evolves. I want to keep this impartial, listing only simple objective facts about each. Corrections welcome!
cc crate owners: @Aatch, @doomrobo, @fizyk20, @tspiteri