diff --git a/benchmark/CMakeLists.txt b/benchmark/CMakeLists.txt index 1d861ae7203b7..4257590e5bf07 100644 --- a/benchmark/CMakeLists.txt +++ b/benchmark/CMakeLists.txt @@ -123,6 +123,8 @@ set(SWIFT_BENCH_MODULES single-source/Queue single-source/RC4 single-source/RGBHistogram + single-source/RandomShuffle + single-source/RandomValues single-source/RangeAssignment single-source/RangeIteration single-source/RangeReplaceableCollectionPlusDefault diff --git a/benchmark/single-source/RandomShuffle.swift b/benchmark/single-source/RandomShuffle.swift new file mode 100644 index 0000000000000..a9869dd7da677 --- /dev/null +++ b/benchmark/single-source/RandomShuffle.swift @@ -0,0 +1,64 @@ +//===--- RandomShuffle.swift ----------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2018 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import TestsUtils + +// +// Benchmark that shuffles arrays of integers. Measures the performance of +// shuffling large arrays. +// + +public let RandomShuffle = [ + BenchmarkInfo(name: "RandomShuffleDef", runFunction: run_RandomShuffleDef, + tags: [.api], setUpFunction: setup_RandomShuffle), + BenchmarkInfo(name: "RandomShuffleLCG", runFunction: run_RandomShuffleLCG, + tags: [.api], setUpFunction: setup_RandomShuffle), +] + +/// A linear congruential PRNG. +struct LCRNG: RandomNumberGenerator { + private var state: UInt64 + + init(seed: Int) { + state = UInt64(truncatingIfNeeded: seed) + for _ in 0..<10 { _ = next() } + } + + mutating func next() -> UInt64 { + state = 2862933555777941757 &* state &+ 3037000493 + return state + } +} + +var numbers = Array(0...3_000_000) + +@inline(never) +func setup_RandomShuffle() { + _ = numbers.count +} + +@inline(never) +public func run_RandomShuffleDef(_ N: Int) { + for _ in 0 ..< N { + numbers.shuffle() + blackHole(numbers.first!) + } +} + +@inline(never) +public func run_RandomShuffleLCG(_ N: Int) { + var generator = LCRNG(seed: 0) + for _ in 0 ..< N { + numbers.shuffle(using: &generator) + blackHole(numbers.first!) + } +} diff --git a/benchmark/single-source/RandomValues.swift b/benchmark/single-source/RandomValues.swift new file mode 100644 index 0000000000000..6387e3d5a99f9 --- /dev/null +++ b/benchmark/single-source/RandomValues.swift @@ -0,0 +1,87 @@ +//===--- RandomValues.swift -----------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2018 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import TestsUtils + +// +// Benchmark generating lots of random values. Measures the performance of +// the default random generator and the algorithms for generating integers +// and floating-point values. +// + +public let RandomValues = [ + BenchmarkInfo(name: "RandomIntegersDef", runFunction: run_RandomIntegersDef, tags: [.api]), + BenchmarkInfo(name: "RandomIntegersLCG", runFunction: run_RandomIntegersLCG, tags: [.api]), + BenchmarkInfo(name: "RandomDoubleDef", runFunction: run_RandomDoubleDef, tags: [.api]), + BenchmarkInfo(name: "RandomDoubleLCG", runFunction: run_RandomDoubleLCG, tags: [.api]), +] + +/// A linear congruential PRNG. +struct LCRNG: RandomNumberGenerator { + private var state: UInt64 + + init(seed: Int) { + state = UInt64(truncatingIfNeeded: seed) + for _ in 0..<10 { _ = next() } + } + + mutating func next() -> UInt64 { + state = 2862933555777941757 &* state &+ 3037000493 + return state + } +} + +@inline(never) +public func run_RandomIntegersDef(_ N: Int) { + for _ in 0 ..< N { + var x = 0 + for _ in 0 ..< 100_000 { + x &+= Int.random(in: 0...10_000) + } + blackHole(x) + } +} + +@inline(never) +public func run_RandomIntegersLCG(_ N: Int) { + for _ in 0 ..< N { + var x = 0 + var generator = LCRNG(seed: 0) + for _ in 0 ..< 100_000 { + x &+= Int.random(in: 0...10_000, using: &generator) + } + CheckResults(x == 498214315) + } +} + +@inline(never) +public func run_RandomDoubleDef(_ N: Int) { + for _ in 0 ..< N { + var x = 0.0 + for _ in 0 ..< 100_000 { + x += Double.random(in: -1000...1000) + } + blackHole(x) + } +} + +@inline(never) +public func run_RandomDoubleLCG(_ N: Int) { + for _ in 0 ..< N { + var x = 0.0 + var generator = LCRNG(seed: 0) + for _ in 0 ..< 100_000 { + x += Double.random(in: -1000...1000, using: &generator) + } + blackHole(x) + } +} diff --git a/benchmark/utils/main.swift b/benchmark/utils/main.swift index fe556661c9eb2..16e89c2740fff 100644 --- a/benchmark/utils/main.swift +++ b/benchmark/utils/main.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information @@ -111,6 +111,8 @@ import ProtocolDispatch2 import Queue import RC4 import RGBHistogram +import RandomShuffle +import RandomValues import RangeAssignment import RangeIteration import RangeReplaceableCollectionPlusDefault @@ -266,6 +268,8 @@ registerBenchmark(QueueGeneric) registerBenchmark(QueueConcrete) registerBenchmark(RC4Test) registerBenchmark(RGBHistogram) +registerBenchmark(RandomShuffle) +registerBenchmark(RandomValues) registerBenchmark(RangeAssignment) registerBenchmark(RangeIteration) registerBenchmark(RangeReplaceableCollectionPlusDefault) diff --git a/stdlib/public/SwiftShims/LibcShims.h b/stdlib/public/SwiftShims/LibcShims.h index 5f6e995a8227f..3ed8988bb367e 100644 --- a/stdlib/public/SwiftShims/LibcShims.h +++ b/stdlib/public/SwiftShims/LibcShims.h @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information @@ -149,6 +149,10 @@ __swift_uint32_t _stdlib_cxx11_mt19937(void); SWIFT_RUNTIME_STDLIB_INTERNAL __swift_uint32_t _stdlib_cxx11_mt19937_uniform(__swift_uint32_t upper_bound); +// Random number for stdlib +SWIFT_RUNTIME_STDLIB_INTERNAL +void _stdlib_random(void *buf, __swift_size_t nbytes); + // Math library functions static inline SWIFT_ALWAYS_INLINE float _stdlib_remainderf(float _self, float _other) { diff --git a/stdlib/public/core/Bool.swift b/stdlib/public/core/Bool.swift index f1548b0869c7a..ba3f8257f0b5b 100644 --- a/stdlib/public/core/Bool.swift +++ b/stdlib/public/core/Bool.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information @@ -86,6 +86,28 @@ public struct Bool { public init(_ value: Bool) { self = value } + + /// Returns a random Boolean value + /// + /// - Parameter generator: The random number generator to use when getting a + /// random Boolean. + /// - Returns: A random Boolean value. + @inlinable + public static func random( + using generator: inout T + ) -> Bool { + return (generator.next() >> 17) & 1 == 0 + } + + /// Returns a random Boolean value + /// + /// - Returns: A random Boolean value. + /// + /// This uses the standard library's default random number generator. + @inlinable + public static func random() -> Bool { + return Bool.random(using: &Random.default) + } } extension Bool : _ExpressibleByBuiltinBooleanLiteral, ExpressibleByBooleanLiteral { diff --git a/stdlib/public/core/CMakeLists.txt b/stdlib/public/core/CMakeLists.txt index 5b21edda7f0e9..6f69f32490c9a 100644 --- a/stdlib/public/core/CMakeLists.txt +++ b/stdlib/public/core/CMakeLists.txt @@ -2,7 +2,7 @@ # # This source file is part of the Swift.org open source project # -# Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors +# Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors # Licensed under Apache License v2.0 with Runtime Library Exception # # See https://swift.org/LICENSE.txt for license information @@ -97,6 +97,7 @@ set(SWIFTLIB_ESSENTIAL Policy.swift PrefixWhile.swift Print.swift + Random.swift RandomAccessCollection.swift Range.swift RangeReplaceableCollection.swift diff --git a/stdlib/public/core/Collection.swift b/stdlib/public/core/Collection.swift index cb90f62e69bc5..943e4f906cf1d 100644 --- a/stdlib/public/core/Collection.swift +++ b/stdlib/public/core/Collection.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information @@ -803,6 +803,25 @@ public protocol Collection: Sequence where SubSequence: Collection { /// `endIndex`. func formIndex(after i: inout Index) + /// Returns a random element of the collection, using the given generator as + /// a source for randomness. + /// + /// You use this method to select a random element from a collection when you + /// are using a custom random number generator. For example, call + /// `randomElement(using:)` to select a random element from an array of names. + /// + /// let names = ["Zoey", "Chloe", "Amani", "Amaia"] + /// let randomName = names.randomElement(using: &myGenerator)! + /// // randomName == "Amani" (maybe) + /// + /// - Parameter generator: The random number generator to use when choosing + /// a random element. + /// - Returns: A random element from the collection. If the collection is + /// empty, the method returns `nil`. + func randomElement( + using generator: inout T + ) -> Element? + @available(*, deprecated, message: "all index distances are now of type Int") typealias IndexDistance = Int } @@ -1016,6 +1035,54 @@ extension Collection { return count } + /// Returns a random element of the collection, using the given generator as + /// a source for randomness. + /// + /// You use this method to select a random element from a collection when you + /// are using a custom random number generator. For example, call + /// `randomElement(using:)` to select a random element from an array of names. + /// + /// let names = ["Zoey", "Chloe", "Amani", "Amaia"] + /// let randomName = names.randomElement(using: &myGenerator)! + /// // randomName == "Amani" (maybe) + /// + /// - Parameter generator: The random number generator to use when choosing + /// a random element. + /// - Returns: A random element from the collection. If the collection is + /// empty, the method returns `nil`. + @inlinable + public func randomElement( + using generator: inout T + ) -> Element? { + guard !isEmpty else { return nil } + let random = generator.next(upperBound: UInt(count)) + let index = self.index( + startIndex, + offsetBy: numericCast(random) + ) + return self[index] + } + + /// Returns a random element of the collection. + /// + /// For example, call `randomElement()` to select a random element from an + /// array of names. + /// + /// let names = ["Zoey", "Chloe", "Amani", "Amaia"] + /// let randomName = names.randomElement()! + /// // randomName == "Amani" (perhaps) + /// + /// This method uses the default random generator, `Random.default`. The call + /// to `names.randomElement()` above is equivalent to calling + /// `names.randomElement(using: &Random.default)`. + /// + /// - Returns: A random element from the collection. If the collection is + /// empty, the method returns `nil`. + @inlinable + public func randomElement() -> Element? { + return randomElement(using: &Random.default) + } + /// Do not use this method directly; call advanced(by: n) instead. @inlinable @inline(__always) diff --git a/stdlib/public/core/CollectionAlgorithms.swift b/stdlib/public/core/CollectionAlgorithms.swift index 01268a91e0b88..7e3c9fcd130aa 100644 --- a/stdlib/public/core/CollectionAlgorithms.swift +++ b/stdlib/public/core/CollectionAlgorithms.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information @@ -367,6 +367,114 @@ extension MutableCollection where Self : BidirectionalCollection { } } +//===----------------------------------------------------------------------===// +// shuffled()/shuffle() +//===----------------------------------------------------------------------===// + +extension Sequence { + /// Returns the elements of the sequence, shuffled using the given generator + /// as a source for randomness. + /// + /// You use this method to randomize the elements of a sequence when you + /// are using a custom random number generator. For example, you can shuffle + /// the numbers between `0` and `9` by calling the `shuffled(using:)` method + /// on that range: + /// + /// let numbers = 0...9 + /// let shuffledNumbers = numbers.shuffled(using: &myGenerator) + /// // shuffledNumbers == [8, 9, 4, 3, 2, 6, 7, 0, 5, 1] + /// + /// - Parameter generator: The random number generator to use when shuffling + /// the sequence. + /// - Returns: An array of this sequence's elements in a shuffled order. + /// + /// - Complexity: O(*n*) + @inlinable + public func shuffled( + using generator: inout T + ) -> [Element] { + var result = ContiguousArray(self) + result.shuffle(using: &generator) + return Array(result) + } + + /// Returns the elements of the sequence, shuffled. + /// + /// For example, you can shuffle the numbers between `0` and `9` by calling + /// the `shuffled()` method on that range: + /// + /// let numbers = 0...9 + /// let shuffledNumbers = numbers.shuffled() + /// // shuffledNumbers == [1, 7, 6, 2, 8, 9, 4, 3, 5, 0] + /// + /// This method uses the default random generator, `Random.default`. The call + /// to `numbers.shuffled()` above is equivalent to calling + /// `numbers.shuffled(using: &Random.default)`. + /// + /// - Returns: A shuffled array of this sequence's elements. + /// + /// - Complexity: O(*n*) + @inlinable + public func shuffled() -> [Element] { + return shuffled(using: &Random.default) + } +} + +extension MutableCollection where Self : RandomAccessCollection { + /// Shuffles the collection in place, using the given generator as a source + /// for randomness. + /// + /// You use this method to randomize the elements of a collection when you + /// are using a custom random number generator. For example, you can use the + /// `shuffle(using:)` method to randomly reorder the elements of an array. + /// + /// var names = ["Alejandro", "Camila", "Diego", "Luciana", "Luis", "Sofía"] + /// names.shuffle(using: &myGenerator) + /// // names == ["Sofía", "Alejandro", "Camila", "Luis", "Diego", "Luciana"] + /// + /// - Parameter generator: The random number generator to use when shuffling + /// the collection. + /// + /// - Complexity: O(*n*) + @inlinable + public mutating func shuffle( + using generator: inout T + ) { + let count = self.count + guard count > 1 else { return } + var amount = count + var currentIndex = startIndex + while amount > 1 { + let random = generator.next(upperBound: UInt(amount)) + amount -= 1 + swapAt( + currentIndex, + index(currentIndex, offsetBy: numericCast(random)) + ) + formIndex(after: ¤tIndex) + } + } + + /// Shuffles the collection in place. + /// + /// Use the `shuffle()` method to randomly reorder the elements of an + /// array. + /// + /// var names = ["Alejandro", "Camila", "Diego", "Luciana", "Luis", "Sofía"] + /// names.shuffle(using: myGenerator) + /// // names == ["Luis", "Camila", "Luciana", "Sofía", "Alejandro", "Diego"] + /// + /// This method uses the default random generator, `Random.default`. The call + /// to `names.shuffle()` above is equivalent to calling + /// `names.shuffle(using: &Random.default)`. + /// + /// - Complexity: O(*n*) + @inlinable + public mutating func shuffle() { + shuffle(using: &Random.default) + } +} + //===----------------------------------------------------------------------===// // sorted()/sort() //===----------------------------------------------------------------------===// diff --git a/stdlib/public/core/FloatingPoint.swift.gyb b/stdlib/public/core/FloatingPoint.swift.gyb index 09e26353ce501..4ee807b8d4541 100644 --- a/stdlib/public/core/FloatingPoint.swift.gyb +++ b/stdlib/public/core/FloatingPoint.swift.gyb @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information @@ -2399,6 +2399,112 @@ extension BinaryFloatingPoint { */ } +% for Range in ['Range', 'ClosedRange']: +% exampleRange = '10.0..<20.0' if Range == 'Range' else '10.0...20.0' +extension BinaryFloatingPoint +where Self.RawSignificand : FixedWidthInteger, + Self.RawSignificand.Stride : SignedInteger, + Self.RawSignificand.Magnitude : UnsignedInteger { + + /// Returns a random value within the specified range, using the given + /// generator as a source for randomness. + /// + /// Use this method to generate a floating-point value within a specific + /// range when you are using a custom random number generator. This example + /// creates three new values in the range `${exampleRange}`. + /// + /// for _ in 1...3 { + /// print(Double.random(in: ${exampleRange}, using: &myGenerator)) + /// } + /// // Prints "18.1900709259179" + /// // Prints "14.2286325689993" + /// // Prints "13.1485686260762" + /// + /// The `random(in:using:)` static method chooses a random value from a + /// continuous uniform distribution in `range`, and then converts that value + /// to the nearest representable value in this type. Depending on the size and + /// span of `range`, some concrete values may be represented more frequently + /// than others. + /// + /// - Parameters: + /// - range: The range in which to create a random value. +% if Range == 'Range': + /// `range` must not be empty. +% end + /// - generator: The random number generator to use when creating the + /// new random value. + /// - Returns: A random value within the bounds of `range`. + @inlinable + public static func random( + in range: ${Range}, + using generator: inout T + ) -> Self { + _precondition( + !range.isEmpty, + "Can't get random value with an empty range" + ) + let delta = range.upperBound - range.lowerBound + let rand: Self.RawSignificand + if Self.RawSignificand.bitWidth == Self.significandBitCount + 1 { + rand = generator.next() +% if 'Closed' in Range: + let tmp: UInt8 = generator.next() & 1 + if rand == Self.RawSignificand.max && tmp == 1 { + return range.upperBound + } +% end + } else { + let significandCount = Self.significandBitCount + 1 + let maxSignificand: Self.RawSignificand = 1 << significandCount +% if 'Closed' not in Range: + rand = generator.next(upperBound: maxSignificand) +% else: + rand = generator.next(upperBound: maxSignificand + 1) + if rand == maxSignificand { + return range.upperBound + } +% end + } + let unitRandom = Self.init(rand) * Self.ulpOfOne / 2 + return delta * unitRandom + range.lowerBound + } + + /// Returns a random value within the specified range. + /// + /// Use this method to generate a floating-point value within a specific + /// range. This example creates three new values in the range + /// `${exampleRange}`. + /// + /// for _ in 1...3 { + /// print(Double.random(in: ${exampleRange})) + /// } + /// // Prints "18.1900709259179" + /// // Prints "14.2286325689993" + /// // Prints "13.1485686260762" + /// + /// The `random()` static method chooses a random value from a continuous + /// uniform distribution in `range`, and then converts that value to the + /// nearest representable value in this type. Depending on the size and span + /// of `range`, some concrete values may be represented more frequently than + /// others. + /// + /// This method uses the default random generator, `Random.default`. The call + /// to `Double.random(in: ${exampleRange})` above is equivalent to calling + /// `Double.random(in: ${exampleRange}, using: &Random.default)`. + /// + /// - Parameter range: The range in which to create a random value. +% if Range == 'Range': + /// `range` must not be empty. +% end + /// - Returns: A random value within the bounds of `range`. + @inlinable + public static func random(in range: ${Range}) -> Self { + return Self.random(in: range, using: &Random.default) + } +} + +% end + /// Returns the absolute value of `x`. @inlinable // FIXME(sil-serialize-all) @_transparent diff --git a/stdlib/public/core/GroupInfo.json b/stdlib/public/core/GroupInfo.json index fd559016a2a71..9fead7b75047b 100644 --- a/stdlib/public/core/GroupInfo.json +++ b/stdlib/public/core/GroupInfo.json @@ -176,6 +176,7 @@ "Policy.swift", "Print.swift", "REPL.swift", + "Random.swift", "Runtime.swift", "RuntimeFunctionCounters.swift", "Shims.swift", diff --git a/stdlib/public/core/Integers.swift.gyb b/stdlib/public/core/Integers.swift.gyb index 9e7b22a56fc21..3b5e34c4f83d3 100644 --- a/stdlib/public/core/Integers.swift.gyb +++ b/stdlib/public/core/Integers.swift.gyb @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information @@ -2402,6 +2402,9 @@ ${assignmentOperatorComment(x.operator, False)} static func ${x.operator}=(_ lhs: inout Self, _ rhs: Self) % end + static func _random( + using generator: inout R + ) -> Self } extension FixedWidthInteger { @@ -2530,6 +2533,168 @@ ${assignmentOperatorComment(x.operator, False)} % end } +% for Range in ['Range', 'ClosedRange']: +% exampleRange = '1..<100' if Range == 'Range' else '1...100' + +extension ${Range} + where Bound: FixedWidthInteger, Bound.Stride : SignedInteger, + Bound.Magnitude: UnsignedInteger +{ + + /// Returns a random element of the range, using the given generator as + /// a source for randomness. + /// + /// You can use this method to select a random element of a range when you + /// are using a custom random number generator. If you're generating a random + /// number, in most cases, you should prefer using the `random(in:using:)` + /// static method of the desired numeric type. That static method is available + /// for both integer and floating point types, and returns a non-optional + /// value. + /// + /// - Parameter generator: The random number generator to use when choosing + /// a random element. + /// - Returns: A random element of the range. +% if 'Closed' not in Range: + /// If the range is empty, the method returns `nil`. +% else: + /// This method never returns `nil`. +% end + @inlinable + public func randomElement( + using generator: inout T + ) -> Element? { + guard !isEmpty else { + return nil + } + let isLowerNegative = Bound.isSigned && lowerBound < 0 + let sameSign = !Bound.isSigned || isLowerNegative == (upperBound < 0) +% if 'Closed' not in Range: + let delta: Bound.Magnitude +% else: + var delta: Bound.Magnitude +% end + if isLowerNegative { + delta = sameSign + ? lowerBound.magnitude - upperBound.magnitude + : lowerBound.magnitude + upperBound.magnitude + } else { + delta = upperBound.magnitude - lowerBound.magnitude + } +% if 'Closed' in Range: + if delta == Bound.Magnitude.max { + return Bound(truncatingIfNeeded: generator.next() as Bound.Magnitude) + } + delta += 1 +% end + let randomMagnitude = generator.next(upperBound: delta) + if sameSign { + return lowerBound + Bound(randomMagnitude) + } else { +% if 'Closed' not in Range: + return randomMagnitude < upperBound.magnitude + ? Bound(randomMagnitude) + : -1 - Bound(randomMagnitude - upperBound.magnitude) +% else: + return Bound.isSigned && randomMagnitude <= upperBound.magnitude + ? Bound(randomMagnitude) + : 0 - Bound(randomMagnitude - upperBound.magnitude) +% end + } + } + + /// Returns a random element of the range, using the given generator as + /// a source for randomness. + /// + /// You can use this method to select a random element of a range when you + /// are using a custom random number generator. If you're generating a random + /// number, in most cases, you should prefer using the `random(in:)` + /// static method of the desired numeric type. That static method is available + /// for both integer and floating point types, and returns a non-optional + /// value. + /// + /// This method uses the default random generator, `Random.default`. Calling + /// `(${exampleRange}).randomElement()` is equivalent to calling + /// `(${exampleRange}).randomElement(using: &Random.default)`. + /// + /// - Returns: A random element of the range. +% if 'Closed' not in Range: + /// If the range is empty, the method returns `nil`. +% else: + /// This method never returns `nil`. +% end + @inlinable + public func randomElement() -> Element? { + return randomElement(using: &Random.default) + } +} + +extension FixedWidthInteger +where Self.Stride : SignedInteger, + Self.Magnitude : UnsignedInteger { + + /// Returns a random value within the specified range, using the given + /// generator as a source for randomness. + /// + /// Use this method to generate an integer within a specific range when you + /// are using a custom random number generator. This example creates three + /// new values in the range `${exampleRange}`. + /// + /// for _ in 1...3 { + /// print(Int.random(in: ${exampleRange}, using: &myGenerator)) + /// } + /// // Prints "7" + /// // Prints "44" + /// // Prints "21" + /// + /// - Parameters: + /// - range: The range in which to create a random value. +% if Range == 'Range': + /// `range` must not be empty. +% end + /// - generator: The random number generator to use when creating the + /// new random value. + /// - Returns: A random value within the bounds of `range`. + @inlinable + public static func random( + in range: ${Range}, + using generator: inout T + ) -> Self { + _precondition( + !range.isEmpty, + "Can't get random value with an empty range" + ) + return range.randomElement(using: &generator)! + } + + /// Returns a random value within the specified range. + /// + /// Use this method to generate an integer within a specific range. This + /// example creates three new values in the range `${exampleRange}`. + /// + /// for _ in 1...3 { + /// print(Int.random(in: ${exampleRange})) + /// } + /// // Prints "53" + /// // Prints "64" + /// // Prints "5" + /// + /// This method uses the default random generator, `Random.default`. The call + /// to `Int.random(in: ${exampleRange})` above is equivalent to calling + /// `Int.random(in: ${exampleRange}, using: &Random.default)`. + /// + /// - Parameter range: The range in which to create a random value. +% if Range == 'Range': + /// `range` must not be empty. +% end + /// - Returns: A random value within the bounds of `range`. + @inlinable + public static func random(in range: ${Range}) -> Self { + return Self.random(in: range, using: &Random.default) + } +} + +% end + //===----------------------------------------------------------------------===// //===--- Operators on FixedWidthInteger -----------------------------------===// //===----------------------------------------------------------------------===// @@ -2860,6 +3025,26 @@ ${assignmentOperatorComment('&' + x.operator, True)} % end } +extension FixedWidthInteger { + public static func _random( + using generator: inout R + ) -> Self { + if bitWidth <= UInt64.bitWidth { + return Self(truncatingIfNeeded: generator.next() as UInt64) + } + + let (quotient, remainder) = bitWidth.quotientAndRemainder( + dividingBy: UInt64.bitWidth + ) + var tmp: Self = 0 + for i in 0 ..< quotient + remainder.signum() { + let next: UInt64 = generator.next() + tmp += Self(truncatingIfNeeded: next) &<< (UInt64.bitWidth * i) + } + return tmp + } +} + //===----------------------------------------------------------------------===// //===--- UnsignedInteger --------------------------------------------------===// //===----------------------------------------------------------------------===// @@ -3856,6 +4041,16 @@ public func _assumeNonNegative(_ x: ${Self}) -> ${Self} { } % end +extension ${Self} { + public static func _random( + using generator: inout R + ) -> ${Self} { + var result: ${Self} = 0 + withUnsafeMutableBytes(of: &result) { generator._fill(bytes: $0) } + return result + } +} + //===--- end of FIXME(integers) -------------------------------------------===// % end # end of concrete FixedWidthInteger section diff --git a/stdlib/public/core/Random.swift b/stdlib/public/core/Random.swift new file mode 100644 index 0000000000000..38e7043f25343 --- /dev/null +++ b/stdlib/public/core/Random.swift @@ -0,0 +1,157 @@ +//===--- Random.swift -----------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2018 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import SwiftShims + +/// A type that can provide uniformly distributed random data. +/// +/// When you call methods that use random data, such as creating new random +/// values or shuffling a collection, you can pass a `RandomNumberGenerator` +/// type to be used as the source for randomness. When you don't pass a +/// generator, the default `Random` type is used. +/// +/// When providing new APIs that use randomness, provide a version that accepts +/// a generator conforming to the `RandomNumberGenerator` protocol as well as a +/// version that uses the default generator. For example, this `Weekday` +/// enumeration provides static methods that return a random day of the week: +/// +/// enum Weekday : CaseIterable { +/// case sunday, monday, tuesday, wednesday, thursday, friday, saturday +/// +/// static func randomWeekday(using generator: inout G) -> Weekday { +/// return Weekday.allCases.randomElement(using: &generator)! +/// } +/// +/// static func randomWeekday() -> Weekday { +/// return Weekday.randomWeekday(using: &Random.default) +/// } +/// } +/// +/// Conforming to the RandomNumberGenerator Protocol +/// ================================================ +/// +/// A custom `RandomNumberGenerator` type can have different characteristics +/// than the default `Random` type. For example, a seedable generator can be +/// used to generate the same sequence of random values for testing purposes. +/// +/// To make a custom type conform to the `RandomNumberGenerator` protocol, +/// implement the required `next()` method. Each call to `next()` must produce +/// a uniform and independent random value. +/// +/// Types that conform to `RandomNumberGenerator` should specifically document +/// the thread safety and quality of the generator. +public protocol RandomNumberGenerator { + /// Returns a value from a uniform, independent distribution of binary data. + /// + /// - Returns: An unsigned 64-bit random value. + mutating func next() -> UInt64 + + // FIXME: De-underscore after swift-evolution amendment + mutating func _fill(bytes buffer: UnsafeMutableRawBufferPointer) +} + +extension RandomNumberGenerator { + public mutating func _fill(bytes buffer: UnsafeMutableRawBufferPointer) { + // FIXME: Optimize + var chunk: UInt64 = 0 + var chunkBytes = 0 + for i in 0..>= UInt8.bitWidth + chunkBytes -= 1 + } + } +} + +extension RandomNumberGenerator { + /// Returns a value from a uniform, independent distribution of binary data. + /// + /// - Returns: A random value of `T`. Bits are randomly distributed so that + /// every value of `T` is equally likely to be returned. + @inlinable + public mutating func next() -> T { + return T._random(using: &self) + } + + /// Returns a random value that is less than the given upper bound. + /// + /// - Parameter upperBound: The upper bound for the randomly generated value. + /// - Returns: A random value of `T` in the range `0..( + upperBound: T + ) -> T { + guard upperBound != 0 else { return 0 } + let tmp = (T.max % upperBound) + 1 + let range = tmp == upperBound ? 0 : tmp + var random: T = 0 + + repeat { + random = next() + } while random < range + + return random % upperBound + } +} + +/// The default source of random data. +/// +/// When you generate random values, shuffle a collection, or perform another +/// operation that depends on random data, this type's `default` property is +/// the generator used by default. For example, the two method calls in this +/// example are equivalent: +/// +/// let x = Int.random(in: 1...100) +/// let y = Int.random(in: 1...100, using: &Random.default) +/// +/// `Random.default` is safe to use in multiple threads, and uses a +/// cryptographically secure algorithm whenever possible. +/// +/// Platform Implementation of `Random` +/// =================================== +/// +/// - Apple platforms all use `arc4random_buf(3)`. +/// - `Linux`, `Android`, `Cygwin`, `Haiku`, `FreeBSD`, and `PS4` all try to +/// use `getrandom(2)`, but if it doesn't exist then they read from +/// `/dev/urandom`. +/// - `Fuchsia` calls `getentropy(3)`. +/// - `Windows` calls `BCryptGenRandom`. +public struct Random : RandomNumberGenerator { + /// The default instance of the `Random` random number generator. + public static var `default`: Random { + get { return Random() } + set { /* Discard */ } + } + + private init() {} + + /// Returns a value from a uniform, independent distribution of binary data. + /// + /// - Returns: An unsigned 64-bit random value. + @effects(releasenone) + public mutating func next() -> UInt64 { + var random: UInt64 = 0 + _stdlib_random(&random, MemoryLayout.size) + return random + } + + public mutating func _fill(bytes buffer: UnsafeMutableRawBufferPointer) { + if !buffer.isEmpty { + _stdlib_random(buffer.baseAddress!, buffer.count) + } + } +} diff --git a/stdlib/public/stubs/LibcShims.cpp b/stdlib/public/stubs/LibcShims.cpp index 7d66992c3e5e7..0ac113cd21aa7 100644 --- a/stdlib/public/stubs/LibcShims.cpp +++ b/stdlib/public/stubs/LibcShims.cpp @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information @@ -14,31 +14,41 @@ #define _REENTRANT #include #endif -#include -#include -#include -#if defined(_WIN32) + +#if defined(_WIN32) && !defined(__CYGWIN__) #include #define WIN32_LEAN_AND_MEAN #include +#include +#pragma comment(lib, "Bcrypt.lib") #else -#include #include #include #include +#include #endif -#include +#include +#include +#include +#include +#include #include +#include #include -#include -#include -#include +#if __has_include() +#include +#endif #include +#include +#include + +#include "llvm/Support/DataTypes.h" #include "swift/Basic/Lazy.h" #include "swift/Runtime/Config.h" +#include "swift/Runtime/Debug.h" +#include "swift/Runtime/Mutex.h" #include "../SwiftShims/LibcShims.h" -#include "llvm/Support/DataTypes.h" using namespace swift; @@ -308,3 +318,76 @@ swift::_stdlib_cxx11_mt19937_uniform(__swift_uint32_t upper_bound) { std::uniform_int_distribution<__swift_uint32_t> RandomUniform(0, upper_bound); return RandomUniform(getGlobalMT19937()); } + +// _stdlib_random +// +// Should the implementation of this function add a new platform/change for a +// platform, make sure to also update the stdlib documentation regarding +// platform implementation of this function. +// This can be found at: stdlib/public/core/Random.swift + +#if defined(__APPLE__) + +SWIFT_RUNTIME_STDLIB_INTERNAL +void swift::_stdlib_random(void *buf, __swift_size_t nbytes) { + arc4random_buf(buf, nbytes); +} + +#elif defined(_WIN32) && !defined(__CYGWIN__) +#warning TODO: Test _stdlib_random on Windows + +SWIFT_RUNTIME_STDLIB_INTERNAL +void swift::_stdlib_random(void *buf, __swift_size_t nbytes) { + NTSTATUS status = BCryptGenRandom(nullptr, + static_cast(buf), + static_cast(nbytes), + BCRYPT_USE_SYSTEM_PREFERRED_RNG); + if (!BCRYPT_SUCCESS(status)) { + fatalError(0, "Fatal error: 0x%.8X in '%s'\n", status, __func__); + } +} + +#else + +#undef WHILE_EINTR +#define WHILE_EINTR(expression) ({ \ + decltype(expression) result = -1; \ + do { result = (expression); } while (result == -1 && errno == EINTR); \ + result; \ +}) + +SWIFT_RUNTIME_STDLIB_INTERNAL +void swift::_stdlib_random(void *buf, __swift_size_t nbytes) { + while (nbytes > 0) { + __swift_ssize_t actual_nbytes = -1; +#if defined(GRND_RANDOM) + static const bool getrandom_available = + !(getrandom(nullptr, 0, 0) == -1 && errno == ENOSYS); + if (getrandom_available) { + actual_nbytes = WHILE_EINTR(getrandom(buf, nbytes, 0)); + } +#elif defined(__Fuchsia__) + __swift_size_t getentropy_nbytes = std::min(nbytes, __swift_size_t{256}); + if (0 == getentropy(buf, getentropy_nbytes)) { + actual_nbytes = getentropy_nbytes; + } +#endif + if (actual_nbytes == -1) { + static const int fd = + WHILE_EINTR(_stdlib_open("/dev/urandom", O_RDONLY | O_CLOEXEC, 0)); + if (fd != -1) { + static StaticMutex mutex; + mutex.withLock([&] { + actual_nbytes = WHILE_EINTR(_stdlib_read(fd, buf, nbytes)); + }); + } + } + if (actual_nbytes == -1) { + fatalError(0, "Fatal error: %d in '%s'\n", errno, __func__); + } + buf = static_cast(buf) + actual_nbytes; + nbytes -= actual_nbytes; + } +} + +#endif diff --git a/test/stdlib/Random.swift b/test/stdlib/Random.swift new file mode 100644 index 0000000000000..3d767f4f99f6f --- /dev/null +++ b/test/stdlib/Random.swift @@ -0,0 +1,261 @@ +// RUN: %target-run-stdlib-swift +// REQUIRES: executable_test + +import StdlibUnittest +import StdlibCollectionUnittest + +let RandomTests = TestSuite("Random") + +// _fill(bytes:) + +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) + + bytes1.withUnsafeMutableBytes { Random.default._fill(bytes: $0) } + expectNotEqual(bytes1, bytes2) + expectNotEqual(bytes1, zeros) + + bytes2.withUnsafeMutableBytes { Random.default._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(_ type: T.Type) + where T.Stride: SignedInteger, T.Magnitude: UnsignedInteger { + + func testRange(_ range: Range, iterations: Int = 1_000) { + var integerSet: Set = [] + 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, iterations: Int = 1_000) { + var integerSet: Set = [] + 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(_ type: T.Type) + where T.RawSignificand: FixedWidthInteger, + T.RawSignificand.Stride: SignedInteger & FixedWidthInteger, + T.RawSignificand.Magnitude: UnsignedInteger { + + let testRange = 0 ..< 1_000 + + // open range + let openRange: Range = 0.0 ..< 10.0 + for _ in testRange { + let random = T.random(in: openRange) + expectTrue(openRange.contains(random)) + } + + // closed range + let closedRange: ClosedRange = 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) + floatingPointRangeTest(Float80.self) +} + +// Random Elements from collection + +RandomTests.test("random elements from collection") { + // Non-empty collection + var elementSet: Set = [] + 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]) +} + +// Uniform Distribution + +func chi2Test(_ samples: [Double]) -> Bool { + precondition(samples.count == 50, "confidence interval requires 50 samples") + let expected = samples.reduce(0, +) / Double(samples.count) + let cvLow = 23.983 // 0.1% with a degree of freedom of (50 - 1) + let cvHigh = 85.351 // 99.9% with a degree of freedom of (50 - 1) + let chi2 = samples.map { + (($0 - expected) * ($0 - expected)) / expected + }.reduce(0, +) + + if chi2 < cvLow || 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()