Skip to content

Commit 868e973

Browse files
[4.2] Two small fixes for FloatingPoint .random( ) (#17856)
* [SR-8178] Fix BinaryFloatingPoint.random(in:) open range returning upperBound (#17794) * Require .upperBound - .lowerBound be finite for FloatingPoint random (#17833) This is a slightly conservative precondition; when we re-work the FloatingPoint random computation in a more principled fashion, we can relax this to only requiring that .upperBound and .lowerBound are both finite. However, the current computation will break down unless this conservative condition is used, and this is future proof--we will only relax it going forward.
1 parent e29ece1 commit 868e973

File tree

2 files changed

+81
-12
lines changed

2 files changed

+81
-12
lines changed

stdlib/public/core/FloatingPoint.swift.gyb

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2423,7 +2423,9 @@ where Self.RawSignificand : FixedWidthInteger {
24232423
/// - Parameters:
24242424
/// - range: The range in which to create a random value.
24252425
% if Range == 'Range':
2426-
/// `range` must not be empty.
2426+
/// `range` must be non-empty and finite.
2427+
% else:
2428+
/// `range` must be finite.
24272429
% end
24282430
/// - generator: The random number generator to use when creating the
24292431
/// new random value.
@@ -2438,6 +2440,15 @@ where Self.RawSignificand : FixedWidthInteger {
24382440
"Can't get random value with an empty range"
24392441
)
24402442
let delta = range.upperBound - range.lowerBound
2443+
// TODO: this still isn't quite right, because the computation of delta
2444+
// can overflow (e.g. if .upperBound = .maximumFiniteMagnitude and
2445+
// .lowerBound = -.upperBound); this should be re-written with an
2446+
// algorithm that handles that case correctly, but this precondition
2447+
// is an acceptable short-term fix.
2448+
_precondition(
2449+
delta.isFinite,
2450+
"There is no uniform distribution on an infinite range"
2451+
)
24412452
let rand: Self.RawSignificand
24422453
if Self.RawSignificand.bitWidth == Self.significandBitCount + 1 {
24432454
rand = generator.next()
@@ -2451,16 +2462,25 @@ where Self.RawSignificand : FixedWidthInteger {
24512462
let significandCount = Self.significandBitCount + 1
24522463
let maxSignificand: Self.RawSignificand = 1 << significandCount
24532464
% if 'Closed' not in Range:
2454-
rand = generator.next(upperBound: maxSignificand)
2465+
// Rather than use .next(upperBound:), which has to work with arbitrary
2466+
// upper bounds, and therefore does extra work to avoid bias, we can take
2467+
// a shortcut because we know that maxSignificand is a power of two.
2468+
rand = generator.next() & (maxSignificand - 1)
24552469
% else:
24562470
rand = generator.next(upperBound: maxSignificand + 1)
24572471
if rand == maxSignificand {
24582472
return range.upperBound
24592473
}
24602474
% end
24612475
}
2462-
let unitRandom = Self.init(rand) * Self.ulpOfOne / 2
2463-
return delta * unitRandom + range.lowerBound
2476+
let unitRandom = Self.init(rand) * (Self.ulpOfOne / 2)
2477+
let randFloat = delta * unitRandom + range.lowerBound
2478+
% if 'Closed' not in Range:
2479+
if randFloat == range.upperBound {
2480+
return Self.random(in: range, using: &generator)
2481+
}
2482+
% end
2483+
return randFloat
24642484
}
24652485

24662486
/// Returns a random value within the specified range.
@@ -2488,7 +2508,9 @@ where Self.RawSignificand : FixedWidthInteger {
24882508
///
24892509
/// - Parameter range: The range in which to create a random value.
24902510
% if Range == 'Range':
2491-
/// `range` must not be empty.
2511+
/// `range` must be non-empty and finite.
2512+
% else:
2513+
/// `range` must be finite.
24922514
% end
24932515
/// - Returns: A random value within the bounds of `range`.
24942516
@inlinable

validation-test/stdlib/Random.swift

Lines changed: 54 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,7 @@ RandomTests.test("basic random numbers") {
4343

4444
// Random integers in ranges
4545

46-
func integerRangeTest<T: FixedWidthInteger>(_ type: T.Type)
47-
where T.Stride: SignedInteger, T.Magnitude: UnsignedInteger {
46+
func integerRangeTest<T: FixedWidthInteger>(_ type: T.Type) {
4847

4948
func testRange(_ range: Range<T>, iterations: Int = 1_000) {
5049
var integerSet: Set<T> = []
@@ -104,10 +103,8 @@ RandomTests.test("random integers in ranges") {
104103
// Random floating points in ranges
105104

106105
func floatingPointRangeTest<T: BinaryFloatingPoint>(_ type: T.Type)
107-
where T.RawSignificand: FixedWidthInteger,
108-
T.RawSignificand.Stride: SignedInteger & FixedWidthInteger,
109-
T.RawSignificand.Magnitude: UnsignedInteger {
110-
106+
where T.RawSignificand: FixedWidthInteger {
107+
111108
let testRange = 0 ..< 1_000
112109

113110
// open range
@@ -230,6 +227,56 @@ RandomTests.test("different random number generators") {
230227
expectEqual(shufflePasses[0], shufflePasses[1])
231228
}
232229

230+
// Random floating points with max values (SR-8178)
231+
232+
var lcrng = LCRNG(seed: 1234567890)
233+
234+
public struct RotatingRNG: RandomNumberGenerator {
235+
public let rotation: [() -> UInt64] = [
236+
{ return .min },
237+
{ return .max },
238+
{ return lcrng.next() }
239+
]
240+
public var rotationIndex = 0
241+
242+
public mutating func next() -> UInt64 {
243+
if rotationIndex == rotation.count {
244+
rotationIndex = 0
245+
}
246+
247+
defer {
248+
rotationIndex += 1
249+
}
250+
251+
return rotation[rotationIndex]()
252+
}
253+
}
254+
255+
func floatingPointRangeMaxTest<T: BinaryFloatingPoint>(_ type: T.Type)
256+
where T.RawSignificand: FixedWidthInteger {
257+
258+
let testRange = 0 ..< 1_000
259+
260+
var rng = RotatingRNG()
261+
let ranges: [Range<T>] = [0 ..< 1, 1 ..< 2, 10 ..< 11, 0 ..< 10]
262+
for range in ranges {
263+
for _ in testRange {
264+
let random = T.random(in: range, using: &rng)
265+
expectTrue(range.contains(random))
266+
expectTrue(range.lowerBound <= random)
267+
expectTrue(random < range.upperBound)
268+
}
269+
}
270+
}
271+
272+
RandomTests.test("random floating point range maxes") {
273+
floatingPointRangeMaxTest(Float.self)
274+
floatingPointRangeMaxTest(Double.self)
275+
#if !os(Windows) && (arch(i386) || arch(x86_64))
276+
floatingPointRangeMaxTest(Float80.self)
277+
#endif
278+
}
279+
233280
// Uniform Distribution
234281

235282
func chi2Test(_ samples: [Double]) -> Bool {
@@ -248,7 +295,7 @@ func chi2Test(_ samples: [Double]) -> Bool {
248295

249296
if chi2 > cvHigh {
250297
return false
251-
}else {
298+
} else {
252299
return true
253300
}
254301
}

0 commit comments

Comments
 (0)