mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
326 lines
9.1 KiB
Swift
326 lines
9.1 KiB
Swift
// RUN: %target-run-stdlib-swift
|
|
// REQUIRES: executable_test
|
|
|
|
import StdlibUnittest
|
|
import StdlibCollectionUnittest
|
|
import SwiftShims // for swift_stdlib_random
|
|
|
|
let RandomTests = TestSuite("Random")
|
|
|
|
// _fill(bytes:)
|
|
|
|
extension RandomNumberGenerator {
|
|
func _fill(bytes buffer: UnsafeMutableRawBufferPointer) {
|
|
guard let start = buffer.baseAddress else { return }
|
|
swift_stdlib_random(start, buffer.count)
|
|
}
|
|
}
|
|
|
|
RandomTests.test("_fill(bytes:)") {
|
|
for count in [100, 1000] {
|
|
var bytes1 = [UInt8](repeating: 0, count: count)
|
|
var bytes2 = [UInt8](repeating: 0, count: count)
|
|
let zeros = [UInt8](repeating: 0, count: count)
|
|
expectEqual(bytes1, bytes2)
|
|
expectEqual(bytes1, zeros)
|
|
expectEqual(bytes2, zeros)
|
|
|
|
var g = SystemRandomNumberGenerator()
|
|
bytes1.withUnsafeMutableBytes { g._fill(bytes: $0) }
|
|
expectNotEqual(bytes1, bytes2)
|
|
expectNotEqual(bytes1, zeros)
|
|
|
|
bytes2.withUnsafeMutableBytes { g._fill(bytes: $0) }
|
|
expectNotEqual(bytes1, bytes2)
|
|
expectNotEqual(bytes2, zeros)
|
|
}
|
|
}
|
|
|
|
// Basic random numbers
|
|
|
|
RandomTests.test("basic random numbers") {
|
|
let randomNumber1 = Int.random(in: .min ... .max)
|
|
let randomNumber2 = Int.random(in: .min ... .max)
|
|
expectNotEqual(randomNumber1, randomNumber2)
|
|
|
|
let randomDouble1 = Double.random(in: 0 ..< 1)
|
|
expectTrue(randomDouble1 < 1 && randomDouble1 >= 0)
|
|
let randomDouble2 = Double.random(in: 0 ..< 1)
|
|
expectTrue(randomDouble2 < 1 && randomDouble2 >= 0)
|
|
expectNotEqual(randomDouble1, randomDouble2)
|
|
}
|
|
|
|
// Random integers in ranges
|
|
|
|
func integerRangeTest<T: FixedWidthInteger>(_ type: T.Type) {
|
|
|
|
func testRange(_ range: Range<T>, iterations: Int = 1_000) {
|
|
var integerSet: Set<T> = []
|
|
for _ in 0 ..< iterations {
|
|
let random = T.random(in: range)
|
|
expectTrue(range.contains(random))
|
|
integerSet.insert(random)
|
|
}
|
|
expectEqual(integerSet, Set(range))
|
|
}
|
|
|
|
func testClosedRange(_ range: ClosedRange<T>, iterations: Int = 1_000) {
|
|
var integerSet: Set<T> = []
|
|
for _ in 0 ..< iterations {
|
|
let random = T.random(in: range)
|
|
expectTrue(range.contains(random))
|
|
integerSet.insert(random)
|
|
}
|
|
expectEqual(integerSet, Set(range))
|
|
}
|
|
|
|
// min open range
|
|
testRange(T.min ..< (T.min + 10))
|
|
|
|
// min closed range
|
|
testClosedRange(T.min ... (T.min + 10))
|
|
|
|
// max open range
|
|
testRange((T.max - 10) ..< T.max)
|
|
|
|
// max closed range
|
|
testClosedRange((T.max - 10) ... T.max)
|
|
|
|
// Test full ranges for Int8 and UInt8
|
|
if T.bitWidth == 8 {
|
|
let fullIterations = 10_000
|
|
|
|
// full open range
|
|
testRange(T.min ..< T.max, iterations: fullIterations)
|
|
|
|
// full closed range
|
|
testClosedRange(T.min ... T.max, iterations: fullIterations)
|
|
}
|
|
}
|
|
|
|
RandomTests.test("random integers in ranges") {
|
|
integerRangeTest(Int8.self)
|
|
integerRangeTest(Int16.self)
|
|
integerRangeTest(Int32.self)
|
|
integerRangeTest(Int64.self)
|
|
integerRangeTest(UInt8.self)
|
|
integerRangeTest(UInt16.self)
|
|
integerRangeTest(UInt32.self)
|
|
integerRangeTest(UInt64.self)
|
|
}
|
|
|
|
// Random floating points in ranges
|
|
|
|
func floatingPointRangeTest<T: BinaryFloatingPoint>(_ type: T.Type)
|
|
where T.RawSignificand: FixedWidthInteger {
|
|
|
|
let testRange = 0 ..< 1_000
|
|
|
|
// open range
|
|
let openRange: Range<T> = 0.0 ..< 10.0
|
|
for _ in testRange {
|
|
let random = T.random(in: openRange)
|
|
expectTrue(openRange.contains(random))
|
|
}
|
|
|
|
// closed range
|
|
let closedRange: ClosedRange<T> = 0.0 ... 10.0
|
|
for _ in testRange {
|
|
let random = T.random(in: closedRange)
|
|
expectTrue(closedRange.contains(random))
|
|
}
|
|
}
|
|
|
|
RandomTests.test("random floating points in ranges") {
|
|
floatingPointRangeTest(Float.self)
|
|
floatingPointRangeTest(Double.self)
|
|
#if !os(Windows) && (arch(i386) || arch(x86_64))
|
|
floatingPointRangeTest(Float80.self)
|
|
#endif
|
|
}
|
|
|
|
// Random Elements from collection
|
|
|
|
RandomTests.test("random elements from collection") {
|
|
// Non-empty collection
|
|
var elementSet: Set<String> = []
|
|
let greetings = ["hello", "hi", "hey", "hola", "what's up"]
|
|
for _ in 0 ..< 1_000 {
|
|
let randomGreeting = greetings.randomElement()
|
|
expectNotNil(randomGreeting)
|
|
expectTrue(greetings.contains(randomGreeting!))
|
|
elementSet.insert(randomGreeting!)
|
|
}
|
|
expectEqual(elementSet, Set(greetings))
|
|
|
|
// Empty collection
|
|
let emptyArray: [String] = []
|
|
for _ in 0 ..< 1_000 {
|
|
let randomElement = emptyArray.randomElement()
|
|
expectNil(randomElement)
|
|
}
|
|
}
|
|
|
|
// Shuffle
|
|
|
|
RandomTests.test("shuffling") {
|
|
let alphabet = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l",
|
|
"m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x",
|
|
"y", "z"]
|
|
var oldAlphabet = MinimalSequence(elements: alphabet)
|
|
for _ in 0 ..< 1_000 {
|
|
let newAlphabet = alphabet.shuffled()
|
|
expectTrue(alphabet.elementsEqual(newAlphabet.sorted()))
|
|
expectFalse(oldAlphabet.elementsEqual(newAlphabet))
|
|
oldAlphabet = MinimalSequence(elements: newAlphabet)
|
|
}
|
|
}
|
|
|
|
// Different RNGS
|
|
|
|
public struct LCRNG: RandomNumberGenerator {
|
|
private var state: UInt64
|
|
private static let m: UInt64 = 1 << 48
|
|
private static let a: UInt64 = 25214903917
|
|
private static let c: UInt64 = 11
|
|
|
|
public init(seed: UInt64) {
|
|
self.state = seed
|
|
}
|
|
|
|
private mutating func next() -> UInt32 {
|
|
state = (LCRNG.a &* state &+ LCRNG.c) % LCRNG.m
|
|
return UInt32(truncatingIfNeeded: state >> 15)
|
|
}
|
|
|
|
public mutating func next() -> UInt64 {
|
|
return UInt64(next() as UInt32) << 32 | UInt64(next() as UInt32)
|
|
}
|
|
}
|
|
|
|
RandomTests.test("different random number generators") {
|
|
// 0 = first pass array, 1 = second pass array
|
|
var intPasses: [[Int]] = [[], []]
|
|
var doublePasses: [[Double]] = [[], []]
|
|
var boolPasses: [[Bool]] = [[], []]
|
|
var collectionPasses: [[Int]] = [[], []]
|
|
var shufflePasses: [[[Int]]] = [[], []]
|
|
|
|
for i in 0 ..< 2 {
|
|
let seed: UInt64 = 1234567890
|
|
var rng = LCRNG(seed: seed)
|
|
|
|
for _ in 0 ..< 1_000 {
|
|
let randomInt = Int.random(in: 0 ... 100, using: &rng)
|
|
intPasses[i].append(randomInt)
|
|
|
|
let randomDouble = Double.random(in: 0 ..< 1, using: &rng)
|
|
doublePasses[i].append(randomDouble)
|
|
|
|
let randomBool = Bool.random(using: &rng)
|
|
boolPasses[i].append(randomBool)
|
|
|
|
let randomIntFromCollection = Array(0 ... 100).randomElement(using: &rng)
|
|
expectNotNil(randomIntFromCollection)
|
|
collectionPasses[i].append(randomIntFromCollection!)
|
|
|
|
let randomShuffledCollection = Array(0 ... 100).shuffled(using: &rng)
|
|
shufflePasses[i].append(randomShuffledCollection)
|
|
}
|
|
}
|
|
|
|
expectEqual(intPasses[0], intPasses[1])
|
|
expectEqual(doublePasses[0], doublePasses[1])
|
|
expectEqual(boolPasses[0], boolPasses[1])
|
|
expectEqual(collectionPasses[0], collectionPasses[1])
|
|
expectEqual(shufflePasses[0], shufflePasses[1])
|
|
}
|
|
|
|
// https://github.com/apple/swift/issues/50710
|
|
// Random floating points with max values
|
|
|
|
var lcrng = LCRNG(seed: 1234567890)
|
|
|
|
public struct RotatingRNG: RandomNumberGenerator {
|
|
public let rotation: [() -> UInt64] = [
|
|
{ return .min },
|
|
{ return .max },
|
|
{ return lcrng.next() }
|
|
]
|
|
public var rotationIndex = 0
|
|
|
|
public mutating func next() -> UInt64 {
|
|
if rotationIndex == rotation.count {
|
|
rotationIndex = 0
|
|
}
|
|
|
|
defer {
|
|
rotationIndex += 1
|
|
}
|
|
|
|
return rotation[rotationIndex]()
|
|
}
|
|
}
|
|
|
|
func floatingPointRangeMaxTest<T: BinaryFloatingPoint>(_ type: T.Type)
|
|
where T.RawSignificand: FixedWidthInteger {
|
|
|
|
let testRange = 0 ..< 1_000
|
|
|
|
var rng = RotatingRNG()
|
|
let ranges: [Range<T>] = [0 ..< 1, 1 ..< 2, 10 ..< 11, 0 ..< 10]
|
|
for range in ranges {
|
|
for _ in testRange {
|
|
let random = T.random(in: range, using: &rng)
|
|
expectTrue(range.contains(random))
|
|
expectTrue(range.lowerBound <= random)
|
|
expectTrue(random < range.upperBound)
|
|
}
|
|
}
|
|
}
|
|
|
|
RandomTests.test("random floating point range maxes") {
|
|
floatingPointRangeMaxTest(Float.self)
|
|
floatingPointRangeMaxTest(Double.self)
|
|
#if !os(Windows) && (arch(i386) || arch(x86_64))
|
|
floatingPointRangeMaxTest(Float80.self)
|
|
#endif
|
|
}
|
|
|
|
// Uniform Distribution
|
|
|
|
func chi2Test(_ samples: [Double]) -> Bool {
|
|
precondition(samples.count == 50, "confidence interval requires 50 samples")
|
|
let expected = samples.reduce(0, +) / Double(samples.count)
|
|
// Right tail for 0.0001% (1e-6) with a degree of freedom of (50 -
|
|
// 1). With hundreds of builds a day, this has to be very low to not get too
|
|
// many spurious failures, but obvious problems should still be detected
|
|
// (e.g. an off-by-one that means samples[0] == 0 will result in chi2 =
|
|
// (0-expected)**2/expected + ... > expected, so as long as we've generated
|
|
// more than samples.count * cvHigh (~5500) values, we'll catch it).
|
|
let cvHigh = 111.1
|
|
let chi2 = samples.map {
|
|
(($0 - expected) * ($0 - expected)) / expected
|
|
}.reduce(0, +)
|
|
|
|
if chi2 > cvHigh {
|
|
return false
|
|
} else {
|
|
return true
|
|
}
|
|
}
|
|
|
|
RandomTests.test("uniform distribution") {
|
|
let upperBound = 50
|
|
let numberOfTrials = 500_000
|
|
var array = [Double](repeating: 0.0, count: upperBound)
|
|
for _ in 0 ..< numberOfTrials {
|
|
let randomIndex = Int.random(in: 0 ..< upperBound)
|
|
array[randomIndex] += 1.0
|
|
}
|
|
|
|
expectTrue(chi2Test(array))
|
|
}
|
|
|
|
runAllTests()
|