Skip to content

Commit 2b458e8

Browse files
Merge pull request #305 from stephentyrone/remove-stochastic-rounding
Remove stochastic rounding mode
2 parents c327d04 + 31fe14d commit 2b458e8

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)