Skip to content

Commit 31fe14d

Browse files
committed
Remove stochastic rounding mode
It's a useful operation, but it deserves its own API to allow the use of a custom random source. Also, the existing API blocks some constant-folding operations because the call to the system random source is an opaque optimization barrier, so removing it allows us to produce better codegen for the other rounding modes. We're also bumping required Swift version to 5.9, and removing the executable test targets for log/log1p, as they're really only useful for development of those algorithms.
1 parent c327d04 commit 31fe14d

File tree

9 files changed

+22
-510
lines changed

9 files changed

+22
-510
lines changed

Package.swift

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
// swift-tools-version:5.5
1+
// swift-tools-version:5.9
22
//===--- Package.swift ----------------------------------------*- swift -*-===//
33
//
44
// This source file is part of the Swift Numerics open source project
55
//
6-
// Copyright (c) 2019-2021 Apple Inc. and the Swift Numerics project authors
6+
// Copyright (c) 2019-2025 Apple Inc. and the Swift Numerics project authors
77
// Licensed under Apache License v2.0 with Runtime Library Exception
88
//
99
// See https://swift.org/LICENSE.txt for license information
@@ -81,19 +81,6 @@ let package = Package(
8181
name: "RealTests",
8282
dependencies: ["_TestSupport"],
8383
exclude: ["CMakeLists.txt"]
84-
),
85-
86-
// MARK: - Test executables
87-
.executableTarget(
88-
name: "ComplexLog",
89-
dependencies: ["Numerics", "_TestSupport"],
90-
path: "Tests/Executable/ComplexLog"
91-
),
92-
93-
.executableTarget(
94-
name: "ComplexLog1p",
95-
dependencies: ["Numerics", "_TestSupport"],
96-
path: "Tests/Executable/ComplexLog1p"
9784
)
9885
]
9986
)

Sources/IntegerUtilities/DivideWithRounding.swift

Lines changed: 1 addition & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -116,22 +116,6 @@ extension BinaryInteger {
116116
// If q is already odd, we have the correct result.
117117
if q._lowWord & 1 == 1 { return q }
118118

119-
case .stochastically:
120-
let bmag = other.magnitude
121-
let rmag = r.magnitude
122-
var bhi: UInt64
123-
var rhi: UInt64
124-
if other.magnitude <= UInt64.max {
125-
bhi = UInt64(bmag)
126-
rhi = UInt64(rmag)
127-
} else {
128-
let shift = bmag._msb - 63
129-
bhi = UInt64(truncatingIfNeeded: bmag >> shift)
130-
rhi = UInt64(truncatingIfNeeded: rmag >> shift)
131-
}
132-
let (sum, car) = rhi.addingReportingOverflow(.random(in: 0 ..< bhi))
133-
if sum < bhi && !car { return q }
134-
135119
case .requireExact:
136120
preconditionFailure("Division was not exact.")
137121
}
@@ -299,22 +283,6 @@ extension SignedInteger {
299283
// If q is already odd, we have the correct result.
300284
if q._lowWord & 1 == 1 { return (q, r) }
301285

302-
case .stochastically:
303-
let bmag = other.magnitude
304-
let rmag = r.magnitude
305-
var bhi: UInt64
306-
var rhi: UInt64
307-
if other.magnitude <= UInt64.max {
308-
bhi = UInt64(bmag)
309-
rhi = UInt64(rmag)
310-
} else {
311-
let shift = bmag._msb - 63
312-
bhi = UInt64(truncatingIfNeeded: bmag >> shift)
313-
rhi = UInt64(truncatingIfNeeded: rmag >> shift)
314-
}
315-
let (sum, car) = rhi.addingReportingOverflow(.random(in: 0 ..< bhi))
316-
if sum < bhi && !car { return (q, r) }
317-
318286
case .requireExact:
319287
preconditionFailure("Division was not exact.")
320288
}
@@ -347,7 +315,7 @@ extension SignedInteger {
347315
/// is not representable.
348316
///
349317
/// - Returns: `(quotient, remainder)`, with `0 <= remainder < b.magnitude`.
350-
func euclideanDivision<T>(_ a: T, _ b: T) -> (quotient: T, remainder: T)
318+
public func euclideanDivision<T>(_ a: T, _ b: T) -> (quotient: T, remainder: T)
351319
where T: SignedInteger
352320
{
353321
a.divided(by: b, rounding: a >= 0 ? .towardZero : .awayFromZero)

Sources/IntegerUtilities/RoundingRule.swift

Lines changed: 16 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,13 @@
22
//
33
// This source file is part of the Swift Numerics open source project
44
//
5-
// Copyright (c) 2021-2024 Apple Inc. and the Swift Numerics project authors
5+
// Copyright (c) 2021-2025 Apple Inc. and the Swift Numerics project authors
66
// Licensed under Apache License v2.0 with Runtime Library Exception
77
//
88
// See https://swift.org/LICENSE.txt for license information
99
//
1010
//===----------------------------------------------------------------------===//
1111

12-
// TODO: it's unfortunate that we can't specify a custom random source
13-
// for the stochastic rounding rule, but I don't see a nice way to have
14-
// that share the API with the other rounding rules, because we'd then
15-
// have to take either the rule in-out or have an additional RNG/state
16-
// parameter. The same problem applies to rounding with dithering and
17-
// any other stateful rounding method. We should consider adding a
18-
// stateful rounding API down the road to support those use cases.
19-
2012
/// A rule that defines how to select one of the two representable results
2113
/// closest to a given value.
2214
///
@@ -59,22 +51,22 @@
5951
/// 2.0 | 2 | 2 | 2 | 2 | 2 |
6052
/// -------+----------+----------+----------+----------+----------+
6153
///
62-
/// Specialized rounding rules
54+
/// Specialized rounding rules
6355
///
64-
/// value | toOdd | stochastically | requireExact |
65-
/// =======+==============+=======================+================+
66-
/// -1.5 | -1 | 50% -2, 50% -1 | trap |
67-
/// -------+--------------+-----------------------+----------------+
68-
/// -0.5 | -1 | 50% -1, 50% 0 | trap |
69-
/// -------+--------------+-----------------------+----------------+
70-
/// 0.5 | 1 | 50% 0, 50% 1 | trap |
71-
/// -------+--------------+-----------------------+----------------+
72-
/// 0.7 | 1 | 30% 0, 70% 1 | trap |
73-
/// -------+--------------+-----------------------+----------------+
74-
/// 1.2 | 1 | 80% 1, 20% 2 | trap |
75-
/// -------+--------------+-----------------------+----------------+
76-
/// 2.0 | 2 | 2 | 2 |
77-
/// -------+--------------+-----------------------+----------------+
56+
/// value | toOdd | requireExact |
57+
/// =======+==============+================+
58+
/// -1.5 | -1 | trap |
59+
/// -------+--------------+----------------+
60+
/// -0.5 | -1 | trap |
61+
/// -------+--------------+----------------+
62+
/// 0.5 | 1 | trap |
63+
/// -------+--------------+----------------+
64+
/// 0.7 | 1 | trap |
65+
/// -------+--------------+----------------+
66+
/// 1.2 | 1 | trap |
67+
/// -------+--------------+----------------+
68+
/// 2.0 | 2 | 2 |
69+
/// -------+--------------+----------------+
7870
/// ```
7971
public enum RoundingRule {
8072
/// Produces the closest representable value that is less than or equal
@@ -200,45 +192,6 @@ public enum RoundingRule {
200192
/// because 5/2 = 2.5 is equally close to 2 and 3, and 2 is even.
201193
case toNearestOrEven
202194

203-
/// Adds a uniform random value from [0, d) to the value being rounded,
204-
/// where d is the distance between the two closest representable values,
205-
/// then rounds the sum downwards.
206-
///
207-
/// Unlike all the other rounding modes, this mode is _not deterministic_;
208-
/// repeated calls to rounding operations with this mode will generally
209-
/// produce different results. There is a tradeoff implicit in using this
210-
/// mode: you can sacrifice _reproducible_ results to get _more accurate_
211-
/// results in aggregate. For a contrived but illustrative example, consider
212-
/// the following:
213-
/// ```
214-
/// let data = Array(repeating: 1, count: 100)
215-
/// let result = data.reduce(0) {
216-
/// $0 + $1.divided(by: 3, rounding: rule)
217-
/// }
218-
/// ```
219-
/// because 1/3 is always the same value between 0 and 1, any
220-
/// deterministic rounding rule must produce either 0 or 100 for
221-
/// this computation. But rounding `stochastically` will
222-
/// produce a value close to 33. The _error_ of the computation
223-
/// is smaller, but the result will now change between runs of the
224-
/// program.
225-
///
226-
/// For this simple case a better solution would be to add the
227-
/// values first, and then divide. This gives a result that is both
228-
/// reproducible _and_ accurate:
229-
/// ```
230-
/// let result = data.reduce(0, +)/3
231-
/// ```
232-
/// but this isn't always possible in more sophisticated scenarios,
233-
/// and in those cases this rounding rule may be useful.
234-
///
235-
/// Examples:
236-
/// - `(-4).divided(by: 3, rounding: .stochastically)`
237-
/// will be –1 with probability 2/3 and –2 with probability 1/3.
238-
/// - `5.shifted(rightBy: 1, rounding: .stochastically)`
239-
/// will be 2 with probability 1/2 and 3 with probability 1/2.
240-
case stochastically
241-
242195
/// If the value being rounded is representable, that value is returned.
243196
/// Otherwise, a precondition failure occurs.
244197
///
@@ -304,15 +257,6 @@ extension FloatingPoint {
304257
// which way that rounds, then select the other value.
305258
let even = (trunc + one/2).rounded(.toNearestOrEven)
306259
return trunc == even ? trunc + one : trunc
307-
case .stochastically:
308-
let trunc = rounded(.towardZero)
309-
if trunc == self { return trunc }
310-
// We have eliminated all large values at this point; add dither in
311-
// ±[0,1) and then truncate.
312-
let bits = Swift.min(-Self.ulpOfOne.exponent, 32)
313-
let random = Self(UInt32.random(in: 0 ... (1 << bits &- 1)))
314-
let dither = Self(sign: sign, exponent: -bits, significand: random)
315-
return (self + dither).rounded(.towardZero)
316260
case .requireExact:
317261
let trunc = rounded(.towardZero)
318262
precondition(isInfinite || trunc == self, "\(self) is not an exact integer.")

Sources/IntegerUtilities/ShiftWithRounding.swift

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -130,27 +130,6 @@ extension BinaryInteger {
130130
return floor + Self((round + lost) >> count)
131131
case .toOdd:
132132
return floor | (lost == 0 ? 0 : 1)
133-
case .stochastically:
134-
// In theory, u01 should be Self.random(in: 0 ..< onesBit), but the
135-
// random(in:) method does not exist on BinaryInteger. This is
136-
// (arguably) good, though, because there's actually no reason to
137-
// generate large amounts of randomness just to implement stochastic
138-
// rounding; 32b suffices for almost all purposes, and 64b is more
139-
// than enough.
140-
var g = SystemRandomNumberGenerator()
141-
let u01 = g.next()
142-
if count < 64 {
143-
// count is small, so mask and lost are representable as both
144-
// UInt64 and Self, regardless of what type Self actually is.
145-
return floor + Self(((u01 & UInt64(mask)) + UInt64(lost)) >> count)
146-
} else {
147-
// count is large, so lost may not be representable as UInt64; pre-
148-
// shift by count-64 to isolate the high 64b of the fraction, then
149-
// add u01 and carry-out to round.
150-
let highWord = UInt64(truncatingIfNeeded: lost >> (Int(count) - 64))
151-
let (_, carry) = highWord.addingReportingOverflow(u01)
152-
return floor + (carry ? 1 : 0)
153-
}
154133
case .requireExact:
155134
precondition(lost == 0, "shift was not exact.")
156135
return floor

Tests/Executable/ComplexLog/main.swift

Lines changed: 0 additions & 108 deletions
This file was deleted.

0 commit comments

Comments
 (0)