Skip to content

Commit

Permalink
Additional documentation for division/shift with rounding
Browse files Browse the repository at this point in the history
Also simplifies check logic for divide with rounding, which is a modest speedup for testing.
  • Loading branch information
stephentyrone committed Oct 13, 2021
1 parent 710ca08 commit 3eaa0b3
Show file tree
Hide file tree
Showing 5 changed files with 67 additions and 30 deletions.
4 changes: 2 additions & 2 deletions Sources/IntegerUtilities/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ See https://swift.org/LICENSE.txt for license information
#]]

add_library(IntegerUtilities
Divide.swift
DivideWithRounding.swift
GCD.swift
Rotate.swift
Rounding.swift
Shift.swift)
ShiftWithRounding.swift)
set_target_properties(IntegerUtilities PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_Swift_MODULE_DIRECTORY})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,24 @@ extension BinaryInteger {
///
/// The default rounding rule is `.down`, which _is not the same_ as the
/// behavior of the `/` operator from the Swift standard library, but is
/// chosen because it generally produces a more useful remainder. To
/// match the behavior of `/`, use the `.towardZero` rounding mode.
/// chosen because it generally produces a more useful remainder. In
/// particular, when `b` is positive, the remainder is always positive.
/// To match the behavior of `/`, use the `.towardZero` rounding mode.
///
/// Note that the remainder of division is not always representable in an
/// unsigned type if a rounding rule other than `.down`, `.towardZero`, or
/// `.requireExact` is used. For example:
///
/// Be aware that if the type is unsigned, the remainder of the division
/// may not be representable when a non-default rounding mode is used:
/// ```
/// let a: UInt = 5
/// let b: UInt = 3
/// let q = a.divided(by: b, rounding: .up) // 2
/// let r = a - b*q // 5 - 3*2 overflows UInt.
///
/// ```
/// For signed types, the remainder is always representable.
/// For this reason, there is no `remainder(dividingBy:rounding:)`
/// operation defined on `BinaryInteger`. Signed integers do not have
/// this problem, so it is defined on the `SignedInteger` protocol
/// instead, as is an overload of `divided(by:rounding:)` that returns
/// both quotient and remainder.
@inlinable
public func divided(
by other: Self,
Expand Down Expand Up @@ -120,6 +129,10 @@ extension BinaryInteger {
// TODO: make this API and make it possible to implement more
// efficiently. Customization point on new/revised integer
// protocol? Shouldn't have to go through .words.
/// The index of the most-significant set bit.
///
/// - Precondition: self is assumed to be non-zero (to be changed
/// if/when this becomes API).
@usableFromInline
internal var _msb: Int {
// a == 0 is never used for division, because this is called
Expand All @@ -137,17 +150,21 @@ extension BinaryInteger {

extension SignedInteger {
/// Divides `self` by `other`, rounding the quotient according to `rule`,
/// and returns both the remainder.
/// and returns the remainder.
///
/// The default rounding rule is `.down`, which _is not the same_ as the
/// behavior of the `%` operator from the Swift standard library, but is
/// chosen because it generally produces a more useful remainder. To
/// match the behavior of `%`, use the `.towardZero` rounding mode.
///
/// - Precondition: `other` cannot be zero.
@inlinable
public func remainder(
dividingBy other: Self,
rounding rule: RoundingRule = .down
) -> Self {
// Produce correct remainder for the .min/-1 case, rather than trapping.
if other == -1 { return 0 }
return self.divided(by: other, rounding: rule).remainder
}

Expand All @@ -163,7 +180,16 @@ extension SignedInteger {
/// library, this function is a disfavored overload of `divided(by:)`
/// instead of using the name `quotientAndRemainder(dividingBy:)`, which
/// would shadow the standard library operation and change the behavior
/// of any existing use sites.
/// of any existing use sites. To call this method, you must explicitly
/// bind the result to a tuple:
///
/// // This calls BinaryInteger's method, which returns only
/// // the quotient.
/// let result = 5.divided(by: 3, rounding: .up) // 2
///
/// // This calls SignedInteger's method, which returns both
/// // the quotient and remainder.
/// let (q, r) = 5.divided(by: 3, rounding: .up) // (q = 2, r = -1)
@inlinable @inline(__always) @_disfavoredOverload
public func divided(
by other: Self,
Expand Down Expand Up @@ -261,21 +287,28 @@ extension SignedInteger {

/// `a = quotient*b + remainder`, with `remainder >= 0`.
///
/// When `a` and `b` are both positive, `quotient` is `a/b` and `remainder`
/// is `a%b`.
///
/// Rounding the quotient so that the remainder is non-negative is called
/// "Euclidean division". This is not a _rounding rule_, as `quotient`
/// cannot be determined just from the unrounded value `a/b`; we need to
/// also know the sign of either `a` or `b` to know which way to round.
/// Because of this, is not present in the `RoundingRule` enum and uses
/// a separate API from the other division operations.
/// cannot be determined from the unrounded value `a/b`; we need to also
/// know the sign of `a` or `b` or `r` to know which way to round. Because
/// of this, is not present in the `RoundingRule` enum and uses a separate
/// API from the other division operations.
///
/// - Parameters:
/// - a: The dividend
/// - b: The divisor, must be non-zero.
/// - b: The divisor
///
/// - Precondition: `b` must be non-zero, and the quotient `a/b` must be
/// representable. In particular, if `T` is a signed fixed-width integer
/// type, then `euclideanDivision(T.min, -1)` will trap, because `-T.min`
/// is not representable.
///
/// - Returns: `(quotient, remainder)`, with `0 <= remainder < b.magnitude`
/// if `quotient` is representable.
/// - Returns: `(quotient, remainder)`, with `0 <= remainder < b.magnitude`.
func euclideanDivision<T>(_ a: T, _ b: T) -> (quotient: T, remainder: T)
where T: SignedInteger
{
a.divided(by: b, rounding: b >= 0 ? .down : .up)
a.divided(by: b, rounding: a >= 0 ? .towardZero : .awayFromZero)
}
2 changes: 1 addition & 1 deletion Sources/IntegerUtilities/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ The following API are defined for all integer types conforming to `BinaryInteger
- The `gcd(_:_:)` free function implements the _Greatest Common Divisor_
operation.

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

- The `divided(by:rounding:)` method implements division with specified
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,12 @@ extension BinaryInteger {
/// 4.shifted(rightBy: 2, rounding: .trap)
///
/// // 5/2 is 2.5, which is not exact, so this traps.
/// 5.shifted(rightBy: 1, rounding: .trap)
/// 5.shifted(rightBy: 1, rounding: .requireExact)
///
/// When `Self(1) << count` is positive, the following are equivalent:
///
/// a.shifted(rightBy: count, rounding: rule)
/// a.divided(by: 1 << count, rounding: rule)
@inlinable
public func shifted<Count: BinaryInteger>(
rightBy count: Count,
Expand Down
17 changes: 8 additions & 9 deletions Tests/IntegerUtilitiesTests/DivideTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,18 @@ import XCTest

final class IntegerUtilitiesDivideTests: XCTestCase {

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

0 comments on commit 3eaa0b3

Please sign in to comment.