Skip to content

Commit

Permalink
Adopt new rounding rules for midpoint
Browse files Browse the repository at this point in the history
Also corrects a typo and makes the deprecated toNearestOrAwayFromZero rule public for migration purposes, and removes the BinaryInteger.midpoint function in favor of the FixedWidthInteger overload. I'm open to reinstating the BI implementation in the future, but let's start with just FWI.
  • Loading branch information
stephentyrone committed Jul 22, 2024
1 parent 64c0625 commit 53efe07
Show file tree
Hide file tree
Showing 3 changed files with 12 additions and 53 deletions.
58 changes: 7 additions & 51 deletions Sources/IntegerUtilities/Midpoint.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,55 +9,6 @@
//
//===----------------------------------------------------------------------===//

/// The average of `a` and `b`, rounded to an integer according to `rule`.
///
/// Unlike commonly seen expressions such as `(a+b)/2` or `(a+b) >> 1` or
/// `a + (b-a)/2` (all of which may overflow for fixed-width integers),
/// this function never overflows, and the result is guaranteed to be
/// representable in the result type.
///
/// The default rounding rule is `.down`, which matches the behavior of
/// `(a + b) >> 1` when that expression does not overflow. Rounding
/// `.towardZero` matches the behavior of `(a + b)/2` when that expression
/// does not overflow. All other rounding modes are supported.
///
/// Rounding `.down` is generally most efficient; if you do not have a
/// reason to chose a specific other rounding rule, you should use the
/// default.
@inlinable
public func midpoint<T: BinaryInteger>(
_ a: T,
_ b: T,
rounding rule: RoundingRule = .down
) -> T {
// Isolate bits in a + b with weight 2, and those with weight 1.
let twos = a & b
let ones = a ^ b
let floor = twos + ones >> 1
let frac = ones & 1
switch rule {
case .down:
return floor
case .up:
return floor + frac
case .towardZero:
return floor + (floor < 0 ? frac : 0)
case .toNearestOrAwayFromZero:
fallthrough
case .awayFromZero:
return floor + (floor >= 0 ? frac : 0)
case .toNearestOrEven:
return floor + (floor & frac)
case .toOdd:
return floor + (~floor & frac)
case .stochastically:
return floor + (Bool.random() ? frac : 0)
case .requireExact:
precondition(frac == 0)
return floor
}
}

/// The average of `a` and `b`, rounded to an integer according to `rule`.
///
/// Unlike commonly seen expressions such as `(a+b)/2` or `(a+b) >> 1` or
Expand All @@ -84,13 +35,19 @@ public func midpoint<T: FixedWidthInteger>(
let floor = twos &+ ones >> 1
let frac = ones & 1
switch rule {
case .toNearestOrDown:
fallthrough
case .down:
return floor
case .toNearestOrUp:
fallthrough
case .up:
return floor &+ frac
case .toNearestOrZero:
fallthrough
case .towardZero:
return floor &+ (floor < 0 ? frac : 0)
case .toNearestOrAwayFromZero:
case .toNearestOrAway:
fallthrough
case .awayFromZero:
return floor &+ (floor >= 0 ? frac : 0)
Expand All @@ -105,4 +62,3 @@ public func midpoint<T: FixedWidthInteger>(
return floor
}
}

2 changes: 1 addition & 1 deletion Sources/IntegerUtilities/RoundingRule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -256,5 +256,5 @@ extension RoundingRule {
/// > Deprecated: Use `.toNearestOrAway` instead.
@inlinable
@available(*, deprecated, renamed: "toNearestOrAway")
static var toNearestOrAwayFromZero: Self { .toNearestOrAway }
public static var toNearestOrAwayFromZero: Self { .toNearestOrAway }
}
5 changes: 4 additions & 1 deletion Tests/IntegerUtilitiesTests/MidpointTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,11 @@ final class IntegerUtilitiesMidpointTests: XCTestCase {
.up,
.towardZero,
.awayFromZero,
.toNearestOrDown,
.toNearestOrUp,
.toNearestOrZero,
.toNearestOrAway,
.toNearestOrEven,
.toNearestOrAwayFromZero,
.toOdd
] {
for a in -128 ... 127 {
Expand Down

0 comments on commit 53efe07

Please sign in to comment.