Skip to content

Commit 828a5d6

Browse files
author
Andronik Ordian
authored
uint: implement integer_sqrt (#554)
* uint: implement integer_sqrt * uint: remove wrong assertion * go brrrrrrrrrrrrrr * we don't need to move anymore * impl-num-traits: add integer-sqrt trait support * add missing test * fmt * bump uint
1 parent 96909f3 commit 828a5d6

File tree

15 files changed

+167
-6
lines changed

15 files changed

+167
-6
lines changed

.travis.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ script:
6161
- cd primitive-types/ && cargo test --all-features && cd ..
6262
- cd primitive-types/ && cargo test --no-default-features --features=serde_no_std && cd ..
6363
- cd primitive-types/ && cargo test --no-default-features --features=scale-info && cd ..
64+
- cd primitive-types/ && cargo test --no-default-features --features=num-traits && cd ..
6465
- cd rlp/ && cargo test --no-default-features && cargo check --benches && cd ..
6566
- cd triehash/ && cargo check --benches && cd ..
6667
- cd kvdb-web/ && wasm-pack test --headless --firefox && cd ..

primitive-types/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,7 @@ required-features = ["scale-info"]
4040
[[test]]
4141
name = "fp_conversion"
4242
required-features = ["fp-conversion"]
43+
44+
[[test]]
45+
name = "num_traits"
46+
required-features = ["num-traits"]

primitive-types/impls/num-traits/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ The format is based on [Keep a Changelog].
55
[Keep a Changelog]: http://keepachangelog.com/en/1.0.0/
66

77
## [Unreleased]
8+
- Added `integer-sqrt` trait support. [#554](https://github.com/paritytech/parity-common/pull/554)
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "impl-num-traits"
3-
version = "0.1.0"
3+
version = "0.1.1"
44
authors = ["Parity Technologies <[email protected]>"]
55
license = "MIT OR Apache-2.0"
66
homepage = "https://github.com/paritytech/parity-common"
@@ -9,4 +9,5 @@ edition = "2018"
99

1010
[dependencies]
1111
num-traits = { version = "0.2", default-features = false }
12+
integer-sqrt = "0.1"
1213
uint = { version = "0.9.0", path = "../../../uint", default-features = false }

primitive-types/impls/num-traits/src/lib.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
#[doc(hidden)]
1414
pub use num_traits;
1515

16+
#[doc(hidden)]
17+
pub use integer_sqrt;
18+
1619
#[doc(hidden)]
1720
pub use uint;
1821

@@ -48,5 +51,11 @@ macro_rules! impl_uint_num_traits {
4851
Self::from_str_radix(txt, radix)
4952
}
5053
}
54+
55+
impl $crate::integer_sqrt::IntegerSquareRoot for $name {
56+
fn integer_sqrt_checked(&self) -> Option<Self> {
57+
Some(self.integer_sqrt())
58+
}
59+
}
5160
};
5261
}

primitive-types/tests/num_traits.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright 2021 Parity Technologies
2+
//
3+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6+
// option. This file may not be copied, modified, or distributed
7+
// except according to those terms.
8+
9+
use impl_num_traits::integer_sqrt::IntegerSquareRoot;
10+
use primitive_types::U256;
11+
12+
#[test]
13+
fn u256_isqrt() {
14+
let x = U256::MAX;
15+
let s = x.integer_sqrt_checked().unwrap();
16+
assert_eq!(x.integer_sqrt(), s);
17+
}

uint/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ The format is based on [Keep a Changelog].
55
[Keep a Changelog]: http://keepachangelog.com/en/1.0.0/
66

77
## [Unreleased]
8+
- Added `integer_sqrt` method. [#554](https://github.com/paritytech/parity-common/pull/554)
89

910
## [0.9.0] - 2021-01-05
1011
- Allow `0x` prefix in `from_str`. [#487](https://github.com/paritytech/parity-common/pull/487)

uint/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ homepage = "http://parity.io"
44
repository = "https://github.com/paritytech/parity-common"
55
license = "MIT OR Apache-2.0"
66
name = "uint"
7-
version = "0.9.0"
7+
version = "0.9.1"
88
authors = ["Parity Technologies <[email protected]>"]
99
readme = "README.md"
1010
edition = "2018"

uint/benches/bigint.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ criterion_group!(
4444
u256_div,
4545
u512_div_mod,
4646
u256_rem,
47+
u256_integer_sqrt,
4748
u256_bit_and,
4849
u256_bit_or,
4950
u256_bit_xor,
@@ -58,6 +59,7 @@ criterion_group!(
5859
u512_mul,
5960
u512_div,
6061
u512_rem,
62+
u512_integer_sqrt,
6163
u512_mul_u32_vs_u64,
6264
mulmod_u512_vs_biguint_vs_gmp,
6365
conversions,
@@ -253,6 +255,22 @@ fn u256_rem(c: &mut Criterion) {
253255
);
254256
}
255257

258+
fn u256_integer_sqrt(c: &mut Criterion) {
259+
c.bench(
260+
"u256_integer_sqrt",
261+
ParameterizedBenchmark::new(
262+
"",
263+
|b, x| b.iter(|| black_box(x.integer_sqrt().0)),
264+
vec![
265+
U256::from(u64::MAX),
266+
U256::from(u128::MAX) + 1,
267+
U256::from(u128::MAX - 1) * U256::from(u128::MAX - 1) - 1,
268+
U256::MAX,
269+
],
270+
),
271+
);
272+
}
273+
256274
fn u512_pairs() -> Vec<(U512, U512)> {
257275
vec![
258276
(U512::from(1u64), U512::from(0u64)),
@@ -286,6 +304,23 @@ fn u512_mul(c: &mut Criterion) {
286304
);
287305
}
288306

307+
fn u512_integer_sqrt(c: &mut Criterion) {
308+
c.bench(
309+
"u512_integer_sqrt",
310+
ParameterizedBenchmark::new(
311+
"",
312+
|b, x| b.iter(|| black_box(x.integer_sqrt().0)),
313+
vec![
314+
U512::from(u32::MAX) + 1,
315+
U512::from(u64::MAX),
316+
(U512::from(u128::MAX) + 1) * (U512::from(u128::MAX) + 1),
317+
U256::MAX.full_mul(U256::MAX) - 1,
318+
U512::MAX,
319+
],
320+
),
321+
);
322+
}
323+
289324
fn u512_div(c: &mut Criterion) {
290325
let one = U512([
291326
8326634216714383706,

uint/fuzz/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,7 @@ path = "fuzz_targets/div_mod.rs"
2424
[[bin]]
2525
name = "div_mod_word"
2626
path = "fuzz_targets/div_mod_word.rs"
27+
28+
[[bin]]
29+
name = "isqrt"
30+
path = "fuzz_targets/isqrt.rs"

uint/fuzz/fuzz_targets/div_mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ fn from_gmp(x: Integer) -> U512 {
2323
}
2424

2525
fuzz_target!(|data: &[u8]| {
26-
if data.len() == 128 {
26+
if data.len() == 128 {
2727
let x = U512::from_little_endian(&data[..64]);
2828
let y = U512::from_little_endian(&data[64..]);
2929
let x_gmp = Integer::from_digits(&data[..64], Order::LsfLe);
@@ -32,5 +32,5 @@ fuzz_target!(|data: &[u8]| {
3232
let (a, b) = x_gmp.div_rem(y_gmp);
3333
assert_eq!((from_gmp(a), from_gmp(b)), x.div_mod(y));
3434
}
35-
}
35+
}
3636
});

uint/fuzz/fuzz_targets/div_mod_word.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ fn div_mod_word(hi: u64, lo: u64, y: u64) -> (u64, u64) {
5757
}
5858

5959
fuzz_target!(|data: &[u8]| {
60-
if data.len() == 24 {
60+
if data.len() == 24 {
6161
let mut buf = [0u8; 8];
6262
buf.copy_from_slice(&data[..8]);
6363
let x = u64::from_ne_bytes(buf);
@@ -68,5 +68,5 @@ fuzz_target!(|data: &[u8]| {
6868
if x < z {
6969
assert_eq!(div_mod_word(x, y, z), div_mod_word_u128(x, y, z));
7070
}
71-
}
71+
}
7272
});

uint/fuzz/fuzz_targets/isqrt.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Copyright 2021 Parity Technologies
2+
//
3+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6+
// option. This file may not be copied, modified, or distributed
7+
// except according to those terms.
8+
9+
#![no_main]
10+
11+
use libfuzzer_sys::fuzz_target;
12+
use uint::*;
13+
14+
construct_uint! {
15+
pub struct U256(4);
16+
}
17+
18+
fn isqrt(mut me: U256) -> U256 {
19+
let one = U256::one();
20+
if me <= one {
21+
return me;
22+
}
23+
// the implementation is based on:
24+
// https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Binary_numeral_system_(base_2)
25+
26+
// "bit" starts at the highest power of four <= self.
27+
let max_shift = 4 * 64 as u32 - 1;
28+
let shift: u32 = (max_shift - me.leading_zeros()) & !1;
29+
let mut bit = one << shift;
30+
let mut result = U256::zero();
31+
while !bit.is_zero() {
32+
let x = result + bit;
33+
result >>= 1;
34+
if me >= x {
35+
me -= x;
36+
result += bit;
37+
}
38+
bit >>= 2;
39+
}
40+
result
41+
}
42+
43+
fuzz_target!(|data: &[u8]| {
44+
if data.len() == 32 {
45+
let x = U256::from_little_endian(data);
46+
let expected = isqrt(x);
47+
let got = x.integer_sqrt();
48+
assert_eq!(got, expected);
49+
}
50+
});

uint/src/uint.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -975,6 +975,28 @@ macro_rules! construct_uint {
975975
self.div_mod_knuth(other, n, m)
976976
}
977977

978+
/// Compute the highest `n` such that `n * n <= self`.
979+
pub fn integer_sqrt(&self) -> Self {
980+
let one = Self::one();
981+
if self <= &one {
982+
return *self;
983+
}
984+
985+
// the implementation is based on:
986+
// https://en.wikipedia.org/wiki/Integer_square_root#Using_only_integer_division
987+
988+
// Set the initial guess to something higher than √self.
989+
let shift: u32 = (self.bits() as u32 + 1) / 2;
990+
let mut x_prev = one << shift;
991+
loop {
992+
let x = (x_prev + self / x_prev) >> 1;
993+
if x >= x_prev {
994+
return x_prev;
995+
}
996+
x_prev = x;
997+
}
998+
}
999+
9781000
/// Fast exponentiation by squaring
9791001
/// https://en.wikipedia.org/wiki/Exponentiation_by_squaring
9801002
///

uint/tests/uint_tests.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1140,6 +1140,22 @@ pub mod laws {
11401140
}
11411141
}
11421142

1143+
quickcheck! {
1144+
fn isqrt(x: $uint_ty) -> TestResult {
1145+
let s = x.integer_sqrt();
1146+
let higher = s + 1;
1147+
if let Some(y) = higher.checked_mul(higher) {
1148+
TestResult::from_bool(
1149+
(s * s <= x) && (y > x)
1150+
)
1151+
} else {
1152+
TestResult::from_bool(
1153+
s * s <= x
1154+
)
1155+
}
1156+
}
1157+
}
1158+
11431159
quickcheck! {
11441160
fn pow_mul(x: $uint_ty) -> TestResult {
11451161
if x.overflowing_pow($uint_ty::from(2)).1 || x.overflowing_pow($uint_ty::from(3)).1 {

0 commit comments

Comments
 (0)