From 3e8676c327086e4761674060793dcfd2f03d0c48 Mon Sep 17 00:00:00 2001 From: Federico Stra Date: Mon, 18 Sep 2023 18:34:44 +0200 Subject: [PATCH 01/10] isqrt: initial implementation --- library/core/src/num/int_macros.rs | 80 +++++++++++++++++++++++++++++ library/core/src/num/uint_macros.rs | 35 +++++++++++++ 2 files changed, 115 insertions(+) diff --git a/library/core/src/num/int_macros.rs b/library/core/src/num/int_macros.rs index 1f43520e1b3..66bd8001353 100644 --- a/library/core/src/num/int_macros.rs +++ b/library/core/src/num/int_macros.rs @@ -898,6 +898,45 @@ pub const fn checked_pow(self, mut exp: u32) -> Option { acc.checked_mul(base) } + /// Returns the square root of the number, rounded down. + /// + /// Returns `None` if `self` is negative. + /// + /// # Examples + /// + /// Basic usage: + /// ``` + #[doc = concat!("assert_eq!(10", stringify!($SelfT), ".checked_isqrt(), Some(3));")] + /// ``` + #[stable(feature = "isqrt", since = "1.73.0")] + #[rustc_const_stable(feature = "isqrt", since = "1.73.0")] + #[must_use = "this returns the result of the operation, \ + without modifying the original"] + #[inline] + pub const fn checked_isqrt(self) -> Option { + if self < 0 { + return None; + } else if self < 2 { + return Some(self); + } + + let mut x: Self = self; + let mut c: Self = 0; + let mut d: Self = 1 << (self.ilog2() & !1); + + while (d != 0) { + if x >= c + d { + x -= c + d; + c = (c >> 1) + d; + } else { + c >>= 1; + } + d >>= 2; + } + + return Some(c); + } + /// Saturating integer addition. Computes `self + rhs`, saturating at the numeric /// bounds instead of overflowing. /// @@ -2061,6 +2100,47 @@ pub const fn pow(self, mut exp: u32) -> Self { acc * base } + /// Returns the square root of the number, rounded down. + /// + /// # Panics + /// + /// This function will panic if `self` is negative. + /// + /// # Examples + /// + /// Basic usage: + /// ``` + #[doc = concat!("assert_eq!(10", stringify!($SelfT), ".isqrt(), 3);")] + /// ``` + #[stable(feature = "isqrt", since = "1.73.0")] + #[rustc_const_stable(feature = "isqrt", since = "1.73.0")] + #[must_use = "this returns the result of the operation, \ + without modifying the original"] + #[inline] + pub const fn isqrt(self) -> Self { + if self < 0 { + panic!("argument of integer square root must be non-negative") + } else if self < 2 { + return self; + } + + let mut x: Self = self; + let mut c: Self = 0; + let mut d: Self = 1 << (self.ilog2() & !1); + + while (d != 0) { + if x >= c + d { + x -= c + d; + c = (c >> 1) + d; + } else { + c >>= 1; + } + d >>= 2; + } + + return c; + } + /// Calculates the quotient of Euclidean division of `self` by `rhs`. /// /// This computes the integer `q` such that `self = q * rhs + r`, with diff --git a/library/core/src/num/uint_macros.rs b/library/core/src/num/uint_macros.rs index 23ca37817d4..4c093ab8220 100644 --- a/library/core/src/num/uint_macros.rs +++ b/library/core/src/num/uint_macros.rs @@ -1979,6 +1979,41 @@ pub const fn pow(self, mut exp: u32) -> Self { acc * base } + /// Returns the square root of the number, rounded down. + /// + /// # Examples + /// + /// Basic usage: + /// ``` + #[doc = concat!("assert_eq!(10", stringify!($SelfT), ".isqrt(), 3);")] + /// ``` + #[stable(feature = "isqrt", since = "1.73.0")] + #[rustc_const_stable(feature = "isqrt", since = "1.73.0")] + #[must_use = "this returns the result of the operation, \ + without modifying the original"] + #[inline] + pub const fn isqrt(self) -> Self { + if self < 2 { + return self; + } + + let mut x: Self = self; + let mut c: Self = 0; + let mut d: Self = 1 << (self.ilog2() & !1); + + while (d != 0) { + if x >= c + d { + x -= c + d; + c = (c >> 1) + d; + } else { + c >>= 1; + } + d >>= 2; + } + + return c; + } + /// Performs Euclidean division. /// /// Since, for the positive integers, all common From 1b34f1c6b2b407c286f0e0bac084114c7475c61e Mon Sep 17 00:00:00 2001 From: Federico Stra Date: Mon, 18 Sep 2023 19:27:26 +0200 Subject: [PATCH 02/10] isqrt: add tests --- library/core/tests/num/int_macros.rs | 11 +++++++++++ library/core/tests/num/uint_macros.rs | 10 ++++++++++ 2 files changed, 21 insertions(+) diff --git a/library/core/tests/num/int_macros.rs b/library/core/tests/num/int_macros.rs index 439bbe66997..a9ce8433864 100644 --- a/library/core/tests/num/int_macros.rs +++ b/library/core/tests/num/int_macros.rs @@ -290,6 +290,17 @@ fn test_pow() { assert_eq!(r.saturating_pow(0), 1 as $T); } + #[test] + fn test_isqrt() { + assert_eq!($T::MIN.checked_isqrt(), None); + assert_eq!((-1 as $T).checked_isqrt(), None); + assert_eq!((0 as $T).isqrt(), 0 as $T); + assert_eq!((1 as $T).isqrt(), 1 as $T); + assert_eq!((2 as $T).isqrt(), 1 as $T); + assert_eq!((99 as $T).isqrt(), 9 as $T); + assert_eq!((100 as $T).isqrt(), 10 as $T); + } + #[test] fn test_div_floor() { let a: $T = 8; diff --git a/library/core/tests/num/uint_macros.rs b/library/core/tests/num/uint_macros.rs index 7d6203db0b9..9a8421c7b95 100644 --- a/library/core/tests/num/uint_macros.rs +++ b/library/core/tests/num/uint_macros.rs @@ -206,6 +206,16 @@ fn test_pow() { assert_eq!(r.saturating_pow(2), MAX); } + #[test] + fn test_isqrt() { + assert_eq!((0 as $T).isqrt(), 0 as $T); + assert_eq!((1 as $T).isqrt(), 1 as $T); + assert_eq!((2 as $T).isqrt(), 1 as $T); + assert_eq!((99 as $T).isqrt(), 9 as $T); + assert_eq!((100 as $T).isqrt(), 10 as $T); + assert_eq!($T::MAX.isqrt(), (1 << ($T::BITS / 2)) - 1); + } + #[test] fn test_div_floor() { assert_eq!((8 as $T).div_floor(3), 2); From 68f0b475c76de88fb5cb37c8cd0163703354415a Mon Sep 17 00:00:00 2001 From: Federico Stra Date: Tue, 26 Sep 2023 15:44:28 +0200 Subject: [PATCH 03/10] isqrt: remove duplication by delegating to unsigned integers --- library/core/src/num/int_macros.rs | 50 +++++++---------------------- library/core/src/num/uint_macros.rs | 2 +- 2 files changed, 12 insertions(+), 40 deletions(-) diff --git a/library/core/src/num/int_macros.rs b/library/core/src/num/int_macros.rs index 66bd8001353..8bc82b5fc88 100644 --- a/library/core/src/num/int_macros.rs +++ b/library/core/src/num/int_macros.rs @@ -915,26 +915,10 @@ pub const fn checked_pow(self, mut exp: u32) -> Option { #[inline] pub const fn checked_isqrt(self) -> Option { if self < 0 { - return None; - } else if self < 2 { - return Some(self); + None + } else { + Some((self as $UnsignedT).isqrt() as Self) } - - let mut x: Self = self; - let mut c: Self = 0; - let mut d: Self = 1 << (self.ilog2() & !1); - - while (d != 0) { - if x >= c + d { - x -= c + d; - c = (c >> 1) + d; - } else { - c >>= 1; - } - d >>= 2; - } - - return Some(c); } /// Saturating integer addition. Computes `self + rhs`, saturating at the numeric @@ -2118,27 +2102,15 @@ pub const fn pow(self, mut exp: u32) -> Self { without modifying the original"] #[inline] pub const fn isqrt(self) -> Self { - if self < 0 { - panic!("argument of integer square root must be non-negative") - } else if self < 2 { - return self; + // I would like to implement it as + // ``` + // self.checked_isqrt().expect("argument of integer square root must be non-negative") + // ``` + // but `expect` is not yet stable as a `const fn`. + match self.checked_isqrt() { + Some(sqrt) => sqrt, + None => panic!("argument of integer square root must be non-negative"), } - - let mut x: Self = self; - let mut c: Self = 0; - let mut d: Self = 1 << (self.ilog2() & !1); - - while (d != 0) { - if x >= c + d { - x -= c + d; - c = (c >> 1) + d; - } else { - c >>= 1; - } - d >>= 2; - } - - return c; } /// Calculates the quotient of Euclidean division of `self` by `rhs`. diff --git a/library/core/src/num/uint_macros.rs b/library/core/src/num/uint_macros.rs index 4c093ab8220..f8005694ceb 100644 --- a/library/core/src/num/uint_macros.rs +++ b/library/core/src/num/uint_macros.rs @@ -2011,7 +2011,7 @@ pub const fn isqrt(self) -> Self { d >>= 2; } - return c; + c } /// Performs Euclidean division. From d49da0fe54251fbee190dc2adcdbffe787aecae7 Mon Sep 17 00:00:00 2001 From: Federico Stra Date: Tue, 26 Sep 2023 16:05:51 +0200 Subject: [PATCH 04/10] isqrt: add more tests --- library/core/tests/num/int_macros.rs | 17 +++++++++++++++++ library/core/tests/num/uint_macros.rs | 15 +++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/library/core/tests/num/int_macros.rs b/library/core/tests/num/int_macros.rs index a9ce8433864..dd0ea5e9238 100644 --- a/library/core/tests/num/int_macros.rs +++ b/library/core/tests/num/int_macros.rs @@ -299,6 +299,23 @@ fn test_isqrt() { assert_eq!((2 as $T).isqrt(), 1 as $T); assert_eq!((99 as $T).isqrt(), 9 as $T); assert_eq!((100 as $T).isqrt(), 10 as $T); + + let n_max: $T = (1024 * 1024).min($T::MAX as u128) as $T; + for n in 0..=n_max { + let isqrt: $T = n.isqrt(); + + assert!(isqrt.pow(2) <= n); + let (square, overflow) = (isqrt + 1).overflowing_pow(2); + assert!(overflow || square > n); + } + + for n in ($T::MAX - 127)..=$T::MAX { + let isqrt: $T = n.isqrt(); + + assert!(isqrt.pow(2) <= n); + let (square, overflow) = (isqrt + 1).overflowing_pow(2); + assert!(overflow || square > n); + } } #[test] diff --git a/library/core/tests/num/uint_macros.rs b/library/core/tests/num/uint_macros.rs index 9a8421c7b95..1ae7d048757 100644 --- a/library/core/tests/num/uint_macros.rs +++ b/library/core/tests/num/uint_macros.rs @@ -214,6 +214,21 @@ fn test_isqrt() { assert_eq!((99 as $T).isqrt(), 9 as $T); assert_eq!((100 as $T).isqrt(), 10 as $T); assert_eq!($T::MAX.isqrt(), (1 << ($T::BITS / 2)) - 1); + + let n_max: $T = (1024 * 1024).min($T::MAX as u128) as $T; + for n in 0..=n_max { + let isqrt: $T = n.isqrt(); + + assert!(isqrt.pow(2) <= n); + assert!(isqrt + 1 == (1 as $T) << ($T::BITS / 2) || (isqrt + 1).pow(2) > n); + } + + for n in ($T::MAX - 255)..=$T::MAX { + let isqrt: $T = n.isqrt(); + + assert!(isqrt.pow(2) <= n); + assert!(isqrt + 1 == (1 as $T) << ($T::BITS / 2) || (isqrt + 1).pow(2) > n); + } } #[test] From 17dfb18bd1ad15ce4437bb44ba30b0c82aad3e07 Mon Sep 17 00:00:00 2001 From: Federico Stra Date: Thu, 28 Sep 2023 10:43:41 +0200 Subject: [PATCH 05/10] fixup! isqrt: initial implementation Fix C-ism and type inference. --- library/core/src/num/uint_macros.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/library/core/src/num/uint_macros.rs b/library/core/src/num/uint_macros.rs index f8005694ceb..b232d5d50cb 100644 --- a/library/core/src/num/uint_macros.rs +++ b/library/core/src/num/uint_macros.rs @@ -1997,11 +1997,11 @@ pub const fn isqrt(self) -> Self { return self; } - let mut x: Self = self; - let mut c: Self = 0; - let mut d: Self = 1 << (self.ilog2() & !1); + let mut x = self; + let mut c = 0; + let mut d = 1 << (self.ilog2() & !1); - while (d != 0) { + while d != 0 { if x >= c + d { x -= c + d; c = (c >> 1) + d; From c97ab231415aa46cd1d35e8d00d601dec0d23e86 Mon Sep 17 00:00:00 2001 From: Federico Stra Date: Thu, 28 Sep 2023 11:15:43 +0200 Subject: [PATCH 06/10] isqrt: fix stability --- library/core/src/lib.rs | 1 + library/core/src/num/int_macros.rs | 10 ++++++---- library/core/src/num/uint_macros.rs | 5 +++-- library/core/tests/lib.rs | 1 + 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/library/core/src/lib.rs b/library/core/src/lib.rs index 8b04bafcda5..be734a9ba52 100644 --- a/library/core/src/lib.rs +++ b/library/core/src/lib.rs @@ -178,6 +178,7 @@ #![feature(ip)] #![feature(ip_bits)] #![feature(is_ascii_octdigit)] +#![feature(isqrt)] #![feature(maybe_uninit_uninit_array)] #![feature(ptr_alignment_type)] #![feature(ptr_metadata)] diff --git a/library/core/src/num/int_macros.rs b/library/core/src/num/int_macros.rs index 8bc82b5fc88..d1fe8b55cc1 100644 --- a/library/core/src/num/int_macros.rs +++ b/library/core/src/num/int_macros.rs @@ -906,10 +906,11 @@ pub const fn checked_pow(self, mut exp: u32) -> Option { /// /// Basic usage: /// ``` + /// #![feature(isqrt)] #[doc = concat!("assert_eq!(10", stringify!($SelfT), ".checked_isqrt(), Some(3));")] /// ``` - #[stable(feature = "isqrt", since = "1.73.0")] - #[rustc_const_stable(feature = "isqrt", since = "1.73.0")] + #[unstable(feature = "isqrt", issue = "none")] + #[rustc_const_unstable(feature = "isqrt", issue = "none")] #[must_use = "this returns the result of the operation, \ without modifying the original"] #[inline] @@ -2094,10 +2095,11 @@ pub const fn pow(self, mut exp: u32) -> Self { /// /// Basic usage: /// ``` + /// #![feature(isqrt)] #[doc = concat!("assert_eq!(10", stringify!($SelfT), ".isqrt(), 3);")] /// ``` - #[stable(feature = "isqrt", since = "1.73.0")] - #[rustc_const_stable(feature = "isqrt", since = "1.73.0")] + #[unstable(feature = "isqrt", issue = "none")] + #[rustc_const_unstable(feature = "isqrt", issue = "none")] #[must_use = "this returns the result of the operation, \ without modifying the original"] #[inline] diff --git a/library/core/src/num/uint_macros.rs b/library/core/src/num/uint_macros.rs index b232d5d50cb..5de672d5d7e 100644 --- a/library/core/src/num/uint_macros.rs +++ b/library/core/src/num/uint_macros.rs @@ -1985,10 +1985,11 @@ pub const fn pow(self, mut exp: u32) -> Self { /// /// Basic usage: /// ``` + /// #![feature(isqrt)] #[doc = concat!("assert_eq!(10", stringify!($SelfT), ".isqrt(), 3);")] /// ``` - #[stable(feature = "isqrt", since = "1.73.0")] - #[rustc_const_stable(feature = "isqrt", since = "1.73.0")] + #[unstable(feature = "isqrt", issue = "none")] + #[rustc_const_unstable(feature = "isqrt", issue = "none")] #[must_use = "this returns the result of the operation, \ without modifying the original"] #[inline] diff --git a/library/core/tests/lib.rs b/library/core/tests/lib.rs index 17011b845cf..4d67f6f1511 100644 --- a/library/core/tests/lib.rs +++ b/library/core/tests/lib.rs @@ -56,6 +56,7 @@ #![feature(min_specialization)] #![feature(numfmt)] #![feature(num_midpoint)] +#![feature(isqrt)] #![feature(step_trait)] #![feature(str_internals)] #![feature(std_internals)] From 51463175a46599eb69375861bfe3626f68d643a4 Mon Sep 17 00:00:00 2001 From: Federico Stra Date: Thu, 28 Sep 2023 12:12:18 +0200 Subject: [PATCH 07/10] isqrt: cite source and rename variables to match original C code --- library/core/src/num/uint_macros.rs | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/library/core/src/num/uint_macros.rs b/library/core/src/num/uint_macros.rs index 5de672d5d7e..371d6e180c2 100644 --- a/library/core/src/num/uint_macros.rs +++ b/library/core/src/num/uint_macros.rs @@ -1998,21 +1998,26 @@ pub const fn isqrt(self) -> Self { return self; } - let mut x = self; - let mut c = 0; - let mut d = 1 << (self.ilog2() & !1); + // The algorithm is based on the one presented in + // + // which cites as source the following C code: + // . - while d != 0 { - if x >= c + d { - x -= c + d; - c = (c >> 1) + d; + let mut op = self; + let mut res = 0; + let mut one = 1 << (self.ilog2() & !1); + + while one != 0 { + if op >= res + one { + op -= res + one; + res = (res >> 1) + one; } else { - c >>= 1; + res >>= 1; } - d >>= 2; + one >>= 2; } - c + res } /// Performs Euclidean division. From 77f9eae9956f6c7c23bdf81c2efdd47625382ea2 Mon Sep 17 00:00:00 2001 From: Federico Stra Date: Thu, 28 Sep 2023 12:32:58 +0200 Subject: [PATCH 08/10] fixup! isqrt: fix stability --- library/core/src/num/int_macros.rs | 8 ++++---- library/core/src/num/uint_macros.rs | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/library/core/src/num/int_macros.rs b/library/core/src/num/int_macros.rs index d1fe8b55cc1..3cbb55af3bc 100644 --- a/library/core/src/num/int_macros.rs +++ b/library/core/src/num/int_macros.rs @@ -909,8 +909,8 @@ pub const fn checked_pow(self, mut exp: u32) -> Option { /// #![feature(isqrt)] #[doc = concat!("assert_eq!(10", stringify!($SelfT), ".checked_isqrt(), Some(3));")] /// ``` - #[unstable(feature = "isqrt", issue = "none")] - #[rustc_const_unstable(feature = "isqrt", issue = "none")] + #[unstable(feature = "isqrt", issue = "116226")] + #[rustc_const_unstable(feature = "isqrt", issue = "116226")] #[must_use = "this returns the result of the operation, \ without modifying the original"] #[inline] @@ -2098,8 +2098,8 @@ pub const fn pow(self, mut exp: u32) -> Self { /// #![feature(isqrt)] #[doc = concat!("assert_eq!(10", stringify!($SelfT), ".isqrt(), 3);")] /// ``` - #[unstable(feature = "isqrt", issue = "none")] - #[rustc_const_unstable(feature = "isqrt", issue = "none")] + #[unstable(feature = "isqrt", issue = "116226")] + #[rustc_const_unstable(feature = "isqrt", issue = "116226")] #[must_use = "this returns the result of the operation, \ without modifying the original"] #[inline] diff --git a/library/core/src/num/uint_macros.rs b/library/core/src/num/uint_macros.rs index 371d6e180c2..565fc1930ea 100644 --- a/library/core/src/num/uint_macros.rs +++ b/library/core/src/num/uint_macros.rs @@ -1988,8 +1988,8 @@ pub const fn pow(self, mut exp: u32) -> Self { /// #![feature(isqrt)] #[doc = concat!("assert_eq!(10", stringify!($SelfT), ".isqrt(), 3);")] /// ``` - #[unstable(feature = "isqrt", issue = "none")] - #[rustc_const_unstable(feature = "isqrt", issue = "none")] + #[unstable(feature = "isqrt", issue = "116226")] + #[rustc_const_unstable(feature = "isqrt", issue = "116226")] #[must_use = "this returns the result of the operation, \ without modifying the original"] #[inline] From fcdfd5b0b9efcf7797b0f1bf7e7ed47ff99de198 Mon Sep 17 00:00:00 2001 From: Federico Stra Date: Thu, 28 Sep 2023 13:59:19 +0200 Subject: [PATCH 09/10] isqrt: `assume` that `isqrt` takes half as many bits https://github.com/rust-lang/rust/issues/89273#issuecomment-970581089 --- library/core/src/num/uint_macros.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/library/core/src/num/uint_macros.rs b/library/core/src/num/uint_macros.rs index 565fc1930ea..f2190efa4d3 100644 --- a/library/core/src/num/uint_macros.rs +++ b/library/core/src/num/uint_macros.rs @@ -2017,6 +2017,13 @@ pub const fn isqrt(self) -> Self { one >>= 2; } + // SAFETY: the result is positive and fits in an integer with half as many bits. + // Inform the optimizer about it. + unsafe { + intrinsics::assume(0 < res); + intrinsics::assume(res < 1 << (Self::BITS / 2)); + } + res } From 25648de28f10799fa6274f64fa12475292231c72 Mon Sep 17 00:00:00 2001 From: Federico Stra Date: Thu, 28 Sep 2023 17:43:01 +0200 Subject: [PATCH 10/10] isqrt: disable long running tests in Miri --- library/core/tests/num/int_macros.rs | 4 ++++ library/core/tests/num/uint_macros.rs | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/library/core/tests/num/int_macros.rs b/library/core/tests/num/int_macros.rs index dd0ea5e9238..165d9a29617 100644 --- a/library/core/tests/num/int_macros.rs +++ b/library/core/tests/num/int_macros.rs @@ -299,7 +299,11 @@ fn test_isqrt() { assert_eq!((2 as $T).isqrt(), 1 as $T); assert_eq!((99 as $T).isqrt(), 9 as $T); assert_eq!((100 as $T).isqrt(), 10 as $T); + } + #[cfg(not(miri))] // Miri is too slow + #[test] + fn test_lots_of_isqrt() { let n_max: $T = (1024 * 1024).min($T::MAX as u128) as $T; for n in 0..=n_max { let isqrt: $T = n.isqrt(); diff --git a/library/core/tests/num/uint_macros.rs b/library/core/tests/num/uint_macros.rs index 1ae7d048757..955440647eb 100644 --- a/library/core/tests/num/uint_macros.rs +++ b/library/core/tests/num/uint_macros.rs @@ -214,7 +214,11 @@ fn test_isqrt() { assert_eq!((99 as $T).isqrt(), 9 as $T); assert_eq!((100 as $T).isqrt(), 10 as $T); assert_eq!($T::MAX.isqrt(), (1 << ($T::BITS / 2)) - 1); + } + #[cfg(not(miri))] // Miri is too slow + #[test] + fn test_lots_of_isqrt() { let n_max: $T = (1024 * 1024).min($T::MAX as u128) as $T; for n in 0..=n_max { let isqrt: $T = n.isqrt();