Skip to content

Commit d6ac4e3

Browse files
committed
[stdlib] Improve performance of generic binary integer == and <
1 parent d70e16b commit d6ac4e3

File tree

1 file changed

+46
-55
lines changed

1 file changed

+46
-55
lines changed

stdlib/public/core/Integers.swift

Lines changed: 46 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1658,39 +1658,32 @@ extension BinaryInteger {
16581658
public static func == <
16591659
Other: BinaryInteger
16601660
>(lhs: Self, rhs: Other) -> Bool {
1661-
let lhsNegative = Self.isSigned && lhs < (0 as Self)
1662-
let rhsNegative = Other.isSigned && rhs < (0 as Other)
1663-
1664-
if lhsNegative != rhsNegative { return false }
1665-
1666-
// Here we know the values are of the same sign.
1661+
let rhs_ = Self(truncatingIfNeeded: rhs)
1662+
// Is `rhs` representable as a value of `Self` type? In other words, does
1663+
// the bit pattern conversion above preserve the value of `rhs`?
16671664
//
1668-
// There are a few possible scenarios from here:
1669-
//
1670-
// 1. Both values are negative
1671-
// - If one value is strictly wider than the other, then it is safe to
1672-
// convert to the wider type.
1673-
// - If the values are of the same width, it does not matter which type we
1674-
// choose to convert to as the values are already negative, and thus
1675-
// include the sign bit if two's complement representation already.
1676-
// 2. Both values are non-negative
1677-
// - If one value is strictly wider than the other, then it is safe to
1678-
// convert to the wider type.
1679-
// - If the values are of the same width, than signedness matters, as not
1680-
// unsigned types are 'wider' in a sense they don't need to 'waste' the
1681-
// sign bit. Therefore it is safe to convert to the unsigned type.
1682-
1683-
if lhs.bitWidth < rhs.bitWidth {
1684-
return Other(truncatingIfNeeded: lhs) == rhs
1685-
}
1686-
if lhs.bitWidth > rhs.bitWidth {
1687-
return lhs == Self(truncatingIfNeeded: rhs)
1665+
// To find out the answer, we see if the value roundtrips by bit pattern
1666+
// conversion back to `Self` [1], and we also check that the original bit
1667+
// pattern conversion doesn't change the sign [2].
1668+
if Other(truncatingIfNeeded: rhs_) == rhs /* [1] */
1669+
&& (rhs < (0 as Other)) == (rhs_ < (0 as Self)) /* [2] */ {
1670+
return lhs == rhs_
16881671
}
16891672

1690-
if Self.isSigned {
1691-
return Other(truncatingIfNeeded: lhs) == rhs
1673+
let lhs_ = Other(truncatingIfNeeded: lhs)
1674+
// Is `lhs` representable as a value of `Other` type?
1675+
if Self(truncatingIfNeeded: lhs_) == lhs
1676+
&& (lhs < (0 as Self)) == (lhs_ < (0 as Other)) {
1677+
return lhs_ == rhs
16921678
}
1693-
return lhs == Self(truncatingIfNeeded: rhs)
1679+
1680+
// If we're here, then either:
1681+
// - `Self` is signed and fixed-width, `Other` is unsigned,
1682+
// `lhs` is negative, and `rhs` is greater than `Self.max`; or
1683+
// - `Other` is signed and fixed-width, `Self` is unsigned,
1684+
// `rhs` is negative, and `lhs` is greater than `Other.max`.
1685+
// Thus, `lhs != rhs`.
1686+
return false
16941687
}
16951688

16961689
/// Returns a Boolean value indicating whether the two given values are not
@@ -1730,34 +1723,32 @@ extension BinaryInteger {
17301723
/// - rhs: Another integer to compare.
17311724
@_transparent
17321725
public static func < <Other: BinaryInteger>(lhs: Self, rhs: Other) -> Bool {
1733-
let lhsNegative = Self.isSigned && lhs < (0 as Self)
1734-
let rhsNegative = Other.isSigned && rhs < (0 as Other)
1735-
if lhsNegative != rhsNegative { return lhsNegative }
1736-
1737-
if lhs == (0 as Self) && rhs == (0 as Other) { return false }
1738-
1739-
// if we get here, lhs and rhs have the same sign. If they're negative,
1740-
// then Self and Other are both signed types, and one of them can represent
1741-
// values of the other type. Otherwise, lhs and rhs are positive, and one
1742-
// of Self, Other may be signed and the other unsigned.
1743-
1744-
let rhsAsSelf = Self(truncatingIfNeeded: rhs)
1745-
let rhsAsSelfNegative = rhsAsSelf < (0 as Self)
1746-
1747-
1748-
// Can we round-trip rhs through Other?
1749-
if Other(truncatingIfNeeded: rhsAsSelf) == rhs &&
1750-
// This additional check covers the `Int8.max < (128 as UInt8)` case.
1751-
// Since the types are of the same width, init(truncatingIfNeeded:)
1752-
// will result in a simple bitcast, so that rhsAsSelf would be -128, and
1753-
// `lhs < rhsAsSelf` will return false.
1754-
// We basically guard against that bitcast by requiring rhs and rhsAsSelf
1755-
// to be the same sign.
1756-
rhsNegative == rhsAsSelfNegative {
1757-
return lhs < rhsAsSelf
1726+
let rhs_ = Self(truncatingIfNeeded: rhs)
1727+
// Is `rhs` representable as a value of `Self` type? In other words, does
1728+
// the bitcast operation above preserve the value of `rhs`?
1729+
//
1730+
// To find out the answer, we see if the value roundtrips by bitcasting back
1731+
// to `Self` [1], and we also check that bitcasting doesn't change the sign
1732+
// [2].
1733+
if Other(truncatingIfNeeded: rhs_) == rhs /* [1] */
1734+
&& (rhs < (0 as Other)) == (rhs_ < (0 as Self)) /* [2] */ {
1735+
return lhs < rhs_
1736+
}
1737+
1738+
let lhs_ = Other(truncatingIfNeeded: lhs)
1739+
// Is `lhs` representable as a value of `Other` type?
1740+
if Self(truncatingIfNeeded: lhs_) == lhs
1741+
&& (lhs < (0 as Self)) == (lhs_ < (0 as Other)) {
1742+
return lhs_ < rhs
17581743
}
17591744

1760-
return Other(truncatingIfNeeded: lhs) < rhs
1745+
// If we're here, then either:
1746+
// - `Self` is signed and fixed-width, `Other` is unsigned,
1747+
// `lhs` is negative, and `rhs` is greater than `Self.max`; or
1748+
// - `Other` is signed and fixed-width, `Self` is unsigned,
1749+
// `rhs` is negative, and `lhs` is greater than `Other.max`.
1750+
// Thus, `lhs < rhs` if and only if `Self.isSigned`.
1751+
return Self.isSigned
17611752
}
17621753

17631754
/// Returns a Boolean value indicating whether the value of the first

0 commit comments

Comments
 (0)