Skip to content

Commit 03d8ce5

Browse files
committed
Implement SE-0225 (BinaryInteger.isMultiple(of:))
A default implementation is provided for FixedWidthInteger. Very basic test coverage included as well.
1 parent 2f4e70b commit 03d8ce5

File tree

6 files changed

+82
-6
lines changed

6 files changed

+82
-6
lines changed

benchmark/single-source/BinaryFloatingPointConversionFromBinaryInteger.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,10 @@ extension MockBinaryInteger : BinaryInteger {
110110
var trailingZeroBitCount: Int {
111111
return _value.trailingZeroBitCount
112112
}
113+
114+
func isMultiple(of other: MockBinaryInteger<T>) -> Bool {
115+
return _value.isMultiple(of: other._value)
116+
}
113117

114118
static func + (
115119
lhs: MockBinaryInteger<T>, rhs: MockBinaryInteger<T>

stdlib/public/core/Integers.swift

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1151,6 +1151,21 @@ public protocol BinaryInteger :
11511151
func quotientAndRemainder(dividingBy rhs: Self)
11521152
-> (quotient: Self, remainder: Self)
11531153

1154+
/// Returns true if this value is a multiple of `other`, and false otherwise.
1155+
///
1156+
/// For two integers a and b, a is a multiple of b if there exists a third
1157+
/// integer q such that a = q*b. For example, 6 is a multiple of 3, because
1158+
/// 6 = 2*3, and zero is a multiple of everything, because 0 = 0*x, for any
1159+
/// integer x.
1160+
///
1161+
/// Two edge cases are worth particular attention:
1162+
/// - `x.isMultiple(of: 0)` is `true` if `x` is zero and `false` otherwise.
1163+
/// - `T.min.isMultiple(of: -1)` is `true` for signed integer `T`, even
1164+
/// though the quotient `T.min / -1` is not representable in type `T`.
1165+
///
1166+
/// - Parameter other: the value to test.
1167+
func isMultiple(of other: Self) -> Bool
1168+
11541169
/// Returns `-1` if this value is negative and `1` if it's positive;
11551170
/// otherwise, `0`.
11561171
///
@@ -2755,6 +2770,16 @@ extension FixedWidthInteger {
27552770
lhs = _nonMaskingRightShift(lhs, shift)
27562771
}
27572772

2773+
@inlinable
2774+
public func isMultiple(of other: Self) -> Bool {
2775+
// Nothing but zero is a multiple of zero.
2776+
if other == 0 { return self == 0 }
2777+
// Special case to avoid overflow on .min / -1 for signed types.
2778+
if Self.isSigned && self == .min && other == -1 { return true }
2779+
// Having handled those special cases, this is safe.
2780+
return self % other == 0
2781+
}
2782+
27582783
@inlinable // FIXME(sil-serialize-all)
27592784
@inline(__always)
27602785
public static func _nonMaskingRightShift(_ lhs: Self, _ rhs: Int) -> Self {

test/Constraints/tuple-arguments-supported.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,8 @@ func test8(_: ((Int, Int)) -> Int) {}
2323
test8 { (_, _) -> Int in 2 }
2424
test8 { (x, y) in x }
2525

26-
func isEven(_ x: Int) -> Bool { return x % 2 == 0 }
2726
let items = Array(zip(0..<10, 0..<10))
28-
_ = items.filter { (_, x) in isEven(x) }
27+
_ = items.filter { (_, x) in x.isMultiple(of: 2) }
2928
_ = items.filter { _ in true }
3029

3130
func toString(indexes: Int?...) -> String {

test/Prototypes/BigInt.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -729,6 +729,11 @@ public struct _BigInt<Word: FixedWidthInteger & UnsignedInteger> :
729729
let r = x._internalDivide(by: rhs)
730730
return (x, r)
731731
}
732+
733+
public func isMultiple(of other: _BigInt) -> Bool {
734+
if other == 0 { return self == 0 }
735+
return self % other == 0
736+
}
732737

733738
public static func &=(lhs: inout _BigInt, rhs: _BigInt) {
734739
var lhsTemp = lhs._dataAsTwosComplement()
@@ -1867,3 +1872,9 @@ BigIntBitTests.test("words") {
18671872
}
18681873

18691874
runAllTests()
1875+
1876+
BigIntTests.test("isMultiple") {
1877+
// Test that these do not crash.
1878+
expectTrue((0 as _BigInt<UInt>).isMultiple(of: 0))
1879+
expectFalse((1 as _BigInt<UInt>).isMultiple(of: 0))
1880+
}

test/Prototypes/DoubleWidth.swift.gyb

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,7 @@ import StdlibUnittest
3939
/// integer type. Nesting `DoubleWidth` instances, in particular, may result in
4040
/// undesirable performance.
4141
public struct DoubleWidth<Base : FixedWidthInteger>
42-
where Base.Magnitude : UnsignedInteger,
43-
Base.Words : Collection, Base.Magnitude.Words : Collection {
42+
where Base.Words : Collection, Base.Magnitude.Words : Collection {
4443

4544
public typealias High = Base
4645
public typealias Low = Base.Magnitude
@@ -282,7 +281,7 @@ extension DoubleWidth.Words: Collection {
282281

283282
public func index(after i: Index) -> Index {
284283
switch i._value {
285-
case let .low(li) where Base.bitWidth < UInt.bitWidth:
284+
case .low where Base.bitWidth < UInt.bitWidth:
286285
return Index(.high(_high.endIndex))
287286
case let .low(li):
288287
let next = _low.index(after: li)
@@ -1004,6 +1003,19 @@ dwTests.test("DivideMinByMinusOne") {
10041003
_ = f(Int1024.min)
10051004
}
10061005

1006+
dwTests.test("isMultiple") {
1007+
func isMultipleTest<T: FixedWidthInteger>(type: T.Type) {
1008+
expectTrue(T.min.isMultiple(of: 2))
1009+
expectFalse(T.max.isMultiple(of: 10))
1010+
// Test that these do not crash.
1011+
expectTrue((0 as T).isMultiple(of: 0))
1012+
expectFalse((1 as T).isMultiple(of: 0))
1013+
expectTrue(T.min.isMultiple(of: 0 &- 1))
1014+
}
1015+
isMultipleTest(type: Int128.self)
1016+
isMultipleTest(type: UInt128.self)
1017+
}
1018+
10071019
dwTests.test("MultiplyMinByMinusOne") {
10081020
func f(_ x: Int1024) -> Int1024 {
10091021
return x * -1

test/stdlib/Integers.swift.gyb

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,10 @@ extension MockBinaryInteger : BinaryInteger {
148148
return _value.trailingZeroBitCount
149149
}
150150

151+
func isMultiple(of other: MockBinaryInteger<T>) -> Bool {
152+
return _value.isMultiple(of: other._value)
153+
}
154+
151155
static func + (
152156
lhs: MockBinaryInteger<T>, rhs: MockBinaryInteger<T>
153157
) -> MockBinaryInteger<T> {
@@ -560,7 +564,7 @@ tests.test("truncatingIfNeeded") {
560564

561565
tests.test("Parsing/LosslessStringConvertible") {
562566
func _toArray<T: LosslessStringConvertible>(_ text: String) -> [T] {
563-
return text.split(separator: " ").map { T(String($0)) }.flatMap { $0 }
567+
return text.split(separator: " ").map { T(String($0)) }.compactMap { $0 }
564568
}
565569

566570
expectEqualSequence([1, 2, 3], _toArray("1 2 3") as [Int])
@@ -798,6 +802,27 @@ tests.test("DivideMinByMinusOne") {
798802
_ = f(Int.min)
799803
}
800804

805+
tests.test("isMultiple") {
806+
func isMultipleTest<T: FixedWidthInteger>(type: T.Type) {
807+
expectTrue(T.min.isMultiple(of: 2))
808+
expectFalse(T.max.isMultiple(of: 10))
809+
// Test that these do not crash.
810+
expectTrue((0 as T).isMultiple(of: 0))
811+
expectFalse((1 as T).isMultiple(of: 0))
812+
expectTrue(T.min.isMultiple(of: 0 &- 1))
813+
}
814+
isMultipleTest(type: Int.self)
815+
isMultipleTest(type: Int8.self)
816+
isMultipleTest(type: Int16.self)
817+
isMultipleTest(type: Int32.self)
818+
isMultipleTest(type: Int64.self)
819+
isMultipleTest(type: UInt.self)
820+
isMultipleTest(type: UInt8.self)
821+
isMultipleTest(type: UInt16.self)
822+
isMultipleTest(type: UInt32.self)
823+
isMultipleTest(type: UInt64.self)
824+
}
825+
801826
tests.test("MultiplyMinByMinusOne") {
802827
func f(_ x: Int) -> Int {
803828
return x * -1

0 commit comments

Comments
 (0)