Skip to content

Commit feb8b62

Browse files
committed
Add Xoroshiro and Xoshiro random number generators
1 parent 483b8a5 commit feb8b62

5 files changed

Lines changed: 133 additions & 2 deletions

File tree

Sources/Numerix/Documentation.docc/Random.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ Numerix provides random number generators
99
## Topics
1010

1111
- ``Wyrand``
12+
- ``Xoshiro128Plus``
13+
- ``Xoroshiro128Plus``
14+
- ``Xoroshiro128PlusPlus``
1215
- ``Vector/random(size:seed:)``
1316
- ``Vector/randomDistribution(size:dist:)``
1417
- ``Vector/randomBNNS(size:bounds:seed:)``

Sources/Numerix/RandomModule/Wyrand.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,17 @@ public struct Wyrand: RandomNumberGenerator {
2323
return mul.low ^ mul.high
2424
}
2525

26+
/// Generate a random single-precision value from a uniform distribution over [0, 1) which includes zero but
27+
/// excludes one.
28+
/// - Returns: Random single-precision value.
2629
public mutating func nextUniform() -> Float {
2730
let x = self.next()
2831
return Float(x >> 40) * 0x1.0p-24
2932
}
3033

34+
/// Generate a random double-precision value from a uniform distribution over [0, 1) which includes zero but
35+
/// excludes one.
36+
/// - Returns: Random double-precision value.
3137
public mutating func nextUniform() -> Double {
3238
let x = self.next()
3339
return Double(x >> 11) * 0x1.0p-53
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
Xoroshiro128+ and Xoroshiro128++ pseudorandom number generators (PRNGs).
3+
*/
4+
5+
import Accelerate
6+
7+
func rotl(_ x: UInt64, _ k: Int) -> UInt64 {
8+
(x &<< k) | (x &>> (64 &- k))
9+
}
10+
11+
/// Xoroshiro128+ is a 64-bit pseudorandom number generator (PRNG) for double-precision values.
12+
public struct Xoroshiro128Plus: RandomNumberGenerator {
13+
private var state: (UInt64, UInt64)
14+
15+
public init(seed: (UInt64, UInt64)? = nil) {
16+
let a = UInt64.random(in: 0..<UInt64.max)
17+
let b = UInt64.random(in: 0..<UInt64.max)
18+
state = seed ?? (a, b)
19+
}
20+
21+
public mutating func next() -> UInt64 {
22+
let s0 = state.0
23+
var s1 = state.1
24+
let result = s0 &+ s1
25+
26+
s1 ^= s0
27+
state.0 = rotl(s0, 24) ^ s1 ^ (s1 << 16)
28+
state.1 = rotl(s1, 37)
29+
30+
return result
31+
}
32+
33+
/// Generate a random double-precision value from a uniform distribution over [0, 1) which includes zero but
34+
/// excludes one.
35+
/// - Returns: Random double-precision value.
36+
public mutating func next() -> Double {
37+
Double(next() >> 11) * 0x1.0p-53
38+
}
39+
}
40+
41+
/// Xoroshiro128++ is a 64-bit pseudorandom number generator (PRNG) for double-precision values.
42+
public struct Xoroshiro128PlusPlus: RandomNumberGenerator {
43+
private var state: (UInt64, UInt64)
44+
45+
public init(seed: (UInt64, UInt64)? = nil) {
46+
let a = UInt64.random(in: 0..<UInt64.max)
47+
let b = UInt64.random(in: 0..<UInt64.max)
48+
state = seed ?? (a, b)
49+
}
50+
51+
public mutating func next() -> UInt64 {
52+
let s0 = state.0
53+
var s1 = state.1
54+
let result = rotl(s0 &+ s1, 17) &+ s0
55+
56+
s1 ^= s0
57+
state.0 = rotl(s0, 49) ^ s1 ^ (s1 << 21)
58+
state.1 = rotl(s1, 28)
59+
60+
return result
61+
}
62+
63+
/// Generate a random double-precision value from a uniform distribution over [0, 1) which includes zero but
64+
/// excludes one.
65+
/// - Returns: Random double-precision value.
66+
public mutating func next() -> Double {
67+
Double(next() >> 11) * 0x1.0p-53
68+
}
69+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
Xoshiro128+ pseudorandom number generator (PRNG).
3+
*/
4+
5+
import Accelerate
6+
7+
/// Xoshiro128+ is a 32-bit pseudorandom number generator (PRNG) for single-precision (floating point) values.
8+
public struct Xoshiro128Plus {
9+
private var state: (UInt32, UInt32, UInt32, UInt32)
10+
11+
public init(seed: (UInt32, UInt32, UInt32, UInt32)? = nil) {
12+
state = seed ?? (arc4random(), arc4random(), arc4random(), arc4random())
13+
}
14+
15+
public mutating func next() -> UInt32 {
16+
let result = state.0 &+ state.3
17+
let t = state.1 << 9
18+
19+
state.2 ^= state.0
20+
state.3 ^= state.1
21+
state.1 ^= state.2
22+
state.0 ^= state.3
23+
24+
state.2 ^= t
25+
26+
state.3 = (state.3 << 11) | (state.3 >> (32 - 11))
27+
28+
return result
29+
}
30+
31+
/// Generate a random single-precision value from a uniform distribution over [0, 1) which includes zero but
32+
/// excludes one.
33+
/// - Returns: Random single-precision value.
34+
public mutating func next() -> Float {
35+
Float(next() >> 8) * 0x1.0p-24
36+
}
37+
}

Tests/RandomTests.swift

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@ Tests for random numbers and random vectors.
33
*/
44

55
import Testing
6-
@testable import Numerix
6+
import Numerix
77

88
struct RandomTests {
99

10-
@Test func randomWyRand() {
10+
@Test func wyrand() {
1111
var rand = Wyrand(seed: 12345)
1212
#expect(rand.next() == 13157676964440363053)
1313

@@ -18,6 +18,22 @@ struct RandomTests {
1818
#expect(b == 0.7952823641045631)
1919
}
2020

21+
@Test func xoshiro() {
22+
var rng = Xoshiro128Plus(seed: (2719949631, 2719949631, 2719949631, 2719949631))
23+
let a: Float = rng.next()
24+
#expect(a == 0.26657522)
25+
}
26+
27+
@Test func xoroshiro() {
28+
var rng = Xoroshiro128Plus(seed: (12274935454779349997, 7213431619994351707))
29+
let a: Double = rng.next()
30+
#expect(a == 0.05646649603323106)
31+
32+
var rngg = Xoroshiro128PlusPlus(seed: (12274935454779349997, 7213431619994351707))
33+
let b: Double = rngg.next()
34+
#expect(b == 0.8419936520290631)
35+
}
36+
2137
@Test func randomVector() {
2238
// No seed
2339
let a = Vector<Float>.random(size: 3)

0 commit comments

Comments
 (0)