Skip to content

Commit ed9fc63

Browse files
committed
Draft implementation of integer midpoint.
1 parent b5adc8c commit ed9fc63

File tree

2 files changed

+147
-0
lines changed

2 files changed

+147
-0
lines changed
+108
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
//===--- Midpoint.swift ---------------------------------------*- swift -*-===//
2+
//
3+
// This source file is part of the Swift Numerics open source project
4+
//
5+
// Copyright (c) 2024 Apple Inc. and the Swift Numerics project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
//
10+
//===----------------------------------------------------------------------===//
11+
12+
/// The average of `a` and `b`, rounded to an integer according to `rule`.
13+
///
14+
/// Unlike commonly seen expressions such as `(a+b)/2` or `(a+b) >> 1` or
15+
/// `a + (b-a)/2` (all of which may overflow for fixed-width integers),
16+
/// this function never overflows, and the result is guaranteed to be
17+
/// representable in the result type.
18+
///
19+
/// The default rounding rule is `.down`, which matches the behavior of
20+
/// `(a + b) >> 1` when that expression does not overflow. Rounding
21+
/// `.towardZero` matches the behavior of `(a + b)/2` when that expression
22+
/// does not overflow. All other rounding modes are supported.
23+
///
24+
/// Rounding `.down` is generally most efficient; if you do not have a
25+
/// reason to chose a specific other rounding rule, you should use the
26+
/// default.
27+
@inlinable
28+
public func midpoint<T: BinaryInteger>(
29+
_ a: T,
30+
_ b: T,
31+
rounding rule: RoundingRule = .down
32+
) -> T {
33+
// Isolate bits in a + b with weight 2, and those with weight 1.
34+
let twos = a & b
35+
let ones = a ^ b
36+
let floor = twos + ones >> 1
37+
let frac = ones & 1
38+
switch rule {
39+
case .down:
40+
return floor
41+
case .up:
42+
return floor + frac
43+
case .towardZero:
44+
return floor + (floor < 0 ? frac : 0)
45+
case .toNearestOrAwayFromZero:
46+
fallthrough
47+
case .awayFromZero:
48+
return floor + (floor >= 0 ? frac : 0)
49+
case .toNearestOrEven:
50+
return floor + (floor & frac)
51+
case .toOdd:
52+
return floor + (~floor & frac)
53+
case .stochastically:
54+
return floor + (Bool.random() ? frac : 0)
55+
case .requireExact:
56+
precondition(frac == 0)
57+
return floor
58+
}
59+
}
60+
61+
/// The average of `a` and `b`, rounded to an integer according to `rule`.
62+
///
63+
/// Unlike commonly seen expressions such as `(a+b)/2` or `(a+b) >> 1` or
64+
/// `a + (b-a)/2` (all of which may overflow), this function never overflows,
65+
/// and the result is guaranteed to be representable in the result type.
66+
///
67+
/// The default rounding rule is `.down`, which matches the behavior of
68+
/// `(a + b) >> 1` when that expression does not overflow. Rounding
69+
/// `.towardZero` matches the behavior of `(a + b)/2` when that expression
70+
/// does not overflow. All other rounding modes are supported.
71+
///
72+
/// Rounding `.down` is generally most efficient; if you do not have a
73+
/// reason to chose a specific other rounding rule, you should use the
74+
/// default.
75+
@inlinable
76+
public func midpoint<T: FixedWidthInteger>(
77+
_ a: T,
78+
_ b: T,
79+
rounding rule: RoundingRule = .down
80+
) -> T {
81+
// Isolate bits in a + b with weight 2, and those with weight 1
82+
let twos = a & b
83+
let ones = a ^ b
84+
let floor = twos &+ ones >> 1
85+
let frac = ones & 1
86+
switch rule {
87+
case .down:
88+
return floor
89+
case .up:
90+
return floor &+ frac
91+
case .towardZero:
92+
return floor &+ (floor < 0 ? frac : 0)
93+
case .toNearestOrAwayFromZero:
94+
fallthrough
95+
case .awayFromZero:
96+
return floor &+ (floor >= 0 ? frac : 0)
97+
case .toNearestOrEven:
98+
return floor &+ (floor & frac)
99+
case .toOdd:
100+
return floor &+ (~floor & frac)
101+
case .stochastically:
102+
return floor &+ (Bool.random() ? frac : 0)
103+
case .requireExact:
104+
precondition(frac == 0)
105+
return floor
106+
}
107+
}
108+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
//===--- MidpointTests.swift ----------------------------------*- swift -*-===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2021 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import IntegerUtilities
14+
import XCTest
15+
16+
final class IntegerUtilitiesMidpointTests: XCTestCase {
17+
func testMidpoint() {
18+
for rule in [
19+
RoundingRule.down,
20+
.up,
21+
.towardZero,
22+
.awayFromZero,
23+
.toNearestOrEven,
24+
.toNearestOrAwayFromZero,
25+
.toOdd
26+
] {
27+
for a in -128 ... 127 {
28+
for b in -128 ... 127 {
29+
let ref = (a + b).shifted(rightBy: 1, rounding: rule)
30+
let tst = midpoint(Int8(a), Int8(b), rounding: rule)
31+
if ref != tst {
32+
print(rule, a, b, ref, tst, separator: "\t")
33+
return
34+
}
35+
}
36+
}
37+
}
38+
}
39+
}

0 commit comments

Comments
 (0)