Skip to content

Remove stochastic rounding mode #305

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 2 additions & 15 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
// swift-tools-version:5.5
// swift-tools-version:5.9
//===--- Package.swift ----------------------------------------*- swift -*-===//
//
// This source file is part of the Swift Numerics open source project
//
// Copyright (c) 2019-2021 Apple Inc. and the Swift Numerics project authors
// Copyright (c) 2019-2025 Apple Inc. and the Swift Numerics project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
Expand Down Expand Up @@ -81,19 +81,6 @@ let package = Package(
name: "RealTests",
dependencies: ["_TestSupport"],
exclude: ["CMakeLists.txt"]
),

// MARK: - Test executables
.executableTarget(
name: "ComplexLog",
dependencies: ["Numerics", "_TestSupport"],
path: "Tests/Executable/ComplexLog"
),

.executableTarget(
name: "ComplexLog1p",
dependencies: ["Numerics", "_TestSupport"],
path: "Tests/Executable/ComplexLog1p"
)
]
)
34 changes: 1 addition & 33 deletions Sources/IntegerUtilities/DivideWithRounding.swift
Original file line number Diff line number Diff line change
Expand Up @@ -116,22 +116,6 @@ extension BinaryInteger {
// If q is already odd, we have the correct result.
if q._lowWord & 1 == 1 { return q }

case .stochastically:
let bmag = other.magnitude
let rmag = r.magnitude
var bhi: UInt64
var rhi: UInt64
if other.magnitude <= UInt64.max {
bhi = UInt64(bmag)
rhi = UInt64(rmag)
} else {
let shift = bmag._msb - 63
bhi = UInt64(truncatingIfNeeded: bmag >> shift)
rhi = UInt64(truncatingIfNeeded: rmag >> shift)
}
let (sum, car) = rhi.addingReportingOverflow(.random(in: 0 ..< bhi))
if sum < bhi && !car { return q }

case .requireExact:
preconditionFailure("Division was not exact.")
}
Expand Down Expand Up @@ -299,22 +283,6 @@ extension SignedInteger {
// If q is already odd, we have the correct result.
if q._lowWord & 1 == 1 { return (q, r) }

case .stochastically:
let bmag = other.magnitude
let rmag = r.magnitude
var bhi: UInt64
var rhi: UInt64
if other.magnitude <= UInt64.max {
bhi = UInt64(bmag)
rhi = UInt64(rmag)
} else {
let shift = bmag._msb - 63
bhi = UInt64(truncatingIfNeeded: bmag >> shift)
rhi = UInt64(truncatingIfNeeded: rmag >> shift)
}
let (sum, car) = rhi.addingReportingOverflow(.random(in: 0 ..< bhi))
if sum < bhi && !car { return (q, r) }

case .requireExact:
preconditionFailure("Division was not exact.")
}
Expand Down Expand Up @@ -347,7 +315,7 @@ extension SignedInteger {
/// is not representable.
///
/// - Returns: `(quotient, remainder)`, with `0 <= remainder < b.magnitude`.
func euclideanDivision<T>(_ a: T, _ b: T) -> (quotient: T, remainder: T)
public func euclideanDivision<T>(_ a: T, _ b: T) -> (quotient: T, remainder: T)
where T: SignedInteger
{
a.divided(by: b, rounding: a >= 0 ? .towardZero : .awayFromZero)
Expand Down
88 changes: 16 additions & 72 deletions Sources/IntegerUtilities/RoundingRule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,13 @@
//
// This source file is part of the Swift Numerics open source project
//
// Copyright (c) 2021-2024 Apple Inc. and the Swift Numerics project authors
// Copyright (c) 2021-2025 Apple Inc. and the Swift Numerics project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
//
//===----------------------------------------------------------------------===//

// TODO: it's unfortunate that we can't specify a custom random source
// for the stochastic rounding rule, but I don't see a nice way to have
// that share the API with the other rounding rules, because we'd then
// have to take either the rule in-out or have an additional RNG/state
// parameter. The same problem applies to rounding with dithering and
// any other stateful rounding method. We should consider adding a
// stateful rounding API down the road to support those use cases.

/// A rule that defines how to select one of the two representable results
/// closest to a given value.
///
Expand Down Expand Up @@ -59,22 +51,22 @@
/// 2.0 | 2 | 2 | 2 | 2 | 2 |
/// -------+----------+----------+----------+----------+----------+
///
/// Specialized rounding rules
/// Specialized rounding rules
///
/// value | toOdd | stochastically | requireExact |
/// =======+==============+=======================+================+
/// -1.5 | -1 | 50% -2, 50% -1 | trap |
/// -------+--------------+-----------------------+----------------+
/// -0.5 | -1 | 50% -1, 50% 0 | trap |
/// -------+--------------+-----------------------+----------------+
/// 0.5 | 1 | 50% 0, 50% 1 | trap |
/// -------+--------------+-----------------------+----------------+
/// 0.7 | 1 | 30% 0, 70% 1 | trap |
/// -------+--------------+-----------------------+----------------+
/// 1.2 | 1 | 80% 1, 20% 2 | trap |
/// -------+--------------+-----------------------+----------------+
/// 2.0 | 2 | 2 | 2 |
/// -------+--------------+-----------------------+----------------+
/// value | toOdd | requireExact |
/// =======+==============+================+
/// -1.5 | -1 | trap |
/// -------+--------------+----------------+
/// -0.5 | -1 | trap |
/// -------+--------------+----------------+
/// 0.5 | 1 | trap |
/// -------+--------------+----------------+
/// 0.7 | 1 | trap |
/// -------+--------------+----------------+
/// 1.2 | 1 | trap |
/// -------+--------------+----------------+
/// 2.0 | 2 | 2 |
/// -------+--------------+----------------+
/// ```
public enum RoundingRule {
/// Produces the closest representable value that is less than or equal
Expand Down Expand Up @@ -200,45 +192,6 @@ public enum RoundingRule {
/// because 5/2 = 2.5 is equally close to 2 and 3, and 2 is even.
case toNearestOrEven

/// Adds a uniform random value from [0, d) to the value being rounded,
/// where d is the distance between the two closest representable values,
/// then rounds the sum downwards.
///
/// Unlike all the other rounding modes, this mode is _not deterministic_;
/// repeated calls to rounding operations with this mode will generally
/// produce different results. There is a tradeoff implicit in using this
/// mode: you can sacrifice _reproducible_ results to get _more accurate_
/// results in aggregate. For a contrived but illustrative example, consider
/// the following:
/// ```
/// let data = Array(repeating: 1, count: 100)
/// let result = data.reduce(0) {
/// $0 + $1.divided(by: 3, rounding: rule)
/// }
/// ```
/// because 1/3 is always the same value between 0 and 1, any
/// deterministic rounding rule must produce either 0 or 100 for
/// this computation. But rounding `stochastically` will
/// produce a value close to 33. The _error_ of the computation
/// is smaller, but the result will now change between runs of the
/// program.
///
/// For this simple case a better solution would be to add the
/// values first, and then divide. This gives a result that is both
/// reproducible _and_ accurate:
/// ```
/// let result = data.reduce(0, +)/3
/// ```
/// but this isn't always possible in more sophisticated scenarios,
/// and in those cases this rounding rule may be useful.
///
/// Examples:
/// - `(-4).divided(by: 3, rounding: .stochastically)`
/// will be –1 with probability 2/3 and –2 with probability 1/3.
/// - `5.shifted(rightBy: 1, rounding: .stochastically)`
/// will be 2 with probability 1/2 and 3 with probability 1/2.
case stochastically

/// If the value being rounded is representable, that value is returned.
/// Otherwise, a precondition failure occurs.
///
Expand Down Expand Up @@ -304,15 +257,6 @@ extension FloatingPoint {
// which way that rounds, then select the other value.
let even = (trunc + one/2).rounded(.toNearestOrEven)
return trunc == even ? trunc + one : trunc
case .stochastically:
let trunc = rounded(.towardZero)
if trunc == self { return trunc }
// We have eliminated all large values at this point; add dither in
// ±[0,1) and then truncate.
let bits = Swift.min(-Self.ulpOfOne.exponent, 32)
let random = Self(UInt32.random(in: 0 ... (1 << bits &- 1)))
let dither = Self(sign: sign, exponent: -bits, significand: random)
return (self + dither).rounded(.towardZero)
case .requireExact:
let trunc = rounded(.towardZero)
precondition(isInfinite || trunc == self, "\(self) is not an exact integer.")
Expand Down
21 changes: 0 additions & 21 deletions Sources/IntegerUtilities/ShiftWithRounding.swift
Original file line number Diff line number Diff line change
Expand Up @@ -130,27 +130,6 @@ extension BinaryInteger {
return floor + Self((round + lost) >> count)
case .toOdd:
return floor | (lost == 0 ? 0 : 1)
case .stochastically:
// In theory, u01 should be Self.random(in: 0 ..< onesBit), but the
// random(in:) method does not exist on BinaryInteger. This is
// (arguably) good, though, because there's actually no reason to
// generate large amounts of randomness just to implement stochastic
// rounding; 32b suffices for almost all purposes, and 64b is more
// than enough.
var g = SystemRandomNumberGenerator()
let u01 = g.next()
if count < 64 {
// count is small, so mask and lost are representable as both
// UInt64 and Self, regardless of what type Self actually is.
return floor + Self(((u01 & UInt64(mask)) + UInt64(lost)) >> count)
} else {
// count is large, so lost may not be representable as UInt64; pre-
// shift by count-64 to isolate the high 64b of the fraction, then
// add u01 and carry-out to round.
let highWord = UInt64(truncatingIfNeeded: lost >> (Int(count) - 64))
let (_, carry) = highWord.addingReportingOverflow(u01)
return floor + (carry ? 1 : 0)
}
case .requireExact:
precondition(lost == 0, "shift was not exact.")
return floor
Expand Down
108 changes: 0 additions & 108 deletions Tests/Executable/ComplexLog/main.swift

This file was deleted.

Loading