-
Notifications
You must be signed in to change notification settings - Fork 10.5k
[SE-0288] [stdlib] Adding isPower(of:) to BinaryInteger #24766
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
dingobye
wants to merge
1
commit into
swiftlang:main
Choose a base branch
from
dingobye:is_power_of
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -1315,6 +1315,161 @@ extension BinaryInteger { | |||||
return self.magnitude % other.magnitude == 0 | ||||||
} | ||||||
|
||||||
/// Returns `true` if this value is a power of the given base, and `false` | ||||||
/// otherwise. | ||||||
/// | ||||||
/// For two integers *a* and *b*, *a* is a power of *b* if *a* is equal to | ||||||
/// any repeated multiplication of *b*. For example: | ||||||
/// | ||||||
/// * `16.isPower(of: 2)` is `true`, because `16 == (2 * 2 * 2 * 2)` | ||||||
/// * `(-27).isPower(of: -3)` is `true`, because `-27 == (-3 * -3 * -3)` | ||||||
/// | ||||||
/// Note that one is a power of anything, because any number to the zero | ||||||
/// power is considered an empty product, which equals one. | ||||||
/// | ||||||
/// For the corner case where base is zero, `x.isPower(of: 0)` is `true` if | ||||||
/// `x` is either zero or one, and `false` otherwise. | ||||||
/// | ||||||
/// - Parameter base: The base value to test. | ||||||
@_alwaysEmitIntoClient | ||||||
public func isPower(of base: Self) -> Bool { | ||||||
// Fast path when base is one of the common cases. | ||||||
if base == 2 { return self._isPowerOfTwo } | ||||||
if base == 10 { return self._isPowerOfTen } | ||||||
if base._isPowerOfTwo { return self._isPowerOf(powerOfTwo: base) } | ||||||
benrimmington marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
// Slow path for other bases. | ||||||
return self._slowIsPower(of: base) | ||||||
} | ||||||
|
||||||
/// Returns `true` iff `self` is a power of two. | ||||||
/// | ||||||
/// This serves as a fast path for `isPower(of:)` when the input base is two. | ||||||
@_alwaysEmitIntoClient | ||||||
internal var _isPowerOfTwo: Bool { | ||||||
let words = self.words | ||||||
guard !words.isEmpty else { return false } | ||||||
|
||||||
// If the value is represented in a single word, perform the classic check. | ||||||
if words.count == 1 { | ||||||
return self > 0 && self & (self - 1) == 0 | ||||||
} | ||||||
|
||||||
// Return false if it is negative. Here we only need to check the most | ||||||
// significant word (i.e. the last element of `words`). | ||||||
if Self.isSigned && Int(bitPattern: words.last!) < 0 { | ||||||
return false | ||||||
} | ||||||
|
||||||
// Check if there is exactly one non-zero word and it is a power of two. | ||||||
var found = false | ||||||
for word in words { | ||||||
if word != 0 { | ||||||
if found || word & (word - 1) != 0 { return false } | ||||||
found = true | ||||||
} | ||||||
} | ||||||
return found | ||||||
} | ||||||
|
||||||
/// Returns `true` iff `self` is a power of the given `base`, which itself is | ||||||
/// a power of two. | ||||||
/// | ||||||
/// This serves as a fast path for `isPower(of:)` when the input base itself | ||||||
/// is a power of two. | ||||||
@_alwaysEmitIntoClient | ||||||
internal func _isPowerOf(powerOfTwo base: Self) -> Bool { | ||||||
_precondition(base._isPowerOfTwo) | ||||||
guard self._isPowerOfTwo else { return false } | ||||||
return self.trailingZeroBitCount.isMultiple(of: base.trailingZeroBitCount) | ||||||
} | ||||||
|
||||||
/// Returns `true` iff `self` is a power of ten. | ||||||
/// | ||||||
/// This serves as a fast path for `isPower(of:)` when the input base is ten. | ||||||
@_alwaysEmitIntoClient | ||||||
internal var _isPowerOfTen: Bool { | ||||||
let exponent = self.trailingZeroBitCount | ||||||
switch exponent { | ||||||
case 0: return self == 1 as UInt8 | ||||||
case 1: return self == 10 as UInt8 | ||||||
case 2: return self == 100 as UInt8 | ||||||
case 3: return self == 1000 as UInt16 | ||||||
case 4: return self == 10000 as UInt16 | ||||||
case 5: return self == 100000 as UInt32 | ||||||
case 6: return self == 1000000 as UInt32 | ||||||
case 7: return self == 10000000 as UInt32 | ||||||
case 8: return self == 100000000 as UInt32 | ||||||
case 9: return self == 1000000000 as UInt32 | ||||||
case 10: return self == 10000000000 as UInt64 | ||||||
case 11: return self == 100000000000 as UInt64 | ||||||
case 12: return self == 1000000000000 as UInt64 | ||||||
case 13: return self == 10000000000000 as UInt64 | ||||||
case 14: return self == 100000000000000 as UInt64 | ||||||
case 15: return self == 1000000000000000 as UInt64 | ||||||
case 16: return self == 10000000000000000 as UInt64 | ||||||
case 17: return self == 100000000000000000 as UInt64 | ||||||
case 18: return self == 1000000000000000000 as UInt64 | ||||||
case 19: return self == 10000000000000000000 as UInt64 | ||||||
default: | ||||||
// If this is 64-bit or less we can't have a higher power of 10 | ||||||
if self.bitWidth <= 64 { return false } | ||||||
|
||||||
// Quickly check if parts of the bit pattern fits the power of 10. | ||||||
// | ||||||
// 10^0 1 | ||||||
// 10^1 1_01_0 | ||||||
// 10^2 1_10_01_00 | ||||||
// 10^3 111_11_01_000 | ||||||
// 10^4 100111_00_01_0000 | ||||||
// 10^5 11000011_01_01_00000 | ||||||
// 10^6 1111010000_10_01_000000 | ||||||
// 10^7 1001100010010_11_01_0000000 | ||||||
// 10^8 101111101011110_00_01_00000000 | ||||||
// 10^9 11101110011010110_01_01_000000000 | ||||||
// 10^10 10010101000000101111_10_01_0000000000 | ||||||
// ... | ||||||
// Column 1 is some "gibberish", which cannot be checked easily | ||||||
// Column 2 is always the last two bits of the exponent | ||||||
// Column 3 is always 01 | ||||||
// Column 4 is the trailing zeros, in equal number to the exponent value | ||||||
// | ||||||
// We check if Column 2 matches the last two bits of the exponent and | ||||||
// Column 3 matches 0b01. | ||||||
guard (self >> exponent)._lowWord & 0b1111 == | ||||||
((exponent << 2) | 0b01) & 0b1111 else { return false } | ||||||
|
||||||
// Now time for the slow path. | ||||||
return self._slowIsPower(of: 10) | ||||||
} | ||||||
} | ||||||
|
||||||
/// Returns `true` iff `self` is a power of the given `base`. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔨
Suggested change
|
||||||
/// | ||||||
/// This serves as the slow path for `isPower(of:)`; it is based on a generic | ||||||
/// implementation that works for any input `base`. | ||||||
@_alwaysEmitIntoClient | ||||||
internal func _slowIsPower(of base: Self) -> Bool { | ||||||
// If self is 1 (i.e. any base to the zero power), return true. | ||||||
if self == 1 { return true } | ||||||
|
||||||
// Here if base is 0, 1 or -1, return true iff self equals base. | ||||||
if base.magnitude <= 1 { return self == base } | ||||||
|
||||||
// At this point, we have base.magnitude >= 2. We are going to repeatedly | ||||||
// perform multiplication by a factor of base, and check if it can equal | ||||||
// self. Such algorithm should be bounded to ensure termination. | ||||||
// | ||||||
// Calculate the bound, and return false when self is not multiple of base. | ||||||
let (bound, remainder) = self.quotientAndRemainder(dividingBy: base) | ||||||
guard remainder == 0 else { return false } | ||||||
|
||||||
// Return true if the product eventualy hits bound. Because if bound is | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice catch, Matt~ |
||||||
// power of base, then self (i.e. bound * base) must also be power of base. | ||||||
var x: Self = 1 | ||||||
while x.magnitude < bound.magnitude { x *= base } | ||||||
return x == bound | ||||||
} | ||||||
|
||||||
//===----------------------------------------------------------------------===// | ||||||
//===--- Homogeneous ------------------------------------------------------===// | ||||||
//===----------------------------------------------------------------------===// | ||||||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.