Skip to content

Commit 5413186

Browse files
committed
Deduplicate isqrt test code
1 parent 6be6854 commit 5413186

File tree

1 file changed

+99
-222
lines changed

1 file changed

+99
-222
lines changed

library/core/tests/num/int_sqrt.rs

+99-222
Original file line numberDiff line numberDiff line change
@@ -1,239 +1,54 @@
1-
//! This tests the `Integer::isqrt` methods.
2-
31
macro_rules! tests {
4-
($($SignedT:ident $UnsignedT:ident),+) => {
2+
($($T:ident $isqrt_consistency_check_fn_macro:ident),+) => {
53
$(
6-
mod $SignedT {
7-
/// This takes an input and, if it's nonnegative or
8-
#[doc = concat!("`", stringify!($SignedT), "::MIN`,")]
9-
/// checks that `isqrt` and `checked_isqrt` produce equivalent
10-
/// results for that input and for the negative of that input.
11-
fn isqrt_consistency_check(n: $SignedT) {
12-
// `$SignedT::MIN` will be negative, so we don't want to handle `n` as if it's nonnegative.
13-
if n >= 0 {
14-
assert_eq!(
15-
Some(n.isqrt()),
16-
n.checked_isqrt(),
17-
"`{n}.checked_isqrt()` should match `Some({n}.isqrt())`.",
18-
);
19-
}
20-
21-
// `wrapping_neg` so that `$SignedT::MIN` will negate to
22-
// itself rather than panicking.
23-
let negative_n = n.wrapping_neg();
24-
25-
// `negative_n` should be negative, but `n` could be zero,
26-
// so make sure not to check that one.
27-
if negative_n < 0 {
28-
assert_eq!(
29-
negative_n.checked_isqrt(),
30-
None,
31-
"`({negative_n}).checked_isqrt()` should be `None`, as {negative_n} is negative.",
32-
);
33-
34-
::std::panic::catch_unwind(::core::panic::AssertUnwindSafe(|| (-n).isqrt())).expect_err(
35-
&format!("`({negative_n}).isqrt()` should have panicked, as {negative_n} is negative.")
36-
);
37-
}
38-
}
4+
mod $T {
5+
$isqrt_consistency_check_fn_macro!($T);
396

407
// Check that the following produce the correct values from
41-
// `isqrt` and that `checked_isqrt` produces the same numeric
42-
// value as `isqrt`. Check also that their negative versions
43-
// and `$SignedT::MIN` produce a panic from `isqrt` and `None`
44-
// from `checked_isqrt`:
8+
// `isqrt`:
459
//
10+
// * `<$T>::MIN` (for signed types, no nonnegative value can
11+
// negate to `<$T>::MIN)
4612
// * the first and last 128 nonnegative values
4713
// * powers of two, minus one
4814
// * powers of two
49-
#[test]
50-
fn isqrt() {
51-
// Check the minimum value because there's no positive
52-
// value that can be negated into the minimum value.
53-
isqrt_consistency_check($SignedT::MIN);
54-
55-
for n in (0..=127)
56-
.chain($SignedT::MAX - 127..=$SignedT::MAX)
57-
.chain((0..$SignedT::BITS - 1).map(|exponent| (1 << exponent) - 1))
58-
.chain((0..$SignedT::BITS - 1).map(|exponent| 1 << exponent))
59-
{
60-
isqrt_consistency_check(n);
61-
62-
let sqrt_n = n.isqrt();
63-
assert!(
64-
sqrt_n * sqrt_n <= n,
65-
"The integer square root of {n} should be lower than {sqrt_n} (the current return value of `{n}.isqrt()`)."
66-
);
67-
assert!(
68-
(sqrt_n + 1).checked_mul(sqrt_n + 1).map(|higher_than_n| n < higher_than_n).unwrap_or(true),
69-
"The integer square root of {n} should be higher than {sqrt_n} (the current return value of `{n}.isqrt()`)."
70-
);
71-
}
72-
}
73-
74-
// Check the square roots of:
7515
//
76-
// * the first 1,024 perfect squares
77-
// * halfway between each of the first 1,024 perfect squares
78-
// and the next perfect square
79-
// * the next perfect square after the each of the first 1,024
80-
// perfect squares, minus one
81-
// * the last 1,024 perfect squares
82-
// * the last 1,024 perfect squares, minus one
83-
// * halfway between each of the last 1,024 perfect squares
84-
// and the previous perfect square
85-
#[test]
86-
// Skip this test on Miri, as it takes too long to run.
87-
#[cfg(not(miri))]
88-
fn isqrt_extended() {
89-
// The correct value is worked out by using the fact that
90-
// the nth nonzero perfect square is the sum of the first n
91-
// odd numbers:
92-
//
93-
// 1 = 1
94-
// 4 = 1 + 3
95-
// 9 = 1 + 3 + 5
96-
// 16 = 1 + 3 + 5 + 7
97-
//
98-
// Note also that the last odd number added in is two times
99-
// the square root of the previous perfect square, plus
100-
// one:
101-
//
102-
// 1 = 2*0 + 1
103-
// 3 = 2*1 + 1
104-
// 5 = 2*2 + 1
105-
// 7 = 2*3 + 1
106-
//
107-
// That means we can add the square root of this perfect
108-
// square once to get about halfway to the next perfect
109-
// square, then we can add the square root of this perfect
110-
// square again to get to the next perfect square minus
111-
// one, then we can add one to get to the next perfect
112-
// square.
113-
//
114-
// This allows us to, for each of the first 1,024 perfect
115-
// squares, test that the square roots of the following are
116-
// all correct and equal to each other:
117-
//
118-
// * the current perfect square
119-
// * about halfway to the next perfect square
120-
// * the next perfect square, minus one
121-
let mut n: $SignedT = 0;
122-
for sqrt_n in 0..1_024.min((1_u128 << (($SignedT::BITS - 1)/2)) - 1) as $SignedT {
123-
isqrt_consistency_check(n);
124-
assert_eq!(
125-
n.isqrt(),
126-
sqrt_n,
127-
"`{sqrt_n}.pow(2).isqrt()` should be {sqrt_n}."
128-
);
129-
130-
n += sqrt_n;
131-
isqrt_consistency_check(n);
132-
assert_eq!(
133-
n.isqrt(),
134-
sqrt_n,
135-
"{n} is about halfway between `{sqrt_n}.pow(2)` and `{}.pow(2)`, so `{n}.isqrt()` should be {sqrt_n}.",
136-
sqrt_n + 1
137-
);
138-
139-
n += sqrt_n;
140-
isqrt_consistency_check(n);
141-
assert_eq!(
142-
n.isqrt(),
143-
sqrt_n,
144-
"`({}.pow(2) - 1).isqrt()` should be {sqrt_n}.",
145-
sqrt_n + 1
146-
);
147-
148-
n += 1;
149-
}
150-
151-
// Similarly, for each of the last 1,024 perfect squares,
152-
// check:
153-
//
154-
// * the current perfect square
155-
// * the current perfect square, minus one
156-
// * about halfway to the previous perfect square
157-
158-
// `MAX`'s `isqrt` return value verified in `isqrt` test
159-
// function above.
160-
let maximum_sqrt = $SignedT::MAX.isqrt();
161-
let mut n = maximum_sqrt * maximum_sqrt;
162-
163-
for sqrt_n in (maximum_sqrt - 1_024.min((1_u128 << (($SignedT::BITS - 1)/2)) - 1) as $SignedT..maximum_sqrt).rev() {
164-
isqrt_consistency_check(n);
165-
assert_eq!(
166-
n.isqrt(),
167-
sqrt_n + 1,
168-
"`{0}.pow(2).isqrt()` should be {0}.",
169-
sqrt_n + 1
170-
);
171-
172-
n -= 1;
173-
isqrt_consistency_check(n);
174-
assert_eq!(
175-
n.isqrt(),
176-
sqrt_n,
177-
"`({}.pow(2) - 1).isqrt()` should be {sqrt_n}.",
178-
sqrt_n + 1
179-
);
180-
181-
n -= sqrt_n;
182-
isqrt_consistency_check(n);
183-
assert_eq!(
184-
n.isqrt(),
185-
sqrt_n,
186-
"{n} is about halfway between `{sqrt_n}.pow(2)` and `{}.pow(2)`, so `{n}.isqrt()` should be {sqrt_n}.",
187-
sqrt_n + 1
188-
);
189-
190-
n -= sqrt_n;
191-
}
192-
}
193-
}
194-
195-
mod $UnsignedT {
196-
/// This takes an input and, if it's nonzero, checks that
197-
/// `isqrt` produces the same numeric value for both
198-
#[doc = concat!("`", stringify!($UnsignedT), "` and ")]
199-
#[doc = concat!("`NonZero<", stringify!($UnsignedT), ">`.")]
200-
fn isqrt_consistency_check(n: $UnsignedT) {
201-
if n > 0 {
202-
assert_eq!(
203-
n.isqrt(),
204-
::core::num::NonZero::<$UnsignedT>::new(n)
205-
.expect("Cannot create a new `NonZero` value from a nonzero value")
206-
.isqrt()
207-
.get(),
208-
"`{n}.isqrt` should match `NonZero`'s `{n}.isqrt().get()`.",
209-
);
210-
}
211-
}
212-
16+
// For signed types, check that `checked_isqrt` and
17+
// `isqrt` either produce the same numeric value or
18+
// respectively produce `None` and a panic.
19+
//
20+
// For unsigned types check that `isqrt` produces the same
21+
// numeric value for `$T` and `NonZero<$T>`.
22+
//
21323
// Check that the following produce the correct values from
21424
// `isqrt` and that `checked_isqrt` produces the same numeric
21525
// value as `isqrt`:
21626
//
217-
// * the first and last 128 values
27+
// * the first and last 128 nonnegative values
21828
// * powers of two, minus one
21929
// * powers of two
22030
#[test]
22131
fn isqrt() {
32+
// Check the minimum value because, for signed types,
33+
// there's no nonnegative value that can be negated into
34+
// the minimum value.
35+
isqrt_consistency_check($T::MIN);
36+
22237
for n in (0..=127)
223-
.chain($UnsignedT::MAX - 127..=$UnsignedT::MAX)
224-
.chain((0..$UnsignedT::BITS).map(|exponent| (1 << exponent) - 1))
225-
.chain((0..$UnsignedT::BITS).map(|exponent| 1 << exponent))
38+
.chain(<$T>::MAX - 127..=<$T>::MAX)
39+
.chain((0..<$T>::MAX.count_ones()).map(|exponent| (1 << exponent) - 1))
40+
.chain((0..<$T>::MAX.count_ones()).map(|exponent| 1 << exponent))
22641
{
22742
isqrt_consistency_check(n);
22843

229-
let sqrt_n = n.isqrt();
44+
let isqrt_n = n.isqrt();
23045
assert!(
231-
sqrt_n * sqrt_n <= n,
232-
"The integer square root of {n} should be lower than {sqrt_n} (the current return value of `{n}.isqrt()`)."
46+
isqrt_n.checked_mul(isqrt_n).map(|isqrt_n_squared| isqrt_n_squared <= n).unwrap_or(false),
47+
"`{n}.isqrt()` should be lower than {isqrt_n}."
23348
);
23449
assert!(
235-
(sqrt_n + 1).checked_mul(sqrt_n + 1).map(|higher_than_n| n < higher_than_n).unwrap_or(true),
236-
"The integer square root of {n} should be higher than {sqrt_n} (the current return value of `{n}.isqrt()`)."
50+
(isqrt_n + 1).checked_mul(isqrt_n + 1).map(|isqrt_n_plus_1_squared| n < isqrt_n_plus_1_squared).unwrap_or(true),
51+
"`{n}.isqrt()` should be higher than {isqrt_n})."
23752
);
23853
}
23954
}
@@ -252,7 +67,7 @@ macro_rules! tests {
25267
#[test]
25368
// Skip this test on Miri, as it takes too long to run.
25469
#[cfg(not(miri))]
255-
fn test_isqrt_extended() {
70+
fn isqrt_extended() {
25671
// The correct value is worked out by using the fact that
25772
// the nth nonzero perfect square is the sum of the first n
25873
// odd numbers:
@@ -274,7 +89,7 @@ macro_rules! tests {
27489
// That means we can add the square root of this perfect
27590
// square once to get about halfway to the next perfect
27691
// square, then we can add the square root of this perfect
277-
// square again to get to the next perfect square minus
92+
// square again to get to the next perfect square, minus
27893
// one, then we can add one to get to the next perfect
27994
// square.
28095
//
@@ -285,8 +100,8 @@ macro_rules! tests {
285100
// * the current perfect square
286101
// * about halfway to the next perfect square
287102
// * the next perfect square, minus one
288-
let mut n: $UnsignedT = 0;
289-
for sqrt_n in 0..1_024.min((1_u128 << ($UnsignedT::BITS/2)) - 1) as $UnsignedT {
103+
let mut n: $T = 0;
104+
for sqrt_n in 0..1_024.min((1_u128 << (<$T>::MAX.count_ones()/2)) - 1) as $T {
290105
isqrt_consistency_check(n);
291106
assert_eq!(
292107
n.isqrt(),
@@ -321,13 +136,13 @@ macro_rules! tests {
321136
// * the current perfect square
322137
// * the current perfect square, minus one
323138
// * about halfway to the previous perfect square
324-
325-
// `MAX`'s `isqrt` return value verified in `isqrt` test
326-
// function above.
327-
let maximum_sqrt = $UnsignedT::MAX.isqrt();
139+
//
140+
// `MAX`'s `isqrt` return value is verified in the `isqrt`
141+
// test function above.
142+
let maximum_sqrt = <$T>::MAX.isqrt();
328143
let mut n = maximum_sqrt * maximum_sqrt;
329144

330-
for sqrt_n in (maximum_sqrt - 1_024.min((1_u128 << ($UnsignedT::BITS/2)) - 1) as $UnsignedT..maximum_sqrt).rev() {
145+
for sqrt_n in (maximum_sqrt - 1_024.min((1_u128 << (<$T>::MAX.count_ones()/2)) - 1) as $T..maximum_sqrt).rev() {
331146
isqrt_consistency_check(n);
332147
assert_eq!(
333148
n.isqrt(),
@@ -362,4 +177,66 @@ macro_rules! tests {
362177
};
363178
}
364179

365-
tests!(i8 u8, i16 u16, i32 u32, i64 u64, i128 u128, isize usize);
180+
macro_rules! signed_check {
181+
($T:ident) => {
182+
/// This takes an input and, if it's nonnegative or
183+
#[doc = concat!("`", stringify!($T), "::MIN`,")]
184+
/// checks that `isqrt` and `checked_isqrt` produce equivalent
185+
/// results for that input and for the negative of that input.
186+
fn isqrt_consistency_check(n: $T) {
187+
// `<$T>::MIN` will be negative, so ignore it in this nonnegative
188+
// section.
189+
if n >= 0 {
190+
assert_eq!(
191+
Some(n.isqrt()),
192+
n.checked_isqrt(),
193+
"`{n}.checked_isqrt()` should match `Some({n}.isqrt())`.",
194+
);
195+
}
196+
197+
// `wrapping_neg` so that `$SignedT::MIN` will negate to
198+
// itself rather than panicking.
199+
let negative_n = n.wrapping_neg();
200+
201+
// Zero negated will still be nonnegative, so ignore it in this
202+
// negative section.
203+
if negative_n < 0 {
204+
assert_eq!(
205+
negative_n.checked_isqrt(),
206+
None,
207+
"`({negative_n}).checked_isqrt()` should be `None`, as {negative_n} is negative.",
208+
);
209+
210+
::std::panic::catch_unwind(::core::panic::AssertUnwindSafe(|| (-n).isqrt())).expect_err(
211+
&format!("`({negative_n}).isqrt()` should have panicked, as {negative_n} is negative.")
212+
);
213+
}
214+
}
215+
};
216+
}
217+
218+
macro_rules! unsigned_check {
219+
($T:ident) => {
220+
/// This takes an input and, if it's nonzero, checks that
221+
/// `isqrt` produces the same numeric value for both
222+
#[doc = concat!("`", stringify!($T), "` and ")]
223+
#[doc = concat!("`NonZero<", stringify!($T), ">`.")]
224+
fn isqrt_consistency_check(n: $T) {
225+
// Zero cannot be turned into a `NonZero` value, so ignore it in
226+
// this nonzero section.
227+
if n > 0 {
228+
assert_eq!(
229+
n.isqrt(),
230+
::core::num::NonZero::<$T>::new(n)
231+
.expect("Was not able to create a new `NonZero` value from a nonzero value")
232+
.isqrt()
233+
.get(),
234+
"`{n}.isqrt` should match `NonZero`'s `{n}.isqrt().get()`.",
235+
);
236+
}
237+
}
238+
};
239+
}
240+
241+
tests!(i8 signed_check, i16 signed_check, i32 signed_check, i64 signed_check, i128 signed_check);
242+
tests!(u8 unsigned_check, u16 unsigned_check, u32 unsigned_check, u64 unsigned_check, u128 unsigned_check);

0 commit comments

Comments
 (0)