Skip to content

Commit d263859

Browse files
authored
Merge pull request #891 from robmaceachern/binaryinteger-iseven-isodd
[Proposal] Add `isEven`, `isOdd`, and `isMultiple(of:)` to BinaryInteger
2 parents 9d7daa3 + 471da22 commit d263859

File tree

1 file changed

+178
-0
lines changed

1 file changed

+178
-0
lines changed
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
# Adding `isEven`, `isOdd`, `isMultiple` to `BinaryInteger`
2+
3+
* Proposal: [SE-NNNN](NNNN-binaryinteger-iseven-isodd.md)
4+
* Authors: [Robert MacEachern](https://github.com/robmaceachern), [Micah Hansonbrook](https://github.com/SiliconUnicorn)
5+
* Review Manager: TBD
6+
* Status: **Implemented**
7+
* Implementation: [apple/swift#18689](https://github.com/apple/swift/pull/18689)
8+
9+
## Introduction
10+
11+
This proposal adds `var isEven: Bool`, `var isOdd: Bool`, and `func isMultiple(of other: 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 `isMultiple` is a more general function to determine whether an integer is a multiple of another integer.
12+
13+
Swift-evolution thread: [Even and Odd Integers](https://forums.swift.org/t/even-and-odd-integers/11774)
14+
15+
## Motivation
16+
17+
It is sometimes necessary to know whether or not an integer is a multiple of another. The most common case is testing if a value is a multiple of 2 (even and oddness).
18+
19+
**Commonality:** Testing if a value is a multiple of another shows up in a surprising number of contexts including UI code, algorithm implementations (often in the form of assertions), tests, benchmarks, documentation and tutorial/educational code.
20+
21+
Currently, the most common way to test if a value is a multiple is by using the remainder operator (`%`) checking for a remainder of zero: `12 % 2 == 0 // returns true. 12 is a multiple of 2`. Similarly, testing that a value is _not_ a multiple of another is done by checking for a remainder other than zero: `13 % 2 != 0 // returns true. 13 is not a multiple of 2`.
22+
23+
Alternatively, it is also possible to use the bitwise AND operator (`&`) to check the even/oddness of a value: `12 & 1 == 0 // returns true`.
24+
25+
Some examples of testing multiples in code (see more in appendix):
26+
27+
```swift
28+
// UITableView alternating row colour
29+
cell.contentView.backgroundColor = indexPath.row % 2 == 0 ? .gray : .white
30+
31+
// Codable.swift.gyb in apple/swift
32+
guard count % 2 == 0 else { throw DecodingError.dataCorrupted(...) }
33+
34+
// Bool.swift in apple/swift
35+
public static func random<T: RandomNumberGenerator>(using generator: inout T) -> Bool {
36+
return (generator.next() >> 17) & 1 == 0
37+
}
38+
39+
// KeyPath.swift in apple/swift
40+
_sanityCheck(bytes > 0 && bytes % 4 == 0, "capacity must be multiple of 4 bytes")
41+
42+
// ReversedCollection Index.base documentation https://developer.apple.com/documentation/swift/reversedcollection/index/2965437-base
43+
guard let i = reversedNumbers.firstIndex(where: { $0 % 2 == 0 })
44+
```
45+
46+
Determining whether a value is even or odd is a common question across programming languages, at least based on these Stack Overflow questions:
47+
[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
48+
[java - Check whether number is even or odd](https://stackoverflow.com/questions/7342237/check-whether-number-is-even-or-odd) 350,000+ views
49+
[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
50+
51+
Convenience properties or functions equivalent to `isEven` and `isOdd` are available in the standard libraries of many other programming languages, including: [Ruby](https://ruby-doc.org/core-2.2.2/Integer.html#method-i-odd-3F), [Haskell](http://hackage.haskell.org/package/base-4.11.1.0/docs/Prelude.html#v:even), [Clojure](https://clojuredocs.org/clojure.core/odd_q), and according to [RosettaCode](https://www.rosettacode.org/wiki/Even_or_odd): Julia, Racket, Scheme, Smalltalk, Common Lisp.
52+
53+
**Readability:** This proposal significantly improves readability, as expressions read like straightforward English sentences. There is no need to mentally parse and understand non-obvious operator precedence rules (`%` has higher precedence than `==`).
54+
55+
The `isEven` and `isOdd` properties are also fewer characters wide than the remainder approach (maximum 7 characters for `.isEven` vs 9 for ` % 2 == 0`) which saves horizontal space while being clearer in intent.
56+
57+
```swift
58+
// UITableView alternating row colour
59+
cell.contentView.backgroundColor = indexPath.row.isEven ? .gray : .white
60+
61+
// Codable.swift.gyb in apple/swift
62+
guard count.isEven else { throw DecodingError.dataCorrupted(...) }
63+
64+
// Bool.swift in apple/swift
65+
public static func random<T: RandomNumberGenerator>(using generator: inout T) -> Bool {
66+
return (generator.next() >> 17).isEven
67+
}
68+
69+
// KeyPath.swift in apple/swift
70+
_sanityCheck(bytes > 0 && bytes.isMultiple(of: 4), "capacity must be multiple of 4 bytes")
71+
```
72+
73+
**Discoverability:** IDEs will be able to suggest `isEven`, `isOdd`, and `isMultiple` as part of autocomplete on integer types which will aid discoverability. It will also be familiar to users coming from languages that also support functionality similar to `isEven` and `isOdd`.
74+
75+
**Trivially composable:** It would be relatively easy to reproduce the proposed functionality 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.
76+
77+
Testing the parity of integers is also relatively common in sample code and educational usage. In this context, it’s usually not appropriate for an author to introduce this functionality (unless they are teaching extensions!) in order to avoid distracting from the main task at hand (e.g. filter, map, etc). It may also be the same situation for authoring test code: it'd be used if it existed but it's not worth the overhead of defining it manually.
78+
79+
This functionality will also eliminate the need to use the remainder operator or bitwise AND when querying the divisibility of an integer.
80+
81+
**Correctness:** It isn't [uncommon](https://github.com/apple/swift/blob/master/stdlib/public/core/RangeReplaceableCollection.swift#L1090) to see tests for oddness written as `value % 2 == 1` in Swift, but this is incorrect for negative odd values. The semantics of the `%` operator vary between programming languages, such as Ruby and Python, which can be surprising.
82+
83+
```
84+
// Swift:
85+
7 % 2 == 1 // true
86+
-7 % 2 == 1 // false. -7 % 2 evaluates to -1
87+
88+
// Ruby and Python
89+
7 % 2 == 1 // true
90+
-7 % 2 == 1 // true
91+
```
92+
93+
The `%` operator will also trap when the righthand side is zero. The proposed solution does not.
94+
95+
There is also a minor correctness risk in misinterpreting something like `value % 2 == 0`, particularly when used in a more complex statement, when compared to `value.isEven`, e.g. `bytes > 0 && bytes % 4 == 0`.
96+
97+
**Performance:** It's _possible_ that `isMultiple` could be implemented in a more performant way than `% divisor == 0` for more complex types, such as a BigInteger/BigNum type.
98+
99+
The addition of `isEven` and `isOdd` likely won’t have a major positive impact on performance but it should not introduce any additional overhead thanks to `@_transparent`.
100+
101+
## Proposed solution
102+
103+
Add two computed properties, `isEven` and `isOdd`, and a function `isMultiple` to the `BinaryInteger` protocol.
104+
105+
```swift
106+
// Integers.swift.gyb
107+
// On protocol BinaryInteger
108+
109+
@_transparent
110+
public var isEven: Bool { return _lowWord % 2 == 0 }
111+
112+
@_transparent
113+
public var isOdd: Bool { return !isEven }
114+
115+
func isMultiple(of other: Self) -> Bool
116+
```
117+
118+
## Detailed design
119+
120+
N/A
121+
122+
## Source compatibility
123+
124+
This is strictly additive.
125+
126+
## Effect on ABI stability
127+
128+
N/A
129+
130+
## Effect on API resilience
131+
132+
N/A
133+
134+
## Alternatives considered
135+
136+
### `isDivisible` instead of `isMultiple`
137+
138+
The original discussions during the pitch phase where related to an `isDivisible(by:)` alternative to `isMultiple`. [Issues](https://forums.swift.org/t/even-and-odd-integers/11774/83) related to divisibility and division by zero were discussed and `isMultiple` was proposed as a solution that 1) avoids trapping on zero, and 2) avoids confusion where a value that is _divisible_ by zero would not be _dividable_ in Swift. e.g.
139+
140+
```
141+
let y = 0
142+
if 10.isDivisible(by: y) {
143+
let val = 10 / y // traps
144+
}
145+
```
146+
147+
### Only `isEven/isOdd` or only `isMultiple`.
148+
149+
During the pitch phase there were discussions about including only one of `isEven/isOdd` or `isMultiple` in the proposal.
150+
151+
On the one hand there were concerns that `isEven/isOdd` would not provide enough utility to justify inclusion into the standard library and that `isMultiple` was preferable as it was more general. `isEven/isOdd` are also trivial inverses of each other which Swift, as a rule, doesn't include in the standard library.
152+
153+
On the other hand there was some unscientific analysis that indicated that even/oddness accounted for 60-80% of the operations in which the result of the remainder operator was compared against zero. This lent some support to including `isEven/isOdd` over `isMultiple`. There is also more precedence in other languages for including `isEven/isOdd` over `isMultiple`.
154+
155+
The authors decided that both were worthy of including in the proposal. Odd and even numbers have had special [shorthand labels](http://mathforum.org/library/drmath/view/65413.html) for thousands of years and are used frequently enough to justify the small additional weight `isEven/isOdd` would add to the standard library. `isMultiple` will greatly improve clarity and readability for arbitrary divisibility checks and also avoid potentially surprising `%` operator semantics with negative values.
156+
157+
## Appendix
158+
159+
### Other example uses in code (and beyond)
160+
161+
* `% 2 == 0` appears 63 times in the [Apple/Swift](https://github.com/apple/swift) repository.
162+
* [Initializing a cryptographic cipher](https://github.com/krzyzanowskim/CryptoSwift/blob/31efdd85ceb4190ee358a0516c6e82d8fd7b9377/Sources/CryptoSwift/Rabbit.swift#L89)
163+
* Colouring a checker/chess board and [determining piece colour](https://github.com/nvzqz/Sage/blob/dec5edd97ba45d46bf94bc2e264d3ce2be6404ad/Sources/Piece.swift#L276)
164+
* Multiple occurrences in cpp Boost library. [Example: handleResizingVertex45](https://www.boost.org/doc/libs/1_62_0/boost/polygon/polygon_45_set_data.hpp)
165+
* Alternating bar colours in a chart
166+
* [VoronoiFilter implementation](https://github.com/BradLarson/GPUImage/blob/master/framework/Source/GPUImageJFAVoronoiFilter.m#L428) and [PoissonBlendFilter](https://github.com/BradLarson/GPUImage/blob/167b0389bc6e9dc4bb0121550f91d8d5d6412c53/framework/Source/GPUImagePoissonBlendFilter.m#L142)
167+
* [Image blurring library](https://github.com/nicklockwood/FXBlurView/blob/master/FXBlurView/FXBlurView.m#L58)
168+
* UPC check digit calculation [(spec)](http://www.gs1.org/how-calculate-check-digit-manually) (values in odd digit indices are multiplied by 3)
169+
* [Precondition check in sqlite function](https://github.com/sqlcipher/sqlcipher/blob/c6f709fca81c910ba133aaf6330c28e01ccfe5f8/src/crypto_impl.c#L1296)
170+
* [Alternating UITableView cell background style](https://github.com/alloy/HockeySDK-CocoaPods/blob/master/Pods/HockeySDK/Classes/BITFeedbackListViewController.m#L502). NSTableView [has built in support](https://developer.apple.com/documentation/appkit/nstableview/1533967-usesalternatingrowbackgroundcolo?language=objc) for this.
171+
* [Barcode reading](https://github.com/TheLevelUp/ZXingObjC/blob/d952cc02beb948ab49832661528c5e3e4953885e/ZXingObjC/oned/rss/expanded/ZXRSSExpandedReader.m#L449)
172+
* [CSS row and column styling](https://www.w3.org/Style/Examples/007/evenodd.en.html) with `nth-child(even)` and `nth-child(odd)` (h/t @brentdax)
173+
* Various test code
174+
175+
Some _really_ real-world examples:
176+
* [Printing even or odd pages only](https://i.stack.imgur.com/TE4Xn.png)
177+
* New [Hearthstone even/odd cards](https://blizzardwatch.com/2018/03/16/hearthstones-new-even-odd-cards-going-shake-meta/)
178+
* A variety of [even-odd rationing](https://en.m.wikipedia.org/wiki/Odd–even_rationing) rules: [parking rules](https://www.icgov.org/city-government/departments-and-divisions/transportation-services/parking/oddeven-parking), [water usage restrictions](https://vancouver.ca/home-property-development/understanding-watering-restrictions.aspx), [driving restrictions](https://auto.ndtv.com/news/odd-even-in-delhi-5-things-you-need-to-know-1773720)

0 commit comments

Comments
 (0)