Skip to content

Commit 3eaa0b3

Browse files
committed
Additional documentation for division/shift with rounding
Also simplifies check logic for divide with rounding, which is a modest speedup for testing.
1 parent 710ca08 commit 3eaa0b3

File tree

5 files changed

+67
-30
lines changed

5 files changed

+67
-30
lines changed

Sources/IntegerUtilities/CMakeLists.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ See https://swift.org/LICENSE.txt for license information
88
#]]
99

1010
add_library(IntegerUtilities
11-
Divide.swift
11+
DivideWithRounding.swift
1212
GCD.swift
1313
Rotate.swift
1414
Rounding.swift
15-
Shift.swift)
15+
ShiftWithRounding.swift)
1616
set_target_properties(IntegerUtilities PROPERTIES
1717
INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_Swift_MODULE_DIRECTORY})
1818

Sources/IntegerUtilities/Divide.swift renamed to Sources/IntegerUtilities/DivideWithRounding.swift

Lines changed: 50 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,24 @@ extension BinaryInteger {
1414
///
1515
/// The default rounding rule is `.down`, which _is not the same_ as the
1616
/// behavior of the `/` operator from the Swift standard library, but is
17-
/// chosen because it generally produces a more useful remainder. To
18-
/// match the behavior of `/`, use the `.towardZero` rounding mode.
17+
/// chosen because it generally produces a more useful remainder. In
18+
/// particular, when `b` is positive, the remainder is always positive.
19+
/// To match the behavior of `/`, use the `.towardZero` rounding mode.
20+
///
21+
/// Note that the remainder of division is not always representable in an
22+
/// unsigned type if a rounding rule other than `.down`, `.towardZero`, or
23+
/// `.requireExact` is used. For example:
1924
///
20-
/// Be aware that if the type is unsigned, the remainder of the division
21-
/// may not be representable when a non-default rounding mode is used:
22-
/// ```
25+
/// let a: UInt = 5
26+
/// let b: UInt = 3
27+
/// let q = a.divided(by: b, rounding: .up) // 2
28+
/// let r = a - b*q // 5 - 3*2 overflows UInt.
2329
///
24-
/// ```
25-
/// For signed types, the remainder is always representable.
30+
/// For this reason, there is no `remainder(dividingBy:rounding:)`
31+
/// operation defined on `BinaryInteger`. Signed integers do not have
32+
/// this problem, so it is defined on the `SignedInteger` protocol
33+
/// instead, as is an overload of `divided(by:rounding:)` that returns
34+
/// both quotient and remainder.
2635
@inlinable
2736
public func divided(
2837
by other: Self,
@@ -120,6 +129,10 @@ extension BinaryInteger {
120129
// TODO: make this API and make it possible to implement more
121130
// efficiently. Customization point on new/revised integer
122131
// protocol? Shouldn't have to go through .words.
132+
/// The index of the most-significant set bit.
133+
///
134+
/// - Precondition: self is assumed to be non-zero (to be changed
135+
/// if/when this becomes API).
123136
@usableFromInline
124137
internal var _msb: Int {
125138
// a == 0 is never used for division, because this is called
@@ -137,17 +150,21 @@ extension BinaryInteger {
137150

138151
extension SignedInteger {
139152
/// Divides `self` by `other`, rounding the quotient according to `rule`,
140-
/// and returns both the remainder.
153+
/// and returns the remainder.
141154
///
142155
/// The default rounding rule is `.down`, which _is not the same_ as the
143156
/// behavior of the `%` operator from the Swift standard library, but is
144157
/// chosen because it generally produces a more useful remainder. To
145158
/// match the behavior of `%`, use the `.towardZero` rounding mode.
159+
///
160+
/// - Precondition: `other` cannot be zero.
146161
@inlinable
147162
public func remainder(
148163
dividingBy other: Self,
149164
rounding rule: RoundingRule = .down
150165
) -> Self {
166+
// Produce correct remainder for the .min/-1 case, rather than trapping.
167+
if other == -1 { return 0 }
151168
return self.divided(by: other, rounding: rule).remainder
152169
}
153170

@@ -163,7 +180,16 @@ extension SignedInteger {
163180
/// library, this function is a disfavored overload of `divided(by:)`
164181
/// instead of using the name `quotientAndRemainder(dividingBy:)`, which
165182
/// would shadow the standard library operation and change the behavior
166-
/// of any existing use sites.
183+
/// of any existing use sites. To call this method, you must explicitly
184+
/// bind the result to a tuple:
185+
///
186+
/// // This calls BinaryInteger's method, which returns only
187+
/// // the quotient.
188+
/// let result = 5.divided(by: 3, rounding: .up) // 2
189+
///
190+
/// // This calls SignedInteger's method, which returns both
191+
/// // the quotient and remainder.
192+
/// let (q, r) = 5.divided(by: 3, rounding: .up) // (q = 2, r = -1)
167193
@inlinable @inline(__always) @_disfavoredOverload
168194
public func divided(
169195
by other: Self,
@@ -261,21 +287,28 @@ extension SignedInteger {
261287

262288
/// `a = quotient*b + remainder`, with `remainder >= 0`.
263289
///
290+
/// When `a` and `b` are both positive, `quotient` is `a/b` and `remainder`
291+
/// is `a%b`.
292+
///
264293
/// Rounding the quotient so that the remainder is non-negative is called
265294
/// "Euclidean division". This is not a _rounding rule_, as `quotient`
266-
/// cannot be determined just from the unrounded value `a/b`; we need to
267-
/// also know the sign of either `a` or `b` to know which way to round.
268-
/// Because of this, is not present in the `RoundingRule` enum and uses
269-
/// a separate API from the other division operations.
295+
/// cannot be determined from the unrounded value `a/b`; we need to also
296+
/// know the sign of `a` or `b` or `r` to know which way to round. Because
297+
/// of this, is not present in the `RoundingRule` enum and uses a separate
298+
/// API from the other division operations.
270299
///
271300
/// - Parameters:
272301
/// - a: The dividend
273-
/// - b: The divisor, must be non-zero.
302+
/// - b: The divisor
303+
///
304+
/// - Precondition: `b` must be non-zero, and the quotient `a/b` must be
305+
/// representable. In particular, if `T` is a signed fixed-width integer
306+
/// type, then `euclideanDivision(T.min, -1)` will trap, because `-T.min`
307+
/// is not representable.
274308
///
275-
/// - Returns: `(quotient, remainder)`, with `0 <= remainder < b.magnitude`
276-
/// if `quotient` is representable.
309+
/// - Returns: `(quotient, remainder)`, with `0 <= remainder < b.magnitude`.
277310
func euclideanDivision<T>(_ a: T, _ b: T) -> (quotient: T, remainder: T)
278311
where T: SignedInteger
279312
{
280-
a.divided(by: b, rounding: b >= 0 ? .down : .up)
313+
a.divided(by: b, rounding: a >= 0 ? .towardZero : .awayFromZero)
281314
}

Sources/IntegerUtilities/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ The following API are defined for all integer types conforming to `BinaryInteger
77
- The `gcd(_:_:)` free function implements the _Greatest Common Divisor_
88
operation.
99

10-
- The `shifted(right:rounding:)` method implements _bitwise shift with
10+
- The `shifted(rightBy:rounding:)` method implements _bitwise shift with
1111
rounding_.
1212

1313
- The `divided(by:rounding:)` method implements division with specified

Sources/IntegerUtilities/Shift.swift renamed to Sources/IntegerUtilities/ShiftWithRounding.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,12 @@ extension BinaryInteger {
3535
/// 4.shifted(rightBy: 2, rounding: .trap)
3636
///
3737
/// // 5/2 is 2.5, which is not exact, so this traps.
38-
/// 5.shifted(rightBy: 1, rounding: .trap)
38+
/// 5.shifted(rightBy: 1, rounding: .requireExact)
39+
///
40+
/// When `Self(1) << count` is positive, the following are equivalent:
41+
///
42+
/// a.shifted(rightBy: count, rounding: rule)
43+
/// a.divided(by: 1 << count, rounding: rule)
3944
@inlinable
4045
public func shifted<Count: BinaryInteger>(
4146
rightBy count: Count,

Tests/IntegerUtilitiesTests/DivideTests.swift

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,18 @@ import XCTest
1515

1616
final class IntegerUtilitiesDivideTests: XCTestCase {
1717

18-
func divisionRuleHolds<T: FixedWidthInteger>(_ a: T, _ b: T, _ q: T, _ r: T) -> Bool {
18+
func divisionRuleHolds<T: BinaryInteger>(_ a: T, _ b: T, _ q: T, _ r: T) -> Bool {
1919
// Validate division rule holds: a = qb + r (have to be careful about
2020
// computing qb, though, to ensure it does not overflow due to
21-
// rounding of q); therefore compute this as a high-low pair and then
22-
// check against a.
23-
var (hi, lo) = b.multipliedFullWidth(by: q)
24-
var carry: Bool
25-
(lo, carry) = lo.addingReportingOverflow(T.Magnitude(truncatingIfNeeded: r))
26-
(hi, _) = hi.addingReportingOverflow(r >> T.bitWidth &+ (carry ? 1 : 0))
27-
if lo != T.Magnitude(truncatingIfNeeded: a) || hi != a >> T.bitWidth {
21+
// rounding of q; compute it in two pieces, subtracting the first from
22+
// a to avoid intermediate overflow).
23+
let b1 = b >> 1
24+
let b2 = b - b1
25+
let ref = a - q*b1 - q*b2
26+
if r != ref {
2827
XCTFail("""
2928
\(a).divided(by: \(b), rounding: .down) failed the division rule.
30-
qb + r was \(hi):\(lo).
29+
a - qb was \(ref), but r is \(r).
3130
""")
3231
return false
3332
}

0 commit comments

Comments
 (0)