From a752c966ec8b97913bb5f0277ae904ed68605551 Mon Sep 17 00:00:00 2001 From: Azoy Date: Fri, 3 Nov 2017 21:51:13 -0500 Subject: [PATCH 1/7] Add shims for stdlib random Initial random api Use C syscall for I/O 1. Fixed an issue where integers would would result in an infinite loop if they were unsigned, or signed integers always returning negative numbers. 2. Fixed an issue with Bool initialization Add shuffle functions Add documentation to Random API Fix a few typos within the documentation Fixes more typos Also states that the range for floating points is from 0 to 1 inclusive Update API to reflect mailing list discussions Remove unnecessary import Make sure not to return upperBound on Range Use SecRandomCopyBytes on older macOS Update API to match mailing list discussion, add tests Added pick(_:) to collection Added random(in:using:) to Randomizable Added tests Fix typo in Randomizable documentation Rename pick to sampling Move sampling below random Update docs Use new Libc naming Fix Random.swift with new Libc naming Remove sampling gybify signed integer creation Make FloatingPoint.random exclusive Refactor {Closed}Range.random Fix FloatingPoint initialization Precondition getting a random number from range Fix some doc typos Make .random a function Update API to reflect discussion Make .random a function Remove .random() in favor of .random(in:) for all numeric types Fix compile errors Clean up _stdlib_random Cleanup around API Remove `.random()` requirement from `Collection` Use generators Optimize shuffle() Thread safety for /dev/urandom Remove {Closed}Range.random() Add Collection random requirement Refactor _stdlib_random Remove whitespace changes Clean linux shim Add shuffle and more tests Provide finishing tests and suggestions Remove refs to Countable ranges Revert to checking if T is > UInt64 (cherry picked from commit d23d219e951bf92a5f40b4f476303fb3c1cd303b) --- stdlib/public/SwiftShims/LibcShims.h | 4 + stdlib/public/core/Bool.swift | 12 + stdlib/public/core/CMakeLists.txt | 2 + stdlib/public/core/ClosedRange.swift | 51 ++++ stdlib/public/core/Collection.swift | 32 +++ stdlib/public/core/CollectionAlgorithms.swift | 44 ++++ stdlib/public/core/FloatingPoint.swift.gyb | 47 ++++ stdlib/public/core/GroupInfo.json | 1 + stdlib/public/core/Integers.swift.gyb | 30 +++ stdlib/public/core/Random.swift | 128 ++++++++++ stdlib/public/core/Range.swift | 50 ++++ stdlib/public/stubs/LibcShims.cpp | 44 ++++ test/stdlib/Random.swift | 225 ++++++++++++++++++ 13 files changed, 670 insertions(+) create mode 100644 stdlib/public/core/Random.swift create mode 100644 test/stdlib/Random.swift diff --git a/stdlib/public/SwiftShims/LibcShims.h b/stdlib/public/SwiftShims/LibcShims.h index 5f6e995a8227f..2f31553084eec 100644 --- a/stdlib/public/SwiftShims/LibcShims.h +++ b/stdlib/public/SwiftShims/LibcShims.h @@ -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 6de6bcf69425f..360e8bababbfc 100644 --- a/stdlib/public/core/Bool.swift +++ b/stdlib/public/core/Bool.swift @@ -86,6 +86,18 @@ public struct Bool { public init(_ value: Bool) { self = value } + + /// Returns a random Boolean + /// + /// - Parameter generator: The random number generator to use when getting a + /// random Boolean. + /// - Returns: A random Boolean. + @_inlineable + public static func random( + using generator: RandomNumberGenerator = Random.default + ) -> Bool { + return generator.next() % 2 == 0 + } } extension Bool : _ExpressibleByBuiltinBooleanLiteral, ExpressibleByBooleanLiteral { diff --git a/stdlib/public/core/CMakeLists.txt b/stdlib/public/core/CMakeLists.txt index 5b21edda7f0e9..110b334575f83 100644 --- a/stdlib/public/core/CMakeLists.txt +++ b/stdlib/public/core/CMakeLists.txt @@ -97,6 +97,7 @@ set(SWIFTLIB_ESSENTIAL Policy.swift PrefixWhile.swift Print.swift + Random.swift RandomAccessCollection.swift Range.swift RangeReplaceableCollection.swift @@ -193,6 +194,7 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") list(APPEND swift_core_link_flags "-all_load") list(APPEND swift_core_framework_depends Foundation) list(APPEND swift_core_framework_depends CoreFoundation) + list(APPEND swift_core_framework_depends Security) list(APPEND swift_core_private_link_libraries icucore) else() # With the GNU linker the equivalent of -all_load is to tell the linker diff --git a/stdlib/public/core/ClosedRange.swift b/stdlib/public/core/ClosedRange.swift index 1a449281a8ddc..b59d1d8426ec9 100644 --- a/stdlib/public/core/ClosedRange.swift +++ b/stdlib/public/core/ClosedRange.swift @@ -490,6 +490,57 @@ extension ClosedRange where Bound: Strideable, Bound.Stride : SignedInteger { } } +extension ClosedRange +where Bound : FixedWidthInteger, + Bound.Magnitude : UnsignedInteger { + + /// Returns a random element from this collection. + /// + /// - Parameter generator: The random number generator to use when getting + /// a random element. + /// - Returns: A random element from this collection. + /// + /// A good example of this is getting a random greeting from an array: + /// + /// let greetings = ["hi", "hey", "hello", "hola"] + /// let randomGreeting = greetings.random() + /// + /// If the collection is empty, the value of this function is `nil`. + /// + /// let numbers = [10, 20, 30, 40, 50] + /// if let randomNumber = numbers.random() { + /// print(randomNumber) + /// } + /// // Could print "20", perhaps + @_inlineable + public func random( + using generator: RandomNumberGenerator = Random.default + ) -> Element? { + let isLowerNegative = Bound.isSigned && lowerBound < 0 + let sameSign = !Bound.isSigned || isLowerNegative == (upperBound < 0) + var delta: Bound.Magnitude + if isLowerNegative { + delta = sameSign + ? lowerBound.magnitude - upperBound.magnitude + : lowerBound.magnitude + upperBound.magnitude + } else { + delta = upperBound.magnitude - lowerBound.magnitude + } + if delta == Bound.Magnitude.max { + return Bound(truncatingIfNeeded: generator.next() as Bound.Magnitude) + } + delta += 1 + let randomMagnitude = generator.next(upperBound: delta) + if sameSign { + return lowerBound + Bound(randomMagnitude) + } else { + return Bound.isSigned && randomMagnitude <= upperBound.magnitude + ? Bound(randomMagnitude) + : 0 - Bound(randomMagnitude - upperBound.magnitude) + } + } +} + extension ClosedRange { @inlinable public func overlaps(_ other: ClosedRange) -> Bool { diff --git a/stdlib/public/core/Collection.swift b/stdlib/public/core/Collection.swift index cb90f62e69bc5..5c13c6c1598f9 100644 --- a/stdlib/public/core/Collection.swift +++ b/stdlib/public/core/Collection.swift @@ -1016,6 +1016,38 @@ extension Collection { return count } + /// Returns a random element from this collection. + /// + /// - Parameter generator: The random number generator to use when getting + /// a random element. + /// - Returns: A random element from this collection. + /// + /// A good example of this is getting a random greeting from an array: + /// + /// let greetings = ["hi", "hey", "hello", "hola"] + /// let randomGreeting = greetings.random() + /// + /// If the collection is empty, the value of this function is `nil`. + /// + /// let numbers = [10, 20, 30, 40, 50] + /// if let randomNumber = numbers.random() { + /// print(randomNumber) + /// } + /// // Could print "20", perhaps + @_inlineable + public func random( + using generator: RandomNumberGenerator = Random.default + ) -> Element? { + guard !isEmpty else { return nil } + let random = generator.next(upperBound: UInt(self.count)) + let index = self.index( + self.startIndex, + offsetBy: IndexDistance(random), + limitedBy: self.endIndex + ) + return self[index] + } + /// 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..d17b8f6fbbd52 100644 --- a/stdlib/public/core/CollectionAlgorithms.swift +++ b/stdlib/public/core/CollectionAlgorithms.swift @@ -367,6 +367,50 @@ extension MutableCollection where Self : BidirectionalCollection { } } +//===----------------------------------------------------------------------===// +// shuffled() +//===----------------------------------------------------------------------===// + +extension Sequence { + /// Returns the elements of the sequence, shuffled. + /// + /// - Parameter generator: The random number generator to use when shuffling + /// the sequence. + /// - Returns: A shuffled array of this sequence's elements. + @_inlineable + public func shuffled( + using generator: RandomNumberGenerator = Random.default + ) -> [Element] { + var result = ContiguousArray(self) + result.shuffle(using: generator) + return Array(result) + } +} + +extension MutableCollection { + /// Shuffles the collection in place. + /// + /// - Parameter generator: The random number generator to use when shuffling + /// the collection. + @_inlineable + public mutating func shuffle( + using generator: RandomNumberGenerator = Random.default + ) { + 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) + } + } +} + //===----------------------------------------------------------------------===// // sorted()/sort() //===----------------------------------------------------------------------===// diff --git a/stdlib/public/core/FloatingPoint.swift.gyb b/stdlib/public/core/FloatingPoint.swift.gyb index 3ccea1d363b02..645745ad58bfc 100644 --- a/stdlib/public/core/FloatingPoint.swift.gyb +++ b/stdlib/public/core/FloatingPoint.swift.gyb @@ -2374,6 +2374,53 @@ extension BinaryFloatingPoint { */ } +% for Range in ['Range', 'ClosedRange']: + +extension BinaryFloatingPoint +where Self.RawSignificand : FixedWidthInteger, + Self.RawSignificand.Stride : SignedInteger & FixedWidthInteger, + Self.RawSignificand.Magnitude : UnsignedInteger { + + /// Returns a random representation of this floating point within the range. + /// + /// - Parameter range: A ${Range} to determine the bounds to get a random value + /// from. + /// - Parameter generator: The random number generator to use when getting + /// the random floating point. + /// - Returns: A random representation of this floating point. + @_inlineable + public static func random( + in range: ${Range}, + using generator: RandomNumberGenerator = Random.default + ) -> Self { +% if 'Closed' not in Range: + _precondition( + range.lowerBound != range.upperBound, + "Can't get random value with lowerBound == upperBound" + ) +% end + let delta = range.upperBound - range.lowerBound + let maxSignificand: Self.RawSignificand = 1 << Self.significandBitCount +% if 'Closed' not in Range: + let rand: Self.RawSignificand = generator.next(upperBound: maxSignificand) +% else: + let rand: Self.RawSignificand = generator.next(upperBound: maxSignificand + 1) + if rand == maxSignificand { + return range.upperBound + } +% end + let unitRandom = Self.init( + sign: .plus, + exponentBitPattern: (1 as Self).exponentBitPattern, + significandBitPattern: rand + ) - 1 + return delta * unitRandom + range.lowerBound + } + +} + +% 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 234273c5c2d04..d1a146decb306 100644 --- a/stdlib/public/core/Integers.swift.gyb +++ b/stdlib/public/core/Integers.swift.gyb @@ -2530,6 +2530,36 @@ ${assignmentOperatorComment(x.operator, False)} % end } +% for Range in ['Range', 'ClosedRange']: + +extension FixedWidthInteger +where Self.Stride : SignedInteger, + Self.Magnitude : UnsignedInteger { + + /// Returns a random representation of this integer within the range. + /// + /// - Parameter range: A ${Range} to determine the bounds to get a random value + /// from. + /// - Parameter generator: The random number generator to use when getting + /// the random integer. + /// - Returns: A random representation of this integer. + @_inlineable + public static func random( + in range: ${Range}, + using generator: RandomNumberGenerator = Random.default + ) -> Self { +% if 'Closed' not in Range: + _precondition( + range.lowerBound != range.upperBound, + "Can't get random value with lowerBound == upperBound" + ) +% end + return range.random(using: generator)! + } +} + +% end + //===----------------------------------------------------------------------===// //===--- Operators on FixedWidthInteger -----------------------------------===// //===----------------------------------------------------------------------===// diff --git a/stdlib/public/core/Random.swift b/stdlib/public/core/Random.swift new file mode 100644 index 0000000000000..bc05231d395c6 --- /dev/null +++ b/stdlib/public/core/Random.swift @@ -0,0 +1,128 @@ +//===--- Random.swift -----------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2017 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 allows random number generators to be used throughout Swift to +/// produce random aspects of Swift's types. +/// +/// The RandomNumberGenerator protocol is used for random number generator types +/// that implement their own pseudo-random or cryptographically secure +/// pseudo-random number generation. Using the RandomNumberGenerator protocol +/// allows these types to be used with types with random methods, collections +/// with .random(), and collections/sequences with +/// .shuffle() and .shuffled() +/// +/// Conforming to the RandomNumberGenerator protocol +/// ========================================== +/// +/// In order to conform to RandomNumberGenerator, types that implement this must be +/// able to produce an unsigned integer. +/// +/// - Note: Types that implement RandomNumberGenerator should have a decent +/// amount of documentation that clearly conveys whether or not the generator +/// produces cryptographically secure numbers or deterministically generated +/// numbers. +public protocol RandomNumberGenerator { + /// Produces the next randomly generated number + /// + /// - Returns: A number that was randomly generated + func next() -> UInt64 +} + +extension RandomNumberGenerator { + /// Produces the next randomly generated number + /// + /// - Returns: A number that was randomly generated + /// + /// This differs from next() as this function has the ability to transform the + /// generated number to any unsigned integer. + @_inlineable + public func next() -> T { + if T.bitWidth == UInt64.bitWidth { + return T(self.next()) + } + + let (quotient, remainder) = T.bitWidth.quotientAndRemainder( + dividingBy: UInt64.bitWidth + ) + var tmp: T = 0 + + for i in 0 ..< quotient { + tmp += T(truncatingIfNeeded: self.next()) &<< (UInt64.bitWidth * i) + } + + if remainder != 0 { + let random = self.next() + let mask = UInt64.max &>> (UInt64.bitWidth - remainder) + tmp += T(truncatingIfNeeded: random & mask) &<< (UInt64.bitWidth * quotient) + } + + return tmp + } + + /// Produces the next randomly generated number that is constricted by an + /// upperBound + /// + /// - Parameter upperBound: The max number this can generate up to. + /// - Returns: A number that was randomly generated from 0 to upperBound + /// + /// This uses the uniform distribution to form a random number within the + /// upperBound. + @_inlineable + public func next(upperBound: T) -> T { + let range = T.max % upperBound + var random: T = 0 + + repeat { + random = self.next() + } while random < range + + return random % upperBound + } +} + +/// The provided default source of random numbers +/// +/// All of the default provided random functions utilize this source of random. +/// Using those functions should be preferred over using this directly. An +/// example of calling this directly: +/// +/// let random: UInt8 = Random.default.next() +/// let randomToTen: UInt32 = Random.default.next(upperBound: 10) +/// +/// However, you should strive to use the random functions on the numeric types. +/// Using the preferred way: +/// +/// let random = UInt8.random(in: .min ... .max) +/// let randomToTen = UInt32.random(in: 0 ..< 10) +/// +/// - Note: The default implementation of randomness is cryptographically secure. +/// It utilizes arc4random(3) on newer versions of macOS, iOS, etc. On older +/// versions of these operating systems it uses SecRandomCopyBytes. For Linux, +/// it tries to use the getrandom(2) system call on newer kernel versions. On +/// older kernel versions, it reads from /dev/urandom. +public struct Random : RandomNumberGenerator { + /// The default random implementation + public static let `default` = Random() + + private init() {} + + /// Produces the next randomly generated number + /// + /// - Returns: A number that was randomly generated + public func next() -> UInt64 { + var random: UInt64 = 0 + _stdlib_random(&random, MemoryLayout.size) + return random + } +} diff --git a/stdlib/public/core/Range.swift b/stdlib/public/core/Range.swift index eb9db799526be..7fc08cabbb559 100644 --- a/stdlib/public/core/Range.swift +++ b/stdlib/public/core/Range.swift @@ -303,6 +303,56 @@ extension Range where Bound: Strideable, Bound.Stride : SignedInteger { } } +extension Range +where Bound : FixedWidthInteger, + Bound.Magnitude : UnsignedInteger { + + /// Returns a random element from this collection. + /// + /// - Parameter generator: The random number generator to use when getting + /// a random element. + /// - Returns: A random element from this collection. + /// + /// A good example of this is getting a random greeting from an array: + /// + /// let greetings = ["hi", "hey", "hello", "hola"] + /// let randomGreeting = greetings.random() + /// + /// If the collection is empty, the value of this function is `nil`. + /// + /// let numbers = [10, 20, 30, 40, 50] + /// if let randomNumber = numbers.random() { + /// print(randomNumber) + /// } + /// // Could print "20", perhaps + @_inlineable + public func random( + using generator: RandomNumberGenerator = Random.default + ) -> Element? { + guard lowerBound != upperBound else { + return nil + } + let isLowerNegative = Bound.isSigned && lowerBound < 0 + let sameSign = !Bound.isSigned || isLowerNegative == (upperBound < 0) + let delta: Bound.Magnitude + if isLowerNegative { + delta = sameSign + ? lowerBound.magnitude - upperBound.magnitude + : lowerBound.magnitude + upperBound.magnitude + } else { + delta = upperBound.magnitude - lowerBound.magnitude + } + let randomMagnitude = generator.next(upperBound: delta) + if sameSign { + return lowerBound + Bound(randomMagnitude) + } else { + return randomMagnitude < upperBound.magnitude + ? Bound(randomMagnitude) + : -1 - Bound(randomMagnitude - upperBound.magnitude) + } + } +} + extension Range: RangeExpression { /// Returns the range of indices described by this range expression within /// the given collection. diff --git a/stdlib/public/stubs/LibcShims.cpp b/stdlib/public/stubs/LibcShims.cpp index 7d66992c3e5e7..4b1cf7af6530b 100644 --- a/stdlib/public/stubs/LibcShims.cpp +++ b/stdlib/public/stubs/LibcShims.cpp @@ -37,6 +37,7 @@ #include #include "swift/Basic/Lazy.h" #include "swift/Runtime/Config.h" +#include "swift/Runtime/Debug.h" #include "../SwiftShims/LibcShims.h" #include "llvm/Support/DataTypes.h" @@ -308,3 +309,46 @@ swift::_stdlib_cxx11_mt19937_uniform(__swift_uint32_t upper_bound) { std::uniform_int_distribution<__swift_uint32_t> RandomUniform(0, upper_bound); return RandomUniform(getGlobalMT19937()); } + +#if defined(__APPLE__) +#include +SWIFT_RUNTIME_STDLIB_INTERNAL +void swift::_stdlib_random(void *buf, __swift_size_t nbytes) { + if (__builtin_available(macOS 10.12, iOS 10, tvOS 10, watchOS 3, *)) { + arc4random_buf(buf, nbytes); + }else { + OSStatus status = SecRandomCopyBytes(kSecRandomDefault, nbytes, buf); + if (status != errSecSuccess) { + fatalError(0, "Fatal error: SecRandomCopyBytes failed with error %d\n", status); + } + } +} +#elif defined(__linux__) +#include +#include +SWIFT_RUNTIME_STDLIB_INTERNAL +void swift::_stdlib_random(void *buf, __swift_size_t nbytes) { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,17,0) + auto _read = [&]() -> __swift_ssize_t { + return syscall(SYS_getrandom, buf, bytes, 0); + }; +#else + auto _read = [&]() -> __swift_ssize_t { + static const int fd = _stdlib_open("/dev/urandom", O_RDONLY); + if (fd < 0) { + fatalError(0, "Unable to open '/dev/urandom'\n"); + } + return _stdlib_read(fd, buf, bytes); + }; +#endif + while (nbytes > 0) { + auto result = _read(); + if (result < 1) { + if (errno == EINTR) { continue; } + fatalError(0, "Unable to read '/dev/urandom'\n"); + } + buf = static_cast(buf) + result; + nbytes -= result; + } +} +#endif diff --git a/test/stdlib/Random.swift b/test/stdlib/Random.swift new file mode 100644 index 0000000000000..ecd384e8ed2a2 --- /dev/null +++ b/test/stdlib/Random.swift @@ -0,0 +1,225 @@ +// RUN: %target-run-simple-swift +// REQUIRES: executable_test + +import StdlibUnittest + +let RandomTests = TestSuite("Random") + +// Basic random numbers + +RandomTests.test("basic random numbers") { + let randomNumber1 = Int.random(in: .min ... .max) + let randomNumber2 = Int.random(in: .min ... .max) + expectTrue(randomNumber1 != randomNumber2) + + let randomDouble1 = Double.random(in: 0 ..< 1) + expectTrue(randomDouble1 < 1 && randomDouble1 >= 0) + let randomDouble2 = Double.random(in: 0 ..< 1) + expectTrue(randomDouble1 < 1 && randomDouble2 >= 0) + expectTrue(randomDouble1 != randomDouble2) +} + +// Random integers in ranges + +func integerRangeTest(_ type: T.Type) + where T.Stride: SignedInteger, T.Magnitude: UnsignedInteger { + + let testRange = 0 ..< 1_000 + var integerSet: Set = [] + + // min open range + let minOpenRange = T.min ..< (T.min + 10) + for _ in testRange { + let random = T.random(in: minOpenRange) + expectTrue(minOpenRange.contains(random)) + integerSet.insert(random) + } + expectTrue(integerSet == Set(T.min ..< (T.min + 10))) + integerSet.removeAll() + + // min closed range + let minClosedRange = T.min ... (T.min + 10) + for _ in testRange { + let random = T.random(in: minClosedRange) + expectTrue(minClosedRange.contains(random)) + integerSet.insert(random) + } + expectTrue(integerSet == Set(T.min ... (T.min + 10))) + integerSet.removeAll() + + // max open range + let maxOpenRange = (T.max - 10) ..< T.max + for _ in testRange { + let random = T.random(in: maxOpenRange) + expectTrue(maxOpenRange.contains(random)) + integerSet.insert(random) + } + expectTrue(integerSet == Set((T.max - 10) ..< T.max)) + integerSet.removeAll() + + // max closed range + let maxClosedRange = (T.max - 10) ... T.max + for _ in testRange { + let random = T.random(in: maxClosedRange) + expectTrue(maxClosedRange.contains(random)) + integerSet.insert(random) + } + expectTrue(integerSet == Set((T.max - 10) ... T.max)) +} + +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") { + let greetings = ["hello", "hi", "hey", "hola", "what's up"] + for _ in 0 ..< 1_000 { + let randomGreeting = greetings.random() + expectNotNil(randomGreeting) + expectTrue(greetings.contains(randomGreeting!)) + } +} + +// Shuffle + +RandomTests.test("shuffling") { + var 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"] + for _ in 0 ..< 1_000 { + let newAlphabet = alphabet.shuffled() + expectTrue(newAlphabet != alphabet) + alphabet = newAlphabet + } +} + +// Different RNGS + +public class 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 func next() -> UInt32 { + state = (LCRNG.a &* state &+ LCRNG.c) % LCRNG.m + return UInt32(truncatingIfNeeded: state >> 15) + } + + public 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 + let 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).random(using: rng) + expectNotNil(randomIntFromCollection) + collectionPasses[i].append(randomIntFromCollection!) + + let randomShuffledCollection = Array(0 ... 100).shuffled(using: rng) + shufflePasses[i].append(randomShuffledCollection) + } + } + + expectTrue(intPasses[0] == intPasses[1]) + expectTrue(doublePasses[0] == doublePasses[1]) + expectTrue(boolPasses[0] == boolPasses[1]) + expectTrue(collectionPasses[0] == collectionPasses[1]) + expectTrue(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() \ No newline at end of file From 5f9522259ab797d0733af9a8995a7051b59d7acc Mon Sep 17 00:00:00 2001 From: Ben Rimmington Date: Sat, 10 Feb 2018 00:09:14 +0000 Subject: [PATCH 2/7] Add `_stdlib_random` for more platforms (#1) * Remove refs to Countable ranges * Add `_stdlib_random` for more platforms * Use `getrandom` (if available) for Android, Cygwin * Reorder the `_stdlib_random` functions * Also include on Linux * Add `#error TODO` in `_stdlib_random` for Windows * Colon after Fatal Error Performance improvement for Random gybify ranges Fix typo in 'basic random numbers' Add _stdlib_random as a testable method Switch to generic constraints Hopefully link against bcrypt Fix some implementation details 1. Uniform distribution is now uniform 2. Apply Jens' method for uniform floats Fix a lineable attribute (cherry picked from commit a5df0ef83d8ef83ac27a8ce8ac1608491cf7648c) --- stdlib/public/core/Bool.swift | 18 ++- stdlib/public/core/CMakeLists.txt | 4 + stdlib/public/core/ClosedRange.swift | 51 ------- stdlib/public/core/Collection.swift | 33 ++++- stdlib/public/core/CollectionAlgorithms.swift | 37 ++++- stdlib/public/core/FloatingPoint.swift.gyb | 27 ++-- stdlib/public/core/Integers.swift.gyb | 113 +++++++++++++- stdlib/public/core/Random.swift | 34 ++++- stdlib/public/core/Range.swift | 50 ------- stdlib/public/stubs/LibcShims.cpp | 140 +++++++++++++----- test/stdlib/Random.swift | 2 +- 11 files changed, 334 insertions(+), 175 deletions(-) diff --git a/stdlib/public/core/Bool.swift b/stdlib/public/core/Bool.swift index 360e8bababbfc..58be7d7908eb6 100644 --- a/stdlib/public/core/Bool.swift +++ b/stdlib/public/core/Bool.swift @@ -92,12 +92,24 @@ public struct Bool { /// - Parameter generator: The random number generator to use when getting a /// random Boolean. /// - Returns: A random Boolean. - @_inlineable - public static func random( - using generator: RandomNumberGenerator = Random.default + @inlinable + public static func random( + using generator: T ) -> Bool { return generator.next() % 2 == 0 } + + /// Returns a random Boolean + /// + /// - Parameter generator: The random number generator to use when getting a + /// random Boolean. + /// - Returns: A random Boolean. + /// + /// 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 110b334575f83..866b3d44d0255 100644 --- a/stdlib/public/core/CMakeLists.txt +++ b/stdlib/public/core/CMakeLists.txt @@ -229,6 +229,10 @@ if(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") ${EXECINFO_LIBRARY}) endif() +if(CMAKE_SYSTEM_NAME STREQUAL "Windows") + list(APPEND swift_core_link_flags "$ENV{SystemRoot}/system32/bcrypt.dll") +endif() + option(SWIFT_CHECK_ESSENTIAL_STDLIB "Check core standard library layering by linking its essential subset" FALSE) diff --git a/stdlib/public/core/ClosedRange.swift b/stdlib/public/core/ClosedRange.swift index b59d1d8426ec9..1a449281a8ddc 100644 --- a/stdlib/public/core/ClosedRange.swift +++ b/stdlib/public/core/ClosedRange.swift @@ -490,57 +490,6 @@ extension ClosedRange where Bound: Strideable, Bound.Stride : SignedInteger { } } -extension ClosedRange -where Bound : FixedWidthInteger, - Bound.Magnitude : UnsignedInteger { - - /// Returns a random element from this collection. - /// - /// - Parameter generator: The random number generator to use when getting - /// a random element. - /// - Returns: A random element from this collection. - /// - /// A good example of this is getting a random greeting from an array: - /// - /// let greetings = ["hi", "hey", "hello", "hola"] - /// let randomGreeting = greetings.random() - /// - /// If the collection is empty, the value of this function is `nil`. - /// - /// let numbers = [10, 20, 30, 40, 50] - /// if let randomNumber = numbers.random() { - /// print(randomNumber) - /// } - /// // Could print "20", perhaps - @_inlineable - public func random( - using generator: RandomNumberGenerator = Random.default - ) -> Element? { - let isLowerNegative = Bound.isSigned && lowerBound < 0 - let sameSign = !Bound.isSigned || isLowerNegative == (upperBound < 0) - var delta: Bound.Magnitude - if isLowerNegative { - delta = sameSign - ? lowerBound.magnitude - upperBound.magnitude - : lowerBound.magnitude + upperBound.magnitude - } else { - delta = upperBound.magnitude - lowerBound.magnitude - } - if delta == Bound.Magnitude.max { - return Bound(truncatingIfNeeded: generator.next() as Bound.Magnitude) - } - delta += 1 - let randomMagnitude = generator.next(upperBound: delta) - if sameSign { - return lowerBound + Bound(randomMagnitude) - } else { - return Bound.isSigned && randomMagnitude <= upperBound.magnitude - ? Bound(randomMagnitude) - : 0 - Bound(randomMagnitude - upperBound.magnitude) - } - } -} - extension ClosedRange { @inlinable public func overlaps(_ other: ClosedRange) -> Bool { diff --git a/stdlib/public/core/Collection.swift b/stdlib/public/core/Collection.swift index 5c13c6c1598f9..18a536b1e22f5 100644 --- a/stdlib/public/core/Collection.swift +++ b/stdlib/public/core/Collection.swift @@ -1034,12 +1034,12 @@ extension Collection { /// print(randomNumber) /// } /// // Could print "20", perhaps - @_inlineable - public func random( - using generator: RandomNumberGenerator = Random.default + @inlinable + public func random( + using generator: T ) -> Element? { guard !isEmpty else { return nil } - let random = generator.next(upperBound: UInt(self.count)) + let random = generator.next(upperBound: UInt(count)) let index = self.index( self.startIndex, offsetBy: IndexDistance(random), @@ -1048,6 +1048,31 @@ extension Collection { return self[index] } + /// Returns a random element from this collection. + /// + /// - Parameter generator: The random number generator to use when getting + /// a random element. + /// - Returns: A random element from this collection. + /// + /// A good example of this is getting a random greeting from an array: + /// + /// let greetings = ["hi", "hey", "hello", "hola"] + /// let randomGreeting = greetings.random() + /// + /// If the collection is empty, the value of this function is `nil`. + /// + /// let numbers = [10, 20, 30, 40, 50] + /// if let randomNumber = numbers.random() { + /// print(randomNumber) + /// } + /// // Could print "20", perhaps + /// + /// This uses the standard library's default random number generator. + @inlinable + public func random() -> Element? { + return random(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 d17b8f6fbbd52..5c1ddf908b309 100644 --- a/stdlib/public/core/CollectionAlgorithms.swift +++ b/stdlib/public/core/CollectionAlgorithms.swift @@ -368,7 +368,7 @@ extension MutableCollection where Self : BidirectionalCollection { } //===----------------------------------------------------------------------===// -// shuffled() +// shuffled()/shuffle() //===----------------------------------------------------------------------===// extension Sequence { @@ -377,14 +377,26 @@ extension Sequence { /// - Parameter generator: The random number generator to use when shuffling /// the sequence. /// - Returns: A shuffled array of this sequence's elements. - @_inlineable - public func shuffled( - using generator: RandomNumberGenerator = Random.default + @inlinable + public func shuffled( + using generator: T ) -> [Element] { var result = ContiguousArray(self) result.shuffle(using: generator) return Array(result) } + + /// Returns the elements of the sequence, shuffled. + /// + /// - Parameter generator: The random number generator to use when shuffling + /// the sequence. + /// - Returns: A shuffled array of this sequence's elements. + /// + /// This uses the standard library's default random number generator. + @inlinable + public func shuffled() -> [Element] { + return shuffled(using: Random.default) + } } extension MutableCollection { @@ -392,9 +404,9 @@ extension MutableCollection { /// /// - Parameter generator: The random number generator to use when shuffling /// the collection. - @_inlineable - public mutating func shuffle( - using generator: RandomNumberGenerator = Random.default + @inlinable + public mutating func shuffle( + using generator: T ) { guard count > 1 else { return } var amount = count @@ -409,6 +421,17 @@ extension MutableCollection { formIndex(after: ¤tIndex) } } + + /// Shuffles the collection in place. + /// + /// - Parameter generator: The random number generator to use when shuffling + /// the collection. + /// + /// This uses the standard library's default random number generator. + @inlinable + public mutating func shuffle() { + shuffle(using: Random.default) + } } //===----------------------------------------------------------------------===// diff --git a/stdlib/public/core/FloatingPoint.swift.gyb b/stdlib/public/core/FloatingPoint.swift.gyb index 645745ad58bfc..d9c1325953089 100644 --- a/stdlib/public/core/FloatingPoint.swift.gyb +++ b/stdlib/public/core/FloatingPoint.swift.gyb @@ -2388,10 +2388,10 @@ where Self.RawSignificand : FixedWidthInteger, /// - Parameter generator: The random number generator to use when getting /// the random floating point. /// - Returns: A random representation of this floating point. - @_inlineable - public static func random( + @inlinable + public static func random( in range: ${Range}, - using generator: RandomNumberGenerator = Random.default + using generator: T ) -> Self { % if 'Closed' not in Range: _precondition( @@ -2400,7 +2400,7 @@ where Self.RawSignificand : FixedWidthInteger, ) % end let delta = range.upperBound - range.lowerBound - let maxSignificand: Self.RawSignificand = 1 << Self.significandBitCount + let maxSignificand = Self.RawSignificand(1 << (Self.significandBitCount + 1)) % if 'Closed' not in Range: let rand: Self.RawSignificand = generator.next(upperBound: maxSignificand) % else: @@ -2409,14 +2409,23 @@ where Self.RawSignificand : FixedWidthInteger, return range.upperBound } % end - let unitRandom = Self.init( - sign: .plus, - exponentBitPattern: (1 as Self).exponentBitPattern, - significandBitPattern: rand - ) - 1 + let unitRandom = Self.init(rand) * Self.ulpOfOne / 2 return delta * unitRandom + range.lowerBound } + /// Returns a random representation of this floating point within the range. + /// + /// - Parameter range: A ${Range} to determine the bounds to get a random value + /// from. + /// - Parameter generator: The random number generator to use when getting + /// the random floating point. + /// - Returns: A random representation of this floating point. + /// + /// This uses the standard library's default random number generator. + @inlinable + public static func random(in range: ${Range}) -> Self { + return Self.random(in: range, using: Random.default) + } } % end diff --git a/stdlib/public/core/Integers.swift.gyb b/stdlib/public/core/Integers.swift.gyb index d1a146decb306..55f770ea50136 100644 --- a/stdlib/public/core/Integers.swift.gyb +++ b/stdlib/public/core/Integers.swift.gyb @@ -2532,6 +2532,99 @@ ${assignmentOperatorComment(x.operator, False)} % for Range in ['Range', 'ClosedRange']: +extension ${Range} +where Bound: FixedWidthInteger, + Bound.Magnitude: UnsignedInteger { + + /// Returns a random element from this collection. + /// + /// - Parameter generator: The random number generator to use when getting + /// a random element. + /// - Returns: A random element from this collection. + /// + /// A good example of this is getting a random greeting from an array: + /// + /// let greetings = ["hi", "hey", "hello", "hola"] + /// let randomGreeting = greetings.random() + /// + /// If the collection is empty, the value of this function is `nil`. + /// + /// let numbers = [10, 20, 30, 40, 50] + /// if let randomNumber = numbers.random() { + /// print(randomNumber) + /// } + /// // Could print "20", perhaps + @inlinable + public func random( + using generator: T + ) -> Element? { +% if 'Closed' not in Range: + guard lowerBound != upperBound else { + return nil + } +% end + 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 from this collection. + /// + /// - Parameter generator: The random number generator to use when getting + /// a random element. + /// - Returns: A random element from this collection. + /// + /// A good example of this is getting a random greeting from an array: + /// + /// let greetings = ["hi", "hey", "hello", "hola"] + /// let randomGreeting = greetings.random() + /// + /// If the collection is empty, the value of this function is `nil`. + /// + /// let numbers = [10, 20, 30, 40, 50] + /// if let randomNumber = numbers.random() { + /// print(randomNumber) + /// } + /// // Could print "20", perhaps + /// + /// This uses the standard library's default random number generator. + @inlinable + public func random() -> Element? { + return self.random(using: Random.default) + } +} + extension FixedWidthInteger where Self.Stride : SignedInteger, Self.Magnitude : UnsignedInteger { @@ -2543,10 +2636,10 @@ where Self.Stride : SignedInteger, /// - Parameter generator: The random number generator to use when getting /// the random integer. /// - Returns: A random representation of this integer. - @_inlineable - public static func random( + @inlinable + public static func random( in range: ${Range}, - using generator: RandomNumberGenerator = Random.default + using generator: T ) -> Self { % if 'Closed' not in Range: _precondition( @@ -2556,6 +2649,20 @@ where Self.Stride : SignedInteger, % end return range.random(using: generator)! } + + /// Returns a random representation of this integer within the range. + /// + /// - Parameter range: A ${Range} to determine the bounds to get a random value + /// from. + /// - Parameter generator: The random number generator to use when getting + /// the random integer. + /// - Returns: A random representation of this integer. + /// + /// This uses the standard library's default random number generator. + @inlinable + public static func random(in range: ${Range}) -> Self { + return Self.random(in: range, using: Random.default) + } } % end diff --git a/stdlib/public/core/Random.swift b/stdlib/public/core/Random.swift index bc05231d395c6..29e8872d6375f 100644 --- a/stdlib/public/core/Random.swift +++ b/stdlib/public/core/Random.swift @@ -46,10 +46,10 @@ extension RandomNumberGenerator { /// /// This differs from next() as this function has the ability to transform the /// generated number to any unsigned integer. - @_inlineable + @inlinable public func next() -> T { - if T.bitWidth == UInt64.bitWidth { - return T(self.next()) + if T.bitWidth <= UInt64.bitWidth { + return T(truncatingIfNeeded: self.next()) } let (quotient, remainder) = T.bitWidth.quotientAndRemainder( @@ -76,11 +76,12 @@ extension RandomNumberGenerator { /// - Parameter upperBound: The max number this can generate up to. /// - Returns: A number that was randomly generated from 0 to upperBound /// - /// This uses the uniform distribution to form a random number within the - /// upperBound. - @_inlineable + /// By default, this uses the uniform distribution to form a random number + /// that is less than the upperBound. + @inlinable public func next(upperBound: T) -> T { - let range = T.max % upperBound + let tmp = (T.max % upperBound) + 1 + let range = tmp == upperBound ? 0 : tmp var random: T = 0 repeat { @@ -125,4 +126,23 @@ public struct Random : RandomNumberGenerator { _stdlib_random(&random, MemoryLayout.size) return random } + + /// Produces the next randomly generated number + /// + /// - Returns: A number that was randomly generated + /// + /// This differs from next() as this function has the ability to transform the + /// generated number to any unsigned integer. + public func next() -> T { + var random: T = 0 + _stdlib_random(&random, MemoryLayout.size) + return random + } +} + +public // @testable +func _stdlib_random(_ bytes: UnsafeMutableRawBufferPointer) { + if !bytes.isEmpty { + _stdlib_random(bytes.baseAddress!, bytes.count) + } } diff --git a/stdlib/public/core/Range.swift b/stdlib/public/core/Range.swift index 7fc08cabbb559..eb9db799526be 100644 --- a/stdlib/public/core/Range.swift +++ b/stdlib/public/core/Range.swift @@ -303,56 +303,6 @@ extension Range where Bound: Strideable, Bound.Stride : SignedInteger { } } -extension Range -where Bound : FixedWidthInteger, - Bound.Magnitude : UnsignedInteger { - - /// Returns a random element from this collection. - /// - /// - Parameter generator: The random number generator to use when getting - /// a random element. - /// - Returns: A random element from this collection. - /// - /// A good example of this is getting a random greeting from an array: - /// - /// let greetings = ["hi", "hey", "hello", "hola"] - /// let randomGreeting = greetings.random() - /// - /// If the collection is empty, the value of this function is `nil`. - /// - /// let numbers = [10, 20, 30, 40, 50] - /// if let randomNumber = numbers.random() { - /// print(randomNumber) - /// } - /// // Could print "20", perhaps - @_inlineable - public func random( - using generator: RandomNumberGenerator = Random.default - ) -> Element? { - guard lowerBound != upperBound else { - return nil - } - let isLowerNegative = Bound.isSigned && lowerBound < 0 - let sameSign = !Bound.isSigned || isLowerNegative == (upperBound < 0) - let delta: Bound.Magnitude - if isLowerNegative { - delta = sameSign - ? lowerBound.magnitude - upperBound.magnitude - : lowerBound.magnitude + upperBound.magnitude - } else { - delta = upperBound.magnitude - lowerBound.magnitude - } - let randomMagnitude = generator.next(upperBound: delta) - if sameSign { - return lowerBound + Bound(randomMagnitude) - } else { - return randomMagnitude < upperBound.magnitude - ? Bound(randomMagnitude) - : -1 - Bound(randomMagnitude - upperBound.magnitude) - } - } -} - extension Range: RangeExpression { /// Returns the range of indices described by this range expression within /// the given collection. diff --git a/stdlib/public/stubs/LibcShims.cpp b/stdlib/public/stubs/LibcShims.cpp index 4b1cf7af6530b..d2838eb1ee55d 100644 --- a/stdlib/public/stubs/LibcShims.cpp +++ b/stdlib/public/stubs/LibcShims.cpp @@ -11,35 +11,67 @@ //===----------------------------------------------------------------------===// #if defined(__APPLE__) -#define _REENTRANT -#include +# define _REENTRANT +# include +# include #endif -#include -#include -#include -#if defined(_WIN32) -#include -#define WIN32_LEAN_AND_MEAN -#include + +#if defined(__CYGWIN__) +# include +# if (CYGWIN_VERSION_API_MAJOR > 0) || (CYGWIN_VERSION_API_MINOR >= 306) +# include +# define SWIFT_STDLIB_USING_GETRANDOM +# endif +#endif + +#if defined(__Fuchsia__) +# include +# define SWIFT_STDLIB_USING_GETENTROPY +#endif + +#if defined(__linux__) +# include +# if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,17,0)) +# include +# if defined(__BIONIC__) || (defined(__GLIBC_PREREQ) && __GLIBC_PREREQ(2,25)) +# include +# define SWIFT_STDLIB_USING_GETRANDOM +# endif +# endif +#endif + +#if defined(_WIN32) && !defined(__CYGWIN__) +# include +# define WIN32_LEAN_AND_MEAN +# define WIN32_NO_STATUS +# include +# undef WIN32_NO_STATUS +# include +# include +# include #else -#include -#include -#include -#include +# include +# include +# include +# include #endif -#include +#include +#include +#include +#include #include +#include #include -#include -#include -#include #include +#include +#include + +#include "llvm/Support/DataTypes.h" #include "swift/Basic/Lazy.h" #include "swift/Runtime/Config.h" #include "swift/Runtime/Debug.h" #include "../SwiftShims/LibcShims.h" -#include "llvm/Support/DataTypes.h" using namespace swift; @@ -311,44 +343,72 @@ swift::_stdlib_cxx11_mt19937_uniform(__swift_uint32_t upper_bound) { } #if defined(__APPLE__) -#include + SWIFT_RUNTIME_STDLIB_INTERNAL void swift::_stdlib_random(void *buf, __swift_size_t nbytes) { if (__builtin_available(macOS 10.12, iOS 10, tvOS 10, watchOS 3, *)) { arc4random_buf(buf, nbytes); - }else { + } else { OSStatus status = SecRandomCopyBytes(kSecRandomDefault, nbytes, buf); if (status != errSecSuccess) { - fatalError(0, "Fatal error: SecRandomCopyBytes failed with error %d\n", status); + fatalError(0, "Fatal error: %d in '%s'\n", status, __func__); } } } -#elif defined(__linux__) -#include -#include + +#elif defined(_WIN32) && !defined(__CYGWIN__) +// TODO: Test on Windows + SWIFT_RUNTIME_STDLIB_INTERNAL void swift::_stdlib_random(void *buf, __swift_size_t nbytes) { -#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,17,0) - auto _read = [&]() -> __swift_ssize_t { - return syscall(SYS_getrandom, buf, bytes, 0); - }; -#else - auto _read = [&]() -> __swift_ssize_t { - static const int fd = _stdlib_open("/dev/urandom", O_RDONLY); - if (fd < 0) { - fatalError(0, "Unable to open '/dev/urandom'\n"); + NTSTATUS status = BCryptGenRandom(nullptr, + static_cast(buf), + static_cast(nbytes), + BCRYPT_USE_SYSTEM_PREFERRED_RNG); + if (!NT_SUCCESS(status)) { + fatalError(0, "Fatal error: %#.8x in '%s'\n", status, __func__); + } +} + +#elif defined(SWIFT_STDLIB_USING_GETENTROPY) + +SWIFT_RUNTIME_STDLIB_INTERNAL +void swift::_stdlib_random(void *buf, __swift_size_t nbytes) { + while (nbytes > 0) { + constexpr __swift_size_t max_nbytes = 256; + __swift_size_t actual_nbytes = (nbytes < max_nbytes ? + nbytes : max_nbytes); + if (0 != getentropy(buf, actual_nbytes)) { + fatalError(0, "Fatal error: %d in '%s'\n", errno, __func__); } - return _stdlib_read(fd, buf, bytes); - }; + buf = static_cast(buf) + actual_nbytes; + nbytes -= actual_nbytes; + } +} + +#else + +SWIFT_RUNTIME_STDLIB_INTERNAL +void swift::_stdlib_random(void *buf, __swift_size_t nbytes) { +#if !defined(SWIFT_STDLIB_USING_GETRANDOM) + static const int fd = _stdlib_open("/dev/urandom", O_RDONLY, 0); + if (fd < 0) { + fatalError(0, "Fatal error: %d in '%s'\n", errno, __func__); + } #endif while (nbytes > 0) { - auto result = _read(); - if (result < 1) { +#if !defined(SWIFT_STDLIB_USING_GETRANDOM) + __swift_ssize_t actual_nbytes = _stdlib_read(fd, buf, nbytes); +#else + __swift_ssize_t actual_nbytes = getrandom(buf, nbytes, 0); +#endif + if (actual_nbytes < 1) { if (errno == EINTR) { continue; } - fatalError(0, "Unable to read '/dev/urandom'\n"); + fatalError(0, "Fatal error: %d in '%s'\n", errno, __func__); } - buf = static_cast(buf) + result; - nbytes -= result; + buf = static_cast(buf) + actual_nbytes; + nbytes -= actual_nbytes; } } + #endif diff --git a/test/stdlib/Random.swift b/test/stdlib/Random.swift index ecd384e8ed2a2..03c7aa199b5e1 100644 --- a/test/stdlib/Random.swift +++ b/test/stdlib/Random.swift @@ -15,7 +15,7 @@ RandomTests.test("basic random numbers") { let randomDouble1 = Double.random(in: 0 ..< 1) expectTrue(randomDouble1 < 1 && randomDouble1 >= 0) let randomDouble2 = Double.random(in: 0 ..< 1) - expectTrue(randomDouble1 < 1 && randomDouble2 >= 0) + expectTrue(randomDouble2 < 1 && randomDouble2 >= 0) expectTrue(randomDouble1 != randomDouble2) } From dff04482f16e4d4f6f857b98724e290567c524ef Mon Sep 17 00:00:00 2001 From: Nate Cook Date: Wed, 11 Apr 2018 23:26:58 -0500 Subject: [PATCH 3/7] Revise documentation, add benchmarks (#3) * [stdlib] Revise documentation for new random APIs * [stdlib] Fix constraints on random integer generation * [test] Isolate failing Random test * [benchmark] Add benchmarks for new random APIs Fix Float80 test Value type generators random -> randomElement Fix some docs One more doc fix Doc fixes & bool fix Use computed over explicit (cherry picked from commit f146d17214ad11c8faf96c184c1194a32aa0d034) --- benchmark/CMakeLists.txt | 2 + benchmark/single-source/RandomShuffle.swift | 64 ++++++++ benchmark/single-source/RandomValues.swift | 87 +++++++++++ benchmark/utils/main.swift | 4 + stdlib/public/core/Bool.swift | 6 +- stdlib/public/core/Collection.swift | 62 ++++---- stdlib/public/core/CollectionAlgorithms.swift | 66 ++++++-- stdlib/public/core/FloatingPoint.swift.gyb | 99 +++++++++--- stdlib/public/core/Integers.swift.gyb | 141 +++++++++++------- stdlib/public/core/Random.swift | 129 ++++++++-------- test/stdlib/Random.swift | 30 ++-- 11 files changed, 484 insertions(+), 206 deletions(-) create mode 100644 benchmark/single-source/RandomShuffle.swift create mode 100644 benchmark/single-source/RandomValues.swift diff --git a/benchmark/CMakeLists.txt b/benchmark/CMakeLists.txt index 93339b5185e8c..8b1499c87c421 100644 --- a/benchmark/CMakeLists.txt +++ b/benchmark/CMakeLists.txt @@ -122,6 +122,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..72847b7ba013f --- /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) 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 +// 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..a8ac6a36aaf87 --- /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) 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 +// 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 b92415562257a..13bb60248c1dc 100644 --- a/benchmark/utils/main.swift +++ b/benchmark/utils/main.swift @@ -110,6 +110,8 @@ import ProtocolDispatch2 import Queue import RC4 import RGBHistogram +import RandomShuffle +import RandomValues import RangeAssignment import RangeIteration import RangeReplaceableCollectionPlusDefault @@ -264,6 +266,8 @@ registerBenchmark(QueueGeneric) registerBenchmark(QueueConcrete) registerBenchmark(RC4Test) registerBenchmark(RGBHistogram) +registerBenchmark(RandomShuffle) +registerBenchmark(RandomValues) registerBenchmark(RangeAssignment) registerBenchmark(RangeIteration) registerBenchmark(RangeReplaceableCollectionPlusDefault) diff --git a/stdlib/public/core/Bool.swift b/stdlib/public/core/Bool.swift index 58be7d7908eb6..f974147684248 100644 --- a/stdlib/public/core/Bool.swift +++ b/stdlib/public/core/Bool.swift @@ -94,9 +94,9 @@ public struct Bool { /// - Returns: A random Boolean. @inlinable public static func random( - using generator: T + using generator: inout T ) -> Bool { - return generator.next() % 2 == 0 + return (generator.next() >> 17) & 1 == 0 } /// Returns a random Boolean @@ -108,7 +108,7 @@ public struct Bool { /// This uses the standard library's default random number generator. @inlinable public static func random() -> Bool { - return Bool.random(using: Random.default) + return Bool.random(using: &Random.default) } } diff --git a/stdlib/public/core/Collection.swift b/stdlib/public/core/Collection.swift index 18a536b1e22f5..220eefccd5f1d 100644 --- a/stdlib/public/core/Collection.swift +++ b/stdlib/public/core/Collection.swift @@ -1016,27 +1016,24 @@ extension Collection { return count } - /// Returns a random element from this collection. + /// Returns a random element of the collection, using the given generator as + /// a source for randomness. /// - /// - Parameter generator: The random number generator to use when getting - /// a random element. - /// - Returns: A random element from this collection. - /// - /// A good example of this is getting a random greeting from an array: - /// - /// let greetings = ["hi", "hey", "hello", "hola"] - /// let randomGreeting = greetings.random() + /// 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. /// - /// If the collection is empty, the value of this function is `nil`. + /// let names = ["Zoey", "Chloe", "Amani", "Amaia"] + /// let randomName = names.randomElement(using: &myGenerator)! + /// // randomName == "Amani" (maybe) /// - /// let numbers = [10, 20, 30, 40, 50] - /// if let randomNumber = numbers.random() { - /// print(randomNumber) - /// } - /// // Could print "20", perhaps + /// - 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 random( - using generator: T + public func randomElement( + using generator: inout T ) -> Element? { guard !isEmpty else { return nil } let random = generator.next(upperBound: UInt(count)) @@ -1048,29 +1045,24 @@ extension Collection { return self[index] } - /// Returns a random element from this collection. + /// Returns a random element of the collection. /// - /// - Parameter generator: The random number generator to use when getting - /// a random element. - /// - Returns: A random element from this collection. - /// - /// A good example of this is getting a random greeting from an array: - /// - /// let greetings = ["hi", "hey", "hello", "hola"] - /// let randomGreeting = greetings.random() + /// For example, call `randomElement()` to select a random element from an + /// array of names. /// - /// If the collection is empty, the value of this function is `nil`. + /// let names = ["Zoey", "Chloe", "Amani", "Amaia"] + /// let randomName = names.randomElement()! + /// // randomName == "Amani" (perhaps) /// - /// let numbers = [10, 20, 30, 40, 50] - /// if let randomNumber = numbers.random() { - /// print(randomNumber) - /// } - /// // Could print "20", 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)`. /// - /// This uses the standard library's default random number generator. + /// - Returns: A random element from the collection. If the collection is + /// empty, the method returns `nil`. @inlinable - public func random() -> Element? { - return random(using: Random.default) + public func randomElement() -> Element? { + return randomElement(using: &Random.default) } /// Do not use this method directly; call advanced(by: n) instead. diff --git a/stdlib/public/core/CollectionAlgorithms.swift b/stdlib/public/core/CollectionAlgorithms.swift index 5c1ddf908b309..aeee8e0d41ca3 100644 --- a/stdlib/public/core/CollectionAlgorithms.swift +++ b/stdlib/public/core/CollectionAlgorithms.swift @@ -372,41 +372,75 @@ extension MutableCollection where Self : BidirectionalCollection { //===----------------------------------------------------------------------===// extension Sequence { - /// Returns the elements of the sequence, shuffled. + /// 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: A shuffled array of this sequence's elements. + /// - Returns: An array of this sequence's elements in a shuffled order. + /// + /// - Complexity: O(*n*) @inlinable public func shuffled( - using generator: T + using generator: inout T ) -> [Element] { var result = ContiguousArray(self) - result.shuffle(using: generator) + 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)`. + /// /// - Parameter generator: The random number generator to use when shuffling /// the sequence. /// - Returns: A shuffled array of this sequence's elements. /// - /// This uses the standard library's default random number generator. + /// - Complexity: O(*n*) @inlinable public func shuffled() -> [Element] { - return shuffled(using: Random.default) + return shuffled(using: &Random.default) } } extension MutableCollection { - /// Shuffles the collection in place. + /// 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: T + using generator: inout T ) { guard count > 1 else { return } var amount = count @@ -424,13 +458,21 @@ extension MutableCollection { /// Shuffles the collection in place. /// - /// - Parameter generator: The random number generator to use when shuffling - /// the collection. + /// 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 uses the standard library's default random number generator. + /// 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) + shuffle(using: &Random.default) } } diff --git a/stdlib/public/core/FloatingPoint.swift.gyb b/stdlib/public/core/FloatingPoint.swift.gyb index d9c1325953089..0b129bcb4ddbc 100644 --- a/stdlib/public/core/FloatingPoint.swift.gyb +++ b/stdlib/public/core/FloatingPoint.swift.gyb @@ -2375,23 +2375,44 @@ 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 & FixedWidthInteger, Self.RawSignificand.Magnitude : UnsignedInteger { - /// Returns a random representation of this floating point within the range. + /// 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" /// - /// - Parameter range: A ${Range} to determine the bounds to get a random value - /// from. - /// - Parameter generator: The random number generator to use when getting - /// the random floating point. - /// - Returns: A random representation of this floating point. + /// The `random(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: T + using generator: inout T ) -> Self { % if 'Closed' not in Range: _precondition( @@ -2400,31 +2421,61 @@ where Self.RawSignificand : FixedWidthInteger, ) % end let delta = range.upperBound - range.lowerBound - let maxSignificand = Self.RawSignificand(1 << (Self.significandBitCount + 1)) -% if 'Closed' not in Range: - let rand: Self.RawSignificand = generator.next(upperBound: maxSignificand) -% else: - let rand: Self.RawSignificand = generator.next(upperBound: maxSignificand + 1) - if rand == maxSignificand { - return range.upperBound + 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 maxSignificand = Self.RawSignificand(1 << (Self.significandBitCount + 1)) +% if 'Closed' not in Range: + rand = generator.next(upperBound: maxSignificand) +% else: + rand = generator.next(upperBound: maxSignificand + 1) + if rand == maxSignificand { + return range.upperBound + } +% end } -% end let unitRandom = Self.init(rand) * Self.ulpOfOne / 2 return delta * unitRandom + range.lowerBound } - /// Returns a random representation of this floating point within the range. + /// Returns a random value within the specified range. /// - /// - Parameter range: A ${Range} to determine the bounds to get a random value - /// from. - /// - Parameter generator: The random number generator to use when getting - /// the random floating point. - /// - Returns: A random representation of this floating point. + /// Use this method to generate a floating-point value within a specific + /// range. This example creates three new values in the range + /// `${exampleRange}`. /// - /// This uses the standard library's default random number generator. + /// 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) + return Self.random(in: range, using: &Random.default) } } diff --git a/stdlib/public/core/Integers.swift.gyb b/stdlib/public/core/Integers.swift.gyb index 55f770ea50136..09170003e7eec 100644 --- a/stdlib/public/core/Integers.swift.gyb +++ b/stdlib/public/core/Integers.swift.gyb @@ -2531,32 +2531,34 @@ ${assignmentOperatorComment(x.operator, False)} } % for Range in ['Range', 'ClosedRange']: +% exampleRange = '1..<100' if Range == 'Range' else '1...100' extension ${Range} -where Bound: FixedWidthInteger, - Bound.Magnitude: UnsignedInteger { + where Bound: FixedWidthInteger, Bound.Stride : SignedInteger, + Bound.Magnitude: UnsignedInteger +{ - /// Returns a random element from this collection. + /// 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 getting + /// - Parameter generator: The random number generator to use when choosing /// a random element. - /// - Returns: A random element from this collection. - /// - /// A good example of this is getting a random greeting from an array: - /// - /// let greetings = ["hi", "hey", "hello", "hola"] - /// let randomGreeting = greetings.random() - /// - /// If the collection is empty, the value of this function is `nil`. - /// - /// let numbers = [10, 20, 30, 40, 50] - /// if let randomNumber = numbers.random() { - /// print(randomNumber) - /// } - /// // Could print "20", perhaps + /// - 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 random( - using generator: T + public func randomElement( + using generator: inout T ) -> Element? { % if 'Closed' not in Range: guard lowerBound != upperBound else { @@ -2599,29 +2601,29 @@ where Bound: FixedWidthInteger, } } - /// Returns a random element from this collection. - /// - /// - Parameter generator: The random number generator to use when getting - /// a random element. - /// - Returns: A random element from this collection. - /// - /// A good example of this is getting a random greeting from an array: - /// - /// let greetings = ["hi", "hey", "hello", "hola"] - /// let randomGreeting = greetings.random() - /// - /// If the collection is empty, the value of this function is `nil`. + /// 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. /// - /// let numbers = [10, 20, 30, 40, 50] - /// if let randomNumber = numbers.random() { - /// print(randomNumber) - /// } - /// // Could print "20", perhaps + /// This method uses the default random generator, `Random.default`. Calling + /// `(${exampleRange}).randomElement()` is equivalent to calling + /// `(${exampleRange}).randomElement(using: &Random.default)`. /// - /// This uses the standard library's default random number generator. + /// - 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 random() -> Element? { - return self.random(using: Random.default) + public func randomElement() -> Element? { + return randomElement(using: &Random.default) } } @@ -2629,17 +2631,32 @@ extension FixedWidthInteger where Self.Stride : SignedInteger, Self.Magnitude : UnsignedInteger { - /// Returns a random representation of this integer within the range. + /// 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" /// - /// - Parameter range: A ${Range} to determine the bounds to get a random value - /// from. - /// - Parameter generator: The random number generator to use when getting - /// the random integer. - /// - Returns: A random representation of this integer. + /// - 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: T + using generator: inout T ) -> Self { % if 'Closed' not in Range: _precondition( @@ -2647,21 +2664,33 @@ where Self.Stride : SignedInteger, "Can't get random value with lowerBound == upperBound" ) % end - return range.random(using: generator)! + return range.randomElement(using: &generator)! } - /// Returns a random representation of this integer within the range. + /// Returns a random value within the specified range. /// - /// - Parameter range: A ${Range} to determine the bounds to get a random value - /// from. - /// - Parameter generator: The random number generator to use when getting - /// the random integer. - /// - Returns: A random representation of this integer. + /// Use this method to generate an integer within a specific range. This + /// example creates three new values in the range `${exampleRange}`. /// - /// This uses the standard library's default random number generator. + /// 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) + return Self.random(in: range, using: &Random.default) } } diff --git a/stdlib/public/core/Random.swift b/stdlib/public/core/Random.swift index 29e8872d6375f..9a8f449b890a6 100644 --- a/stdlib/public/core/Random.swift +++ b/stdlib/public/core/Random.swift @@ -12,42 +12,57 @@ import SwiftShims -/// A type that allows random number generators to be used throughout Swift to -/// produce random aspects of Swift's types. +/// A type that can provide uniformly distributed random data. /// -/// The RandomNumberGenerator protocol is used for random number generator types -/// that implement their own pseudo-random or cryptographically secure -/// pseudo-random number generation. Using the RandomNumberGenerator protocol -/// allows these types to be used with types with random methods, collections -/// with .random(), and collections/sequences with -/// .shuffle() and .shuffled() +/// 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. /// -/// Conforming to the RandomNumberGenerator protocol -/// ========================================== +/// 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: /// -/// In order to conform to RandomNumberGenerator, types that implement this must be -/// able to produce an unsigned integer. +/// enum Weekday : CaseIterable { +/// case sunday, monday, tuesday, wednesday, thursday, friday, saturday /// -/// - Note: Types that implement RandomNumberGenerator should have a decent -/// amount of documentation that clearly conveys whether or not the generator -/// produces cryptographically secure numbers or deterministically generated -/// numbers. +/// 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 { - /// Produces the next randomly generated number + /// Returns a value from a uniform, independent distribution of binary data. /// - /// - Returns: A number that was randomly generated - func next() -> UInt64 + /// - Returns: An unsigned 64-bit random value. + mutating func next() -> UInt64 } extension RandomNumberGenerator { - /// Produces the next randomly generated number - /// - /// - Returns: A number that was randomly generated + /// Returns a value from a uniform, independent distribution of binary data. /// - /// This differs from next() as this function has the ability to transform the - /// generated number to any unsigned integer. + /// - Returns: A random value of `T`. Bits are randomly distributed so that + /// every value of `T` is equally likely to be returned. @inlinable - public func next() -> T { + public mutating func next() -> T { if T.bitWidth <= UInt64.bitWidth { return T(truncatingIfNeeded: self.next()) } @@ -70,16 +85,15 @@ extension RandomNumberGenerator { return tmp } - /// Produces the next randomly generated number that is constricted by an - /// upperBound + /// Returns a random value that is less than the given upper bound. /// - /// - Parameter upperBound: The max number this can generate up to. - /// - Returns: A number that was randomly generated from 0 to upperBound - /// - /// By default, this uses the uniform distribution to form a random number - /// that is less than the upperBound. + /// - Parameter upperBound: The upper bound for the randomly generated value. + /// - Returns: A random value of `T` in the range `0..(upperBound: T) -> T { + public mutating func next( + upperBound: T + ) -> T { let tmp = (T.max % upperBound) + 1 let range = tmp == upperBound ? 0 : tmp var random: T = 0 @@ -92,48 +106,41 @@ extension RandomNumberGenerator { } } -/// The provided default source of random numbers -/// -/// All of the default provided random functions utilize this source of random. -/// Using those functions should be preferred over using this directly. An -/// example of calling this directly: -/// -/// let random: UInt8 = Random.default.next() -/// let randomToTen: UInt32 = Random.default.next(upperBound: 10) +/// The default source of random data. /// -/// However, you should strive to use the random functions on the numeric types. -/// Using the preferred way: +/// 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 random = UInt8.random(in: .min ... .max) -/// let randomToTen = UInt32.random(in: 0 ..< 10) +/// let x = Int.random(in: 1...100) +/// let y = Int.random(in: 1...100, using: &Random.default) /// -/// - Note: The default implementation of randomness is cryptographically secure. -/// It utilizes arc4random(3) on newer versions of macOS, iOS, etc. On older -/// versions of these operating systems it uses SecRandomCopyBytes. For Linux, -/// it tries to use the getrandom(2) system call on newer kernel versions. On -/// older kernel versions, it reads from /dev/urandom. +/// `Random.default` is safe to use in multiple threads, and uses a +/// cryptographically secure algorithm whenever possible. public struct Random : RandomNumberGenerator { - /// The default random implementation - public static let `default` = Random() + /// The shared, default instance of the `Range` random number generator. + public static var `default`: Random { + get { return Random() } + set { /* Discard */ } + } private init() {} - /// Produces the next randomly generated number + /// Returns a value from a uniform, independent distribution of binary data. /// - /// - Returns: A number that was randomly generated - public func next() -> UInt64 { + /// - Returns: An unsigned 64-bit random value. + public mutating func next() -> UInt64 { var random: UInt64 = 0 _stdlib_random(&random, MemoryLayout.size) return random } - /// Produces the next randomly generated number - /// - /// - Returns: A number that was randomly generated + /// Returns a value from a uniform, independent distribution of binary data. /// - /// This differs from next() as this function has the ability to transform the - /// generated number to any unsigned integer. - public func next() -> T { + /// - Returns: A random value of `T`. Bits are randomly distributed so that + /// every value of `T` is equally likely to be returned. + public mutating func next() -> T { var random: T = 0 _stdlib_random(&random, MemoryLayout.size) return random diff --git a/test/stdlib/Random.swift b/test/stdlib/Random.swift index 03c7aa199b5e1..ab86c653fd9d7 100644 --- a/test/stdlib/Random.swift +++ b/test/stdlib/Random.swift @@ -34,7 +34,7 @@ func integerRangeTest(_ type: T.Type) expectTrue(minOpenRange.contains(random)) integerSet.insert(random) } - expectTrue(integerSet == Set(T.min ..< (T.min + 10))) + expectTrue(integerSet == Set(minOpenRange)) integerSet.removeAll() // min closed range @@ -44,7 +44,7 @@ func integerRangeTest(_ type: T.Type) expectTrue(minClosedRange.contains(random)) integerSet.insert(random) } - expectTrue(integerSet == Set(T.min ... (T.min + 10))) + expectTrue(integerSet == Set(minClosedRange)) integerSet.removeAll() // max open range @@ -54,7 +54,7 @@ func integerRangeTest(_ type: T.Type) expectTrue(maxOpenRange.contains(random)) integerSet.insert(random) } - expectTrue(integerSet == Set((T.max - 10) ..< T.max)) + expectTrue(integerSet == Set(maxOpenRange)) integerSet.removeAll() // max closed range @@ -64,7 +64,7 @@ func integerRangeTest(_ type: T.Type) expectTrue(maxClosedRange.contains(random)) integerSet.insert(random) } - expectTrue(integerSet == Set((T.max - 10) ... T.max)) + expectTrue(integerSet == Set(maxClosedRange)) } RandomTests.test("random integers in ranges") { @@ -113,7 +113,7 @@ RandomTests.test("random floating points in ranges") { RandomTests.test("random elements from collection") { let greetings = ["hello", "hi", "hey", "hola", "what's up"] for _ in 0 ..< 1_000 { - let randomGreeting = greetings.random() + let randomGreeting = greetings.randomElement() expectNotNil(randomGreeting) expectTrue(greetings.contains(randomGreeting!)) } @@ -134,7 +134,7 @@ RandomTests.test("shuffling") { // Different RNGS -public class LCRNG: RandomNumberGenerator { +public struct LCRNG: RandomNumberGenerator { private var state: UInt64 private static let m: UInt64 = 1 << 48 private static let a: UInt64 = 25214903917 @@ -144,12 +144,12 @@ public class LCRNG: RandomNumberGenerator { self.state = seed } - private func next() -> UInt32 { + private mutating func next() -> UInt32 { state = (LCRNG.a &* state &+ LCRNG.c) % LCRNG.m return UInt32(truncatingIfNeeded: state >> 15) } - public func next() -> UInt64 { + public mutating func next() -> UInt64 { return UInt64(next() as UInt32) << 32 | UInt64(next() as UInt32) } } @@ -164,23 +164,23 @@ RandomTests.test("different random number generators") { for i in 0 ..< 2 { let seed: UInt64 = 1234567890 - let rng = LCRNG(seed: seed) + var rng = LCRNG(seed: seed) for _ in 0 ..< 1_000 { - let randomInt = Int.random(in: 0 ... 100, using: rng) + let randomInt = Int.random(in: 0 ... 100, using: &rng) intPasses[i].append(randomInt) - let randomDouble = Double.random(in: 0 ..< 1, using: rng) + let randomDouble = Double.random(in: 0 ..< 1, using: &rng) doublePasses[i].append(randomDouble) - let randomBool = Bool.random(using: rng) + let randomBool = Bool.random(using: &rng) boolPasses[i].append(randomBool) - let randomIntFromCollection = Array(0 ... 100).random(using: rng) + let randomIntFromCollection = Array(0 ... 100).randomElement(using: &rng) expectNotNil(randomIntFromCollection) collectionPasses[i].append(randomIntFromCollection!) - let randomShuffledCollection = Array(0 ... 100).shuffled(using: rng) + let randomShuffledCollection = Array(0 ... 100).shuffled(using: &rng) shufflePasses[i].append(randomShuffledCollection) } } @@ -222,4 +222,4 @@ RandomTests.test("uniform distribution") { expectTrue(chi2Test(array)) } -runAllTests() \ No newline at end of file +runAllTests() From 856400f162ca9e439d8efe99a966d06cb34905fe Mon Sep 17 00:00:00 2001 From: Ben Rimmington Date: Sun, 22 Apr 2018 15:05:08 +0100 Subject: [PATCH 4/7] Consolidate `_stdlib_random` functions (#2) * Use the `__has_include` and `GRND_RANDOM` macros * Use `getentropy` instead of `getrandom` * Use `std::min` from the header * Move `#if` out of the `_stdlib_random` function * Use `getrandom` with "/dev/urandom" fallback * Use `#pragma comment` to import "Bcrypt.lib" * * * Use "/dev/urandom" instead of `SecRandomCopyBytes` * Use `swift::StaticMutex` for shared "/dev/urandom" * Add `getrandom_available`; use `O_CLOEXEC` flag Add platform impl docs Update copyrights Fix docs Add _stdlib_random test Update _stdlib_random test Add missing & Notice about _stdlib_random Fix docs Guard on upperBound = 0 Test full range of 8 bit integers Remove some gyb Clean up integerRangeTest Remove FixedWidthInteger constraint Use arc4random universally Fix randomElement Constrain shuffle to RandomAccessCollection warning instead of error Move Apple's implementation Fix failing test on 32 bit systems (cherry picked from commit b65d0c1d117332a632c10895e62218fa380d8251) --- benchmark/single-source/RandomShuffle.swift | 2 +- benchmark/single-source/RandomValues.swift | 2 +- benchmark/utils/main.swift | 2 +- stdlib/public/SwiftShims/LibcShims.h | 2 +- stdlib/public/core/Bool.swift | 12 +- stdlib/public/core/CMakeLists.txt | 7 +- stdlib/public/core/Collection.swift | 26 +++- stdlib/public/core/CollectionAlgorithms.swift | 7 +- stdlib/public/core/FloatingPoint.swift.gyb | 15 +- stdlib/public/core/Integers.swift.gyb | 12 +- stdlib/public/core/Random.swift | 28 +++- stdlib/public/stubs/LibcShims.cpp | 137 ++++++++---------- test/stdlib/Random.swift | 122 ++++++++++------ 13 files changed, 203 insertions(+), 171 deletions(-) diff --git a/benchmark/single-source/RandomShuffle.swift b/benchmark/single-source/RandomShuffle.swift index 72847b7ba013f..a9869dd7da677 100644 --- a/benchmark/single-source/RandomShuffle.swift +++ b/benchmark/single-source/RandomShuffle.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors +// 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 diff --git a/benchmark/single-source/RandomValues.swift b/benchmark/single-source/RandomValues.swift index a8ac6a36aaf87..6387e3d5a99f9 100644 --- a/benchmark/single-source/RandomValues.swift +++ b/benchmark/single-source/RandomValues.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors +// 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 diff --git a/benchmark/utils/main.swift b/benchmark/utils/main.swift index 13bb60248c1dc..038d67c752586 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 diff --git a/stdlib/public/SwiftShims/LibcShims.h b/stdlib/public/SwiftShims/LibcShims.h index 2f31553084eec..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 diff --git a/stdlib/public/core/Bool.swift b/stdlib/public/core/Bool.swift index f974147684248..33b82323ff2ef 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 @@ -87,11 +87,11 @@ public struct Bool { self = value } - /// Returns a random Boolean + /// Returns a random Boolean value /// /// - Parameter generator: The random number generator to use when getting a /// random Boolean. - /// - Returns: A random Boolean. + /// - Returns: A random Boolean value. @inlinable public static func random( using generator: inout T @@ -99,11 +99,9 @@ public struct Bool { return (generator.next() >> 17) & 1 == 0 } - /// Returns a random Boolean + /// Returns a random Boolean value /// - /// - Parameter generator: The random number generator to use when getting a - /// random Boolean. - /// - Returns: A random Boolean. + /// - Returns: A random Boolean value. /// /// This uses the standard library's default random number generator. @inlinable diff --git a/stdlib/public/core/CMakeLists.txt b/stdlib/public/core/CMakeLists.txt index 866b3d44d0255..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 @@ -194,7 +194,6 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") list(APPEND swift_core_link_flags "-all_load") list(APPEND swift_core_framework_depends Foundation) list(APPEND swift_core_framework_depends CoreFoundation) - list(APPEND swift_core_framework_depends Security) list(APPEND swift_core_private_link_libraries icucore) else() # With the GNU linker the equivalent of -all_load is to tell the linker @@ -229,10 +228,6 @@ if(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") ${EXECINFO_LIBRARY}) endif() -if(CMAKE_SYSTEM_NAME STREQUAL "Windows") - list(APPEND swift_core_link_flags "$ENV{SystemRoot}/system32/bcrypt.dll") -endif() - option(SWIFT_CHECK_ESSENTIAL_STDLIB "Check core standard library layering by linking its essential subset" FALSE) diff --git a/stdlib/public/core/Collection.swift b/stdlib/public/core/Collection.swift index 220eefccd5f1d..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 } @@ -1038,9 +1057,8 @@ extension Collection { guard !isEmpty else { return nil } let random = generator.next(upperBound: UInt(count)) let index = self.index( - self.startIndex, - offsetBy: IndexDistance(random), - limitedBy: self.endIndex + startIndex, + offsetBy: numericCast(random) ) return self[index] } diff --git a/stdlib/public/core/CollectionAlgorithms.swift b/stdlib/public/core/CollectionAlgorithms.swift index aeee8e0d41ca3..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 @@ -411,8 +411,6 @@ extension Sequence { /// to `numbers.shuffled()` above is equivalent to calling /// `numbers.shuffled(using: &Random.default)`. /// - /// - Parameter generator: The random number generator to use when shuffling - /// the sequence. /// - Returns: A shuffled array of this sequence's elements. /// /// - Complexity: O(*n*) @@ -422,7 +420,7 @@ extension Sequence { } } -extension MutableCollection { +extension MutableCollection where Self : RandomAccessCollection { /// Shuffles the collection in place, using the given generator as a source /// for randomness. /// @@ -442,6 +440,7 @@ extension MutableCollection { public mutating func shuffle( using generator: inout T ) { + let count = self.count guard count > 1 else { return } var amount = count var currentIndex = startIndex diff --git a/stdlib/public/core/FloatingPoint.swift.gyb b/stdlib/public/core/FloatingPoint.swift.gyb index 0b129bcb4ddbc..a98af67353427 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 @@ -2378,7 +2378,7 @@ extension BinaryFloatingPoint { % exampleRange = '10.0..<20.0' if Range == 'Range' else '10.0...20.0' extension BinaryFloatingPoint where Self.RawSignificand : FixedWidthInteger, - Self.RawSignificand.Stride : SignedInteger & FixedWidthInteger, + Self.RawSignificand.Stride : SignedInteger, Self.RawSignificand.Magnitude : UnsignedInteger { /// Returns a random value within the specified range, using the given @@ -2395,7 +2395,7 @@ where Self.RawSignificand : FixedWidthInteger, /// // Prints "14.2286325689993" /// // Prints "13.1485686260762" /// - /// The `random(using:)` static method chooses a random value from a + /// 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 @@ -2414,12 +2414,10 @@ where Self.RawSignificand : FixedWidthInteger, in range: ${Range}, using generator: inout T ) -> Self { -% if 'Closed' not in Range: _precondition( - range.lowerBound != range.upperBound, - "Can't get random value with lowerBound == upperBound" + !range.isEmpty, + "Can't get random value with an empty range" ) -% end let delta = range.upperBound - range.lowerBound let rand: Self.RawSignificand if Self.RawSignificand.bitWidth == Self.significandBitCount + 1 { @@ -2431,7 +2429,8 @@ where Self.RawSignificand : FixedWidthInteger, } % end } else { - let maxSignificand = Self.RawSignificand(1 << (Self.significandBitCount + 1)) + let significandCount = Self.significandBitCount + 1 + let maxSignificand: Self.RawSignificand = 1 << significandCount % if 'Closed' not in Range: rand = generator.next(upperBound: maxSignificand) % else: diff --git a/stdlib/public/core/Integers.swift.gyb b/stdlib/public/core/Integers.swift.gyb index 09170003e7eec..3b41ea3b8d3a5 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 @@ -2560,11 +2560,9 @@ extension ${Range} public func randomElement( using generator: inout T ) -> Element? { -% if 'Closed' not in Range: - guard lowerBound != upperBound else { + guard !isEmpty else { return nil } -% end let isLowerNegative = Bound.isSigned && lowerBound < 0 let sameSign = !Bound.isSigned || isLowerNegative == (upperBound < 0) % if 'Closed' not in Range: @@ -2658,12 +2656,10 @@ where Self.Stride : SignedInteger, in range: ${Range}, using generator: inout T ) -> Self { -% if 'Closed' not in Range: _precondition( - range.lowerBound != range.upperBound, - "Can't get random value with lowerBound == upperBound" + !range.isEmpty, + "Can't get random value with an empty range" ) -% end return range.randomElement(using: &generator)! } diff --git a/stdlib/public/core/Random.swift b/stdlib/public/core/Random.swift index 9a8f449b890a6..421cf16c10b7d 100644 --- a/stdlib/public/core/Random.swift +++ b/stdlib/public/core/Random.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) 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 @@ -32,7 +32,7 @@ import SwiftShims /// } /// /// static func randomWeekday() -> Weekday { -/// return Weekday.randomWeekday(using: Random.default) +/// return Weekday.randomWeekday(using: &Random.default) /// } /// } /// @@ -64,7 +64,7 @@ extension RandomNumberGenerator { @inlinable public mutating func next() -> T { if T.bitWidth <= UInt64.bitWidth { - return T(truncatingIfNeeded: self.next()) + return T(truncatingIfNeeded: next()) } let (quotient, remainder) = T.bitWidth.quotientAndRemainder( @@ -73,11 +73,11 @@ extension RandomNumberGenerator { var tmp: T = 0 for i in 0 ..< quotient { - tmp += T(truncatingIfNeeded: self.next()) &<< (UInt64.bitWidth * i) + tmp += T(truncatingIfNeeded: next()) &<< (UInt64.bitWidth * i) } if remainder != 0 { - let random = self.next() + let random = next() let mask = UInt64.max &>> (UInt64.bitWidth - remainder) tmp += T(truncatingIfNeeded: random & mask) &<< (UInt64.bitWidth * quotient) } @@ -94,12 +94,13 @@ extension RandomNumberGenerator { public mutating func next( 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 = self.next() + random = next() } while random < range return random % upperBound @@ -118,8 +119,18 @@ extension RandomNumberGenerator { /// /// `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 shared, default instance of the `Range` random number generator. + /// The default instance of the `Random` random number generator. public static var `default`: Random { get { return Random() } set { /* Discard */ } @@ -130,6 +141,7 @@ public struct Random : RandomNumberGenerator { /// 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) @@ -147,7 +159,7 @@ public struct Random : RandomNumberGenerator { } } -public // @testable +@usableFromInline internal// @testable func _stdlib_random(_ bytes: UnsafeMutableRawBufferPointer) { if !bytes.isEmpty { _stdlib_random(bytes.baseAddress!, bytes.count) diff --git a/stdlib/public/stubs/LibcShims.cpp b/stdlib/public/stubs/LibcShims.cpp index d2838eb1ee55d..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 @@ -11,51 +11,24 @@ //===----------------------------------------------------------------------===// #if defined(__APPLE__) -# define _REENTRANT -# include -# include -#endif - -#if defined(__CYGWIN__) -# include -# if (CYGWIN_VERSION_API_MAJOR > 0) || (CYGWIN_VERSION_API_MINOR >= 306) -# include -# define SWIFT_STDLIB_USING_GETRANDOM -# endif -#endif - -#if defined(__Fuchsia__) -# include -# define SWIFT_STDLIB_USING_GETENTROPY -#endif - -#if defined(__linux__) -# include -# if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,17,0)) -# include -# if defined(__BIONIC__) || (defined(__GLIBC_PREREQ) && __GLIBC_PREREQ(2,25)) -# include -# define SWIFT_STDLIB_USING_GETRANDOM -# endif -# endif +#define _REENTRANT +#include #endif #if defined(_WIN32) && !defined(__CYGWIN__) -# include -# define WIN32_LEAN_AND_MEAN -# define WIN32_NO_STATUS -# include -# undef WIN32_NO_STATUS -# include -# include -# include +#include +#define WIN32_LEAN_AND_MEAN +#include +#include +#pragma comment(lib, "Bcrypt.lib") #else -# include -# include -# include -# include +#include +#include +#include +#include #endif +#include #include #include #include @@ -63,6 +36,9 @@ #include #include #include +#if __has_include() +#include +#endif #include #include #include @@ -71,6 +47,7 @@ #include "swift/Basic/Lazy.h" #include "swift/Runtime/Config.h" #include "swift/Runtime/Debug.h" +#include "swift/Runtime/Mutex.h" #include "../SwiftShims/LibcShims.h" using namespace swift; @@ -342,22 +319,22 @@ swift::_stdlib_cxx11_mt19937_uniform(__swift_uint32_t 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) { - if (__builtin_available(macOS 10.12, iOS 10, tvOS 10, watchOS 3, *)) { - arc4random_buf(buf, nbytes); - } else { - OSStatus status = SecRandomCopyBytes(kSecRandomDefault, nbytes, buf); - if (status != errSecSuccess) { - fatalError(0, "Fatal error: %d in '%s'\n", status, __func__); - } - } + arc4random_buf(buf, nbytes); } #elif defined(_WIN32) && !defined(__CYGWIN__) -// TODO: Test on Windows +#warning TODO: Test _stdlib_random on Windows SWIFT_RUNTIME_STDLIB_INTERNAL void swift::_stdlib_random(void *buf, __swift_size_t nbytes) { @@ -365,45 +342,47 @@ void swift::_stdlib_random(void *buf, __swift_size_t nbytes) { static_cast(buf), static_cast(nbytes), BCRYPT_USE_SYSTEM_PREFERRED_RNG); - if (!NT_SUCCESS(status)) { - fatalError(0, "Fatal error: %#.8x in '%s'\n", status, __func__); - } -} - -#elif defined(SWIFT_STDLIB_USING_GETENTROPY) - -SWIFT_RUNTIME_STDLIB_INTERNAL -void swift::_stdlib_random(void *buf, __swift_size_t nbytes) { - while (nbytes > 0) { - constexpr __swift_size_t max_nbytes = 256; - __swift_size_t actual_nbytes = (nbytes < max_nbytes ? - nbytes : max_nbytes); - if (0 != getentropy(buf, actual_nbytes)) { - fatalError(0, "Fatal error: %d in '%s'\n", errno, __func__); - } - buf = static_cast(buf) + actual_nbytes; - nbytes -= actual_nbytes; + 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) { -#if !defined(SWIFT_STDLIB_USING_GETRANDOM) - static const int fd = _stdlib_open("/dev/urandom", O_RDONLY, 0); - if (fd < 0) { - fatalError(0, "Fatal error: %d in '%s'\n", errno, __func__); - } -#endif while (nbytes > 0) { -#if !defined(SWIFT_STDLIB_USING_GETRANDOM) - __swift_ssize_t actual_nbytes = _stdlib_read(fd, buf, nbytes); -#else - __swift_ssize_t actual_nbytes = getrandom(buf, 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) { - if (errno == EINTR) { continue; } + 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; diff --git a/test/stdlib/Random.swift b/test/stdlib/Random.swift index ab86c653fd9d7..c3b465bc66aad 100644 --- a/test/stdlib/Random.swift +++ b/test/stdlib/Random.swift @@ -1,22 +1,44 @@ -// RUN: %target-run-simple-swift +// RUN: %target-run-stdlib-swift // REQUIRES: executable_test import StdlibUnittest +import StdlibCollectionUnittest let RandomTests = TestSuite("Random") +// _stdlib_random + +RandomTests.test("_stdlib_random") { + 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 { _stdlib_random($0) } + expectNotEqual(bytes1, bytes2) + expectNotEqual(bytes1, zeros) + + bytes2.withUnsafeMutableBytes { _stdlib_random($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) - expectTrue(randomNumber1 != randomNumber2) + 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) - expectTrue(randomDouble1 != randomDouble2) + expectNotEqual(randomDouble1, randomDouble2) } // Random integers in ranges @@ -24,47 +46,48 @@ RandomTests.test("basic random numbers") { func integerRangeTest(_ type: T.Type) where T.Stride: SignedInteger, T.Magnitude: UnsignedInteger { - let testRange = 0 ..< 1_000 - var integerSet: Set = [] + 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 - let minOpenRange = T.min ..< (T.min + 10) - for _ in testRange { - let random = T.random(in: minOpenRange) - expectTrue(minOpenRange.contains(random)) - integerSet.insert(random) - } - expectTrue(integerSet == Set(minOpenRange)) - integerSet.removeAll() + testRange(T.min ..< (T.min + 10)) // min closed range - let minClosedRange = T.min ... (T.min + 10) - for _ in testRange { - let random = T.random(in: minClosedRange) - expectTrue(minClosedRange.contains(random)) - integerSet.insert(random) - } - expectTrue(integerSet == Set(minClosedRange)) - integerSet.removeAll() + testClosedRange(T.min ... (T.min + 10)) // max open range - let maxOpenRange = (T.max - 10) ..< T.max - for _ in testRange { - let random = T.random(in: maxOpenRange) - expectTrue(maxOpenRange.contains(random)) - integerSet.insert(random) - } - expectTrue(integerSet == Set(maxOpenRange)) - integerSet.removeAll() + testRange((T.max - 10) ..< T.max) // max closed range - let maxClosedRange = (T.max - 10) ... T.max - for _ in testRange { - let random = T.random(in: maxClosedRange) - expectTrue(maxClosedRange.contains(random)) - integerSet.insert(random) + 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) } - expectTrue(integerSet == Set(maxClosedRange)) } RandomTests.test("random integers in ranges") { @@ -111,24 +134,37 @@ RandomTests.test("random floating points in ranges") { // 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") { - var alphabet = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", + 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(newAlphabet != alphabet) - alphabet = newAlphabet + expectTrue(alphabet.elementsEqual(newAlphabet.sorted())) + expectFalse(oldAlphabet.elementsEqual(newAlphabet)) + oldAlphabet = MinimalSequence(elements: newAlphabet) } } @@ -185,11 +221,11 @@ RandomTests.test("different random number generators") { } } - expectTrue(intPasses[0] == intPasses[1]) - expectTrue(doublePasses[0] == doublePasses[1]) - expectTrue(boolPasses[0] == boolPasses[1]) - expectTrue(collectionPasses[0] == collectionPasses[1]) - expectTrue(shufflePasses[0] == shufflePasses[1]) + 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 From 86d657c57a6d828a726e75235af40aca75d926ef Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Wed, 9 May 2018 17:51:36 +0100 Subject: [PATCH 5/7] [stdlib] Add & implement Random._fill(bytes:) requirement (cherry picked from commit 12a2b326449473127c83ea041e8bd69c88735548) --- stdlib/public/core/Random.swift | 29 ++++++++++++++++++++++++----- test/stdlib/Random.swift | 8 ++++---- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/stdlib/public/core/Random.swift b/stdlib/public/core/Random.swift index 421cf16c10b7d..95a163695f556 100644 --- a/stdlib/public/core/Random.swift +++ b/stdlib/public/core/Random.swift @@ -54,6 +54,26 @@ public protocol RandomNumberGenerator { /// /// - 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 { @@ -157,11 +177,10 @@ public struct Random : RandomNumberGenerator { _stdlib_random(&random, MemoryLayout.size) return random } -} -@usableFromInline internal// @testable -func _stdlib_random(_ bytes: UnsafeMutableRawBufferPointer) { - if !bytes.isEmpty { - _stdlib_random(bytes.baseAddress!, bytes.count) + public mutating func _fill(bytes buffer: UnsafeMutableRawBufferPointer) { + if !buffer.isEmpty { + _stdlib_random(buffer.baseAddress!, buffer.count) + } } } diff --git a/test/stdlib/Random.swift b/test/stdlib/Random.swift index c3b465bc66aad..3d767f4f99f6f 100644 --- a/test/stdlib/Random.swift +++ b/test/stdlib/Random.swift @@ -6,9 +6,9 @@ import StdlibCollectionUnittest let RandomTests = TestSuite("Random") -// _stdlib_random +// _fill(bytes:) -RandomTests.test("_stdlib_random") { +RandomTests.test("_fill(bytes:)") { for count in [100, 1000] { var bytes1 = [UInt8](repeating: 0, count: count) var bytes2 = [UInt8](repeating: 0, count: count) @@ -17,11 +17,11 @@ RandomTests.test("_stdlib_random") { expectEqual(bytes1, zeros) expectEqual(bytes2, zeros) - bytes1.withUnsafeMutableBytes { _stdlib_random($0) } + bytes1.withUnsafeMutableBytes { Random.default._fill(bytes: $0) } expectNotEqual(bytes1, bytes2) expectNotEqual(bytes1, zeros) - bytes2.withUnsafeMutableBytes { _stdlib_random($0) } + bytes2.withUnsafeMutableBytes { Random.default._fill(bytes: $0) } expectNotEqual(bytes1, bytes2) expectNotEqual(bytes2, zeros) } From 282a0c12bd95b8963a47faef8daaa04c2236d76c Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Wed, 9 May 2018 18:02:34 +0100 Subject: [PATCH 6/7] [stdlib] Random: don't randomize FixedWidthIntegers by overwriting their raw memory Custom FixedWidthInteger types may not support this. Introduce a new (non-public) FixedWidthInteger requirement for generating random values; implement it using &<( + using generator: inout R + ) -> Self } extension FixedWidthInteger { @@ -3022,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 --------------------------------------------------===// //===----------------------------------------------------------------------===// @@ -4015,6 +4038,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 index 95a163695f556..38e7043f25343 100644 --- a/stdlib/public/core/Random.swift +++ b/stdlib/public/core/Random.swift @@ -83,26 +83,7 @@ extension RandomNumberGenerator { /// every value of `T` is equally likely to be returned. @inlinable public mutating func next() -> T { - if T.bitWidth <= UInt64.bitWidth { - return T(truncatingIfNeeded: next()) - } - - let (quotient, remainder) = T.bitWidth.quotientAndRemainder( - dividingBy: UInt64.bitWidth - ) - var tmp: T = 0 - - for i in 0 ..< quotient { - tmp += T(truncatingIfNeeded: next()) &<< (UInt64.bitWidth * i) - } - - if remainder != 0 { - let random = next() - let mask = UInt64.max &>> (UInt64.bitWidth - remainder) - tmp += T(truncatingIfNeeded: random & mask) &<< (UInt64.bitWidth * quotient) - } - - return tmp + return T._random(using: &self) } /// Returns a random value that is less than the given upper bound. @@ -167,16 +148,6 @@ public struct Random : RandomNumberGenerator { _stdlib_random(&random, MemoryLayout.size) return random } - - /// 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. - public mutating func next() -> T { - var random: T = 0 - _stdlib_random(&random, MemoryLayout.size) - return random - } public mutating func _fill(bytes buffer: UnsafeMutableRawBufferPointer) { if !buffer.isEmpty { From eba0e1f6e30da7f8914ef6891ffe6e1c78fd8e2d Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Thu, 10 May 2018 19:47:14 +0100 Subject: [PATCH 7/7] [test] Move Random tests under validation-test StdlibCollectionUnittest is not available in smoke tests. (cherry picked from commit c03fe15caf95260472655c30f5fa44846da6bb0c) --- {test => validation-test}/stdlib/Random.swift | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {test => validation-test}/stdlib/Random.swift (100%) diff --git a/test/stdlib/Random.swift b/validation-test/stdlib/Random.swift similarity index 100% rename from test/stdlib/Random.swift rename to validation-test/stdlib/Random.swift