diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 44bcde8..9823d24 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -10,6 +10,8 @@ jobs: matrix: rust: [ 1.31.0, # MSRV + 1.51.0, + 1.60.0, stable, beta, nightly, @@ -24,7 +26,6 @@ jobs: - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.rust }} - - run: cargo build - run: ./ci/test_full.sh # try a target that doesn't have std at all diff --git a/.github/workflows/master.yaml b/.github/workflows/main.yaml similarity index 91% rename from .github/workflows/master.yaml rename to .github/workflows/main.yaml index 68c4900..0ce4f57 100644 --- a/.github/workflows/master.yaml +++ b/.github/workflows/main.yaml @@ -1,8 +1,8 @@ -name: master +name: main on: push: branches: - - master + - main schedule: - cron: '0 0 * * 0' # 00:00 Sunday @@ -24,5 +24,4 @@ jobs: - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.rust }} - - run: cargo build - run: ./ci/test_full.sh diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index 787f908..4e6aa37 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -20,7 +20,6 @@ jobs: - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.rust }} - - run: cargo build - run: ./ci/test_full.sh fmt: diff --git a/Cargo.toml b/Cargo.toml index 8c5fc92..a22ea3a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ categories = ["algorithms", "science", "no-std"] license = "MIT OR Apache-2.0" repository = "https://github.com/rust-num/num-integer" name = "num-integer" -version = "0.1.46" +version = "0.1.47" readme = "README.md" exclude = ["/ci/*", "/.github/*"] edition = "2018" diff --git a/README.md b/README.md index aefe368..01c656b 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![crate](https://img.shields.io/crates/v/num-integer.svg)](https://crates.io/crates/num-integer) [![documentation](https://docs.rs/num-integer/badge.svg)](https://docs.rs/num-integer) [![minimum rustc 1.31](https://img.shields.io/badge/rustc-1.31+-red.svg)](https://rust-lang.github.io/rfcs/2495-min-rust-version.html) -[![build status](https://github.com/rust-num/num-integer/workflows/master/badge.svg)](https://github.com/rust-num/num-integer/actions) +[![build status](https://github.com/rust-num/num-integer/actions/workflows/main.yaml/badge.svg)](https://github.com/rust-num/num-integer/actions/workflows/main.yaml) `Integer` trait and functions for Rust. diff --git a/RELEASES.md b/RELEASES.md index 9aeca2d..aa2ed42 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,3 +1,12 @@ +# Release 0.1.47 (2026-08-11) + +- [Implement Karatsuba Square Root for 128-bit `Roots::sqrt`][80] +- Miscellaneous other improvements to docs and testing. + +**Contributors**: @durin42, @cuviper, @mikem8891 + +[80]: https://github.com/rust-num/num-integer/pull/80 + # Release 0.1.46 (2024-02-07) - [Upgrade to 2018 edition, **MSRV 1.31**][51] diff --git a/ci/rustup.sh b/ci/rustup.sh index fed3e45..18015d2 100755 --- a/ci/rustup.sh +++ b/ci/rustup.sh @@ -5,6 +5,6 @@ set -ex ci=$(dirname $0) -for version in 1.31.0 stable beta nightly; do +for version in 1.31.0 1.51.0 1.60.0 stable beta nightly; do rustup run "$version" "$ci/test_full.sh" done diff --git a/ci/test_full.sh b/ci/test_full.sh index c03cec7..7a8e043 100755 --- a/ci/test_full.sh +++ b/ci/test_full.sh @@ -21,6 +21,14 @@ check_version() { ]] } +export CARGO_RESOLVER_INCOMPATIBLE_RUST_VERSIONS=fallback +generate_lockfile() { + cargo generate-lockfile + if ! check_version 1.85 ; then + cargo +stable update + fi +} + echo "Testing $CRATE on rustc $RUST_VERSION" if ! check_version $MSRV ; then echo "The minimum for $CRATE is rustc $MSRV" @@ -30,6 +38,8 @@ fi FEATURES=() echo "Testing supported features: ${FEATURES[*]}" +generate_lockfile + set -x # test the default diff --git a/src/lib.rs b/src/lib.rs index 30fce25..42d4614 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -100,6 +100,8 @@ pub trait Integer: Sized + Num + PartialOrd + Ord + Eq { /// Greatest Common Divisor (GCD). /// + /// The result should always be non-negative. + /// /// # Examples /// /// ~~~ @@ -1018,14 +1020,14 @@ macro_rules! impl_integer_for_usize { #[test] fn test_is_multiple_of() { - assert!((0 as $T).is_multiple_of(&(0 as $T))); - assert!((6 as $T).is_multiple_of(&(6 as $T))); - assert!((6 as $T).is_multiple_of(&(3 as $T))); - assert!((6 as $T).is_multiple_of(&(1 as $T))); - - assert!(!(42 as $T).is_multiple_of(&(5 as $T))); - assert!(!(5 as $T).is_multiple_of(&(3 as $T))); - assert!(!(42 as $T).is_multiple_of(&(0 as $T))); + assert!(<$T as Integer>::is_multiple_of(&(0 as $T), &(0 as $T))); + assert!(<$T as Integer>::is_multiple_of(&(6 as $T), &(6 as $T))); + assert!(<$T as Integer>::is_multiple_of(&(6 as $T), &(3 as $T))); + assert!(<$T as Integer>::is_multiple_of(&(6 as $T), &(1 as $T))); + + assert!(!<$T as Integer>::is_multiple_of(&(42 as $T), &(5 as $T))); + assert!(!<$T as Integer>::is_multiple_of(&(5 as $T), &(3 as $T))); + assert!(!<$T as Integer>::is_multiple_of(&(42 as $T), &(0 as $T))); } #[test] diff --git a/src/roots.rs b/src/roots.rs index aed3802..5e23986 100644 --- a/src/roots.rs +++ b/src/roots.rs @@ -196,6 +196,44 @@ fn log2(x: T) -> u32 { bits::() - 1 - x.leading_zeros() } +/// 128-bit Karatsuba Square Root, using b = 2³² +/// +/// Reference: +/// Paul Zimmermann. Karatsuba Square Root. [Research Report] RR-3805, INRIA. 1999, pp.8. +/// +#[inline] +fn karatsuba_sqrt(n: u128) -> u128 { + // Algorithm SqrtRem(n = a₃b³ + a₂b² + a₁b + a₀) + // Input: 0 ≤ aᵢ < b with a₃ ≥ b/4 + // Output: (s,r) such that s² ≤ n = s² + r < (s+1)² + debug_assert!(n.leading_zeros() < 2); + let a0 = n as u32 as u128; + let a1 = (n >> 32) as u32 as u128; + let a23 = n >> 64; + + // (s',r') ← SqrtRem(a₃b + a₂) + let s1 = (a23 as u64).sqrt() as u128; + let r1 = a23 - s1 * s1; + + // (q,u) ← DivRem(r'b + a₁, 2s') + let (q, u) = ((r1 << 32) | a1).div_rem(&(2 * s1)); + + // s ← s'b + q + let mut s = (s1 << 32) + q; + + // r ← ub + a₀ - q² + // if r < 0 then + // r ← r + 2s - 1 + // s ← s - 1 + // + // but to avoid negatives, we compare and adjust before subtraction, + // and in this case we don't care about the actual remainder. + if ((u << 32) | a0) < q * q { + s -= 1; + } + s +} + macro_rules! unsigned_roots { ($T:ident) => { impl Roots for $T { @@ -275,17 +313,14 @@ macro_rules! unsigned_roots { fn sqrt(&self) -> Self { fn go(a: $T) -> $T { if bits::<$T>() > 64 { - // 128-bit division is slow, so do a bitwise `sqrt` until it's small enough. + // 128-bit division is slow in the Babylonian method, + // so use 64-bit sqrt and Karatsuba if needed. return if a <= core::u64::MAX as $T { (a as u64).sqrt() as $T } else { - let lo = (a >> 2u32).sqrt() << 1; - let hi = lo + 1; - if hi * hi <= a { - hi - } else { - lo - } + let shift = a.leading_zeros() / 2; + let n = a << (shift * 2); + (karatsuba_sqrt(n as u128) >> shift) as $T }; }