Skip to content

Commit 3d871d9

Browse files
committed
Updates to proposal
1 parent 5e324eb commit 3d871d9

File tree

2 files changed

+133
-121
lines changed

2 files changed

+133
-121
lines changed
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
# Adding `isEven`, `isOdd`, `isDivisible` to `BinaryInteger`
2+
3+
* Proposal: [SE-NNNN](NNNN-binaryinteger-iseven-isodd.md)
4+
* Authors: [Robert MacEachern](https://robmaceachern.com), [SiliconUnicorn](https://forums.swift.org/u/siliconunicorn/summary)
5+
* Review Manager: TBD
6+
* Status: **Awaiting implementation**
7+
8+
## Introduction
9+
10+
We propose adding `var isEven: Bool`, `var isOdd: Bool`, and `func isDivisible(by denominator: Self) -> Bool` to the `BinaryInteger` protocol. `isEven` and `isOdd` are convenience properties for querying the [parity](https://en.wikipedia.org/wiki/Parity_(mathematics)) of the integer and `isDivisible` is a more general function to determine the divisibility of an integer by an arbitrary denominator.
11+
12+
Swift-evolution thread: [Even and Odd Integers](https://forums.swift.org/t/even-and-odd-integers/11774)
13+
14+
## Motivation
15+
16+
It is sometimes necessary to know whether or not an integer is divisible by a particular value. The most common case is testing for divisibility by 2, even and oddness.
17+
18+
**Commonality:** Testing divisibility shows up in a surprising number of contexts including UI code, algorithm implementations (often in the form of assertions), tests, benchmarks, documentation and tutorial code.
19+
20+
The most common way to test a value for divisibility is by using the remainder operator (`%`) checking for a remainder of zero: `12 % 2 == 0 // returns true. 12 is divisible by 2`. Similarly, testing for indivisibility is done by checking for a remainder other than zero: `13 % 2 != 0 // returns true. 13 is not divisible by 2`.
21+
22+
Alternatively, it is also possible to use the bitwise AND operator (`&`) to check the even/oddness of a value: `12 & 1 == 0 // returns true`.
23+
24+
```swift
25+
// Gray background for even rows, white for odd.
26+
view.backgroundColor = indexPath.row % 2 == 0 ? .gray : .white
27+
28+
// Codable.swift.gyb in apple/swift
29+
guard count % 2 == 0 else { throw DecodingError.dataCorrupted(...) }
30+
31+
// Bool.swift in apple/swift
32+
public static func random<T: RandomNumberGenerator>(using generator: inout T) -> Bool {
33+
return (generator.next() >> 17) & 1 == 0
34+
}
35+
36+
// KeyPath.swift in apple/swift
37+
_sanityCheck(bytes > 0 && bytes % 4 == 0, "capacity must be multiple of 4 bytes")
38+
39+
// ReversedCollection Index.base documentation https://developer.apple.com/documentation/swift/reversedcollection/index/2965437-base
40+
guard let i = reversedNumbers.firstIndex(where: { $0 % 2 == 0 })
41+
```
42+
43+
**Readability:** This proposal significantly improves readability. There is no need to understand operator precedence rules (`%` has higher precedence than `==`) which are non-obvious.
44+
45+
The properties are also fewer characters wide than the modulus approach (maximum 7 characters for `.isEven` vs 9 for ` % 2 == 0`) which saves horizontal space while being clearer in intent.
46+
47+
```swift
48+
// Gray background for even rows, white for odd.
49+
view.backgroundColor = indexPath.row.isEven ? .gray : .white
50+
51+
// Codable.swift.gyb in apple/swift
52+
guard count.isEven else { throw DecodingError.dataCorrupted(...) }
53+
54+
// Bool.swift in apple/swift
55+
public static func random<T: RandomNumberGenerator>(using generator: inout T) -> Bool {
56+
return (generator.next() >> 17).isEven
57+
}
58+
59+
// KeyPath.swift in apple/swift
60+
_sanityCheck(bytes > 0 && bytes.isDivisible(by: 4), "capacity must be multiple of 4 bytes")
61+
```
62+
63+
**Discoverability:** Determining whether a value is even or odd is a common question across programming languages, at least based on these Stack Overflow questions:
64+
[c - How do I check if an integer is even or odd?](https://stackoverflow.com/questions/160930/how-do-i-check-if-an-integer-is-even-or-odd) 300,000+ views
65+
[java - Check whether number is even or odd](https://stackoverflow.com/questions/7342237/check-whether-number-is-even-or-odd) 350,000+ views
66+
[Check if a number is odd or even in python](https://stackoverflow.com/questions/21837208/check-if-a-number-is-odd-or-even-in-python) 140,000+ views
67+
68+
IDEs will be able to suggest `.isEven` and `.isOdd` as part of autocomplete which will aid discoverability.
69+
70+
**Consistency:** It would be relatively easy to reproduce the properties in user code but there would be benefits to having a standard implementation. It may not be obvious to some users exactly which protocol these properties belong on (`Int`?, `SignedInteger`?, `FixedWidthInteger`?, `BinaryInteger`?). This inconsistency can be seen in a [popular Swift utility library](https://github.com/SwifterSwift/SwifterSwift/blob/master/Sources/Extensions/SwiftStdlib/SignedIntegerExtensions.swift#L28) which defines `isEven` and `isOdd` on `SignedInteger` which results in the properties being inaccessible for unsigned integers.
71+
72+
These properties will also eliminate the need to use remainder 2 and bitwise AND 1 to determine parity.
73+
74+
Adding `isEven` and `isOdd` is also consistent with the `.isEmpty` utility property, which is a convenience for `.count == 0`.
75+
```swift
76+
if array.count == 0 { ... }
77+
if array.isEmpty { ... }
78+
79+
if value % 2 == 0 { ... }
80+
if value.isEven { ... }
81+
```
82+
83+
**Correctness:** There is a minor correctness risk in misinterpreting something like `value % 2 == 0`, particularly when used in a more complex statement, when compared to `value.isEven`.
84+
85+
**Performance:** This proposal likely won’t have a major positive impact on performance but it should not introduce any additional overhead thanks to `@inlineable`.
86+
87+
## Proposed solution
88+
89+
Add two computed properties, `isEven` and `isOdd`, and a function `isDivisible` to the `BinaryInteger` protocol.
90+
91+
```swift
92+
@inlinable
93+
/// A Boolean value indicating whether this value is even.
94+
///
95+
/// An integer is even if it is divisible by two.
96+
public var isEven: Bool {
97+
return isDivisible(by: 2)
98+
}
99+
100+
@inlinable
101+
/// A Boolean value indicating whether this value is odd.
102+
///
103+
/// An integer is odd if it is not divisible by two.
104+
public var isOdd: Bool {
105+
return !isDivisible(by: 2)
106+
}
107+
108+
@inlinable
109+
///
110+
func isDivisible(by denominator: Self) -> Bool {
111+
return self % denominator == 0
112+
}
113+
```
114+
115+
## Detailed design
116+
117+
N/A
118+
119+
## Source compatibility
120+
121+
This is strictly additive.
122+
123+
## Effect on ABI stability
124+
125+
N/A
126+
127+
## Effect on API resilience
128+
129+
N/A
130+
131+
## Alternatives considered
132+
133+
N/A

proposals/nnnn-binaryinteger-iseven-isodd.md

Lines changed: 0 additions & 121 deletions
This file was deleted.

0 commit comments

Comments
 (0)