Update core:math/rand to use context.random_generator and remove rand.Rand

This commit is contained in:
gingerBill
2024-06-15 15:33:24 +01:00
parent 318d5e4a7e
commit 7ec17ecf98
8 changed files with 197 additions and 387 deletions
+62 -289
View File
@@ -10,52 +10,18 @@ import "core:crypto"
import "core:math"
import "core:mem"
Rand :: struct {
state: u64,
inc: u64,
is_system: bool,
Default_Random_State :: runtime.Default_Random_State
default_random_generator :: runtime.default_random_generator
create :: proc(seed: u64) -> (state: Default_Random_State) {
seed := seed
runtime.default_random_generator(&state)
runtime.default_random_generator_proc(&state, .Reset, ([^]byte)(&seed)[:size_of(seed)])
return
}
to_random_generator :: proc(r: ^Rand) -> runtime.Random_Generator {
return {
procedure = proc(data: rawptr, mode: runtime.Random_Generator_Mode, p: []byte) {
r := (^Rand)(data)
switch mode {
case .Read:
_ = read(p, r)
case .Reset:
if r.is_system {
return
}
seed: u64
runtime.mem_copy_non_overlapping(&seed, raw_data(p), min(size_of(seed), len(p)))
init(r, seed)
case .Query_Info:
if len(p) != size_of(runtime.Random_Generator_Query_Info) {
return
}
info := (^runtime.Random_Generator_Query_Info)(raw_data(p))
info^ += {.Uniform}
if r.is_system {
info^ += {.External_Entropy}
} else {
info^ += {.Resettable}
}
}
},
data = r,
}
}
@(private)
global_rand := create(u64(intrinsics.read_cycle_counter()))
/*
Sets the seed used by the global random number generator.
Reset the seed used by the context.random_generator.
Inputs:
- seed: The seed value
@@ -72,143 +38,46 @@ Example:
Possible Output:
10
*/
@(deprecated="Prefer `rand.reset`")
set_global_seed :: proc(seed: u64) {
init(&global_rand, seed)
runtime.random_generator_reset_u64(context.random_generator, seed)
}
/*
Creates a new random number generator.
Reset the seed used by the context.random_generator.
Inputs:
- seed: The seed value to create the random number generator with
Returns:
- res: The created random number generator
- seed: The seed value
Example:
import "core:math/rand"
import "core:fmt"
create_example :: proc() {
my_rand := rand.create(1)
fmt.println(rand.uint64(&my_rand))
set_global_seed_example :: proc() {
rand.set_global_seed(1)
fmt.println(rand.uint64())
}
Possible Output:
10
*/
@(require_results)
create :: proc(seed: u64) -> (res: Rand) {
r: Rand
init(&r, seed)
return r
reset :: proc(seed: u64) {
runtime.random_generator_reset_u64(context.random_generator, seed)
}
/*
Initialises a random number generator.
Inputs:
- r: The random number generator to initialise
- seed: The seed value to initialise this random number generator
Example:
import "core:math/rand"
import "core:fmt"
init_example :: proc() {
my_rand: rand.Rand
rand.init(&my_rand, 1)
fmt.println(rand.uint64(&my_rand))
}
Possible Output:
10
*/
init :: proc(r: ^Rand, seed: u64) {
r.state = 0
r.inc = (seed << 1) | 1
_random_u64(r)
r.state += seed
_random_u64(r)
}
/*
Initialises a random number generator to use the system random number generator.
The system random number generator is platform specific, and not supported
on all targets.
Inputs:
- r: The random number generator to use the system random number generator
WARNING: Panics if the system random number generator is not supported.
Support can be determined via the `core:crypto.HAS_RAND_BYTES` constant.
Example:
import "core:crypto"
import "core:math/rand"
import "core:fmt"
init_as_system_example :: proc() {
my_rand: rand.Rand
switch crypto.HAS_RAND_BYTES {
case true:
rand.init_as_system(&my_rand)
fmt.println(rand.uint64(&my_rand))
case false:
fmt.println("system random not supported!")
}
}
Possible Output:
10
*/
init_as_system :: proc(r: ^Rand) {
if !crypto.HAS_RAND_BYTES {
panic(#procedure + " is not supported on this platform yet")
}
r.state = 0
r.inc = 0
r.is_system = true
}
@(private)
_random_u64 :: proc(r: ^Rand) -> u64 {
r := r
switch {
case r == nil:
if res: u64; runtime.random_generator_read_ptr(context.random_generator, &res, size_of(res)) {
return res
}
r = &global_rand
case r.is_system:
value: u64
crypto.rand_bytes((cast([^]u8)&value)[:size_of(u64)])
return value
}
old_state := r.state
r.state = old_state * 6364136223846793005 + (r.inc|1)
xor_shifted := (((old_state >> 59) + 5) ~ old_state) * 12605985483714917081
rot := (old_state >> 59)
return (xor_shifted >> rot) | (xor_shifted << ((-rot) & 63))
_random_u64 :: proc() -> (res: u64) {
ok := runtime.random_generator_read_ptr(context.random_generator, &res, size_of(res))
assert(ok, "uninitialized context.random_generator")
return
}
/*
Generates a random 32 bit value using the provided random number generator. If no generator is provided the global random number generator will be used.
Inputs:
- r: The random number generator to use, or nil for the global generator
Returns:
- val: A random unsigned 32 bit value
@@ -217,11 +86,7 @@ Example:
import "core:fmt"
uint32_example :: proc() {
// Using the global random number generator
fmt.println(rand.uint32())
// Using local random number generator
my_rand := rand.create(1)
fmt.println(rand.uint32(&my_rand))
}
Possible Output:
@@ -231,14 +96,11 @@ Possible Output:
*/
@(require_results)
uint32 :: proc(r: ^Rand = nil) -> (val: u32) { return u32(_random_u64(r)) }
uint32 :: proc() -> (val: u32) { return u32(_random_u64()) }
/*
Generates a random 64 bit value using the provided random number generator. If no generator is provided the global random number generator will be used.
Inputs:
- r: The random number generator to use, or nil for the global generator
Returns:
- val: A random unsigned 64 bit value
@@ -247,11 +109,7 @@ Example:
import "core:fmt"
uint64_example :: proc() {
// Using the global random number generator
fmt.println(rand.uint64())
// Using local random number generator
my_rand := rand.create(1)
fmt.println(rand.uint64(&my_rand))
}
Possible Output:
@@ -261,14 +119,11 @@ Possible Output:
*/
@(require_results)
uint64 :: proc(r: ^Rand = nil) -> (val: u64) { return _random_u64(r) }
uint64 :: proc() -> (val: u64) { return _random_u64() }
/*
Generates a random 128 bit value using the provided random number generator. If no generator is provided the global random number generator will be used.
Inputs:
- r: The random number generator to use, or nil for the global generator
Returns:
- val: A random unsigned 128 bit value
@@ -277,11 +132,7 @@ Example:
import "core:fmt"
uint128_example :: proc() {
// Using the global random number generator
fmt.println(rand.uint128())
// Using local random number generator
my_rand := rand.create(1)
fmt.println(rand.uint128(&my_rand))
}
Possible Output:
@@ -291,9 +142,9 @@ Possible Output:
*/
@(require_results)
uint128 :: proc(r: ^Rand = nil) -> (val: u128) {
a := u128(_random_u64(r))
b := u128(_random_u64(r))
uint128 :: proc() -> (val: u128) {
a := u128(_random_u64())
b := u128(_random_u64())
return (a<<64) | b
}
@@ -301,9 +152,6 @@ uint128 :: proc(r: ^Rand = nil) -> (val: u128) {
Generates a random 31 bit value using the provided random number generator. If no generator is provided the global random number generator will be used.
The sign bit will always be set to 0, thus all generated numbers will be positive.
Inputs:
- r: The random number generator to use, or nil for the global generator
Returns:
- val: A random 31 bit value
@@ -312,11 +160,7 @@ Example:
import "core:fmt"
int31_example :: proc() {
// Using the global random number generator
fmt.println(rand.int31())
// Using local random number generator
my_rand := rand.create(1)
fmt.println(rand.int31(&my_rand))
}
Possible Output:
@@ -325,15 +169,12 @@ Possible Output:
389
*/
@(require_results) int31 :: proc(r: ^Rand = nil) -> (val: i32) { return i32(uint32(r) << 1 >> 1) }
@(require_results) int31 :: proc() -> (val: i32) { return i32(uint32() << 1 >> 1) }
/*
Generates a random 63 bit value using the provided random number generator. If no generator is provided the global random number generator will be used.
The sign bit will always be set to 0, thus all generated numbers will be positive.
Inputs:
- r: The random number generator to use, or nil for the global generator
Returns:
- val: A random 63 bit value
@@ -342,11 +183,7 @@ Example:
import "core:fmt"
int63_example :: proc() {
// Using the global random number generator
fmt.println(rand.int63())
// Using local random number generator
my_rand := rand.create(1)
fmt.println(rand.int63(&my_rand))
}
Possible Output:
@@ -355,15 +192,12 @@ Possible Output:
389
*/
@(require_results) int63 :: proc(r: ^Rand = nil) -> (val: i64) { return i64(uint64(r) << 1 >> 1) }
@(require_results) int63 :: proc() -> (val: i64) { return i64(uint64() << 1 >> 1) }
/*
Generates a random 127 bit value using the provided random number generator. If no generator is provided the global random number generator will be used.
The sign bit will always be set to 0, thus all generated numbers will be positive.
Inputs:
- r: The random number generator to use, or nil for the global generator
Returns:
- val: A random 127 bit value
@@ -372,11 +206,7 @@ Example:
import "core:fmt"
int127_example :: proc() {
// Using the global random number generator
fmt.println(rand.int127())
// Using local random number generator
my_rand := rand.create(1)
fmt.println(rand.int127(&my_rand))
}
Possible Output:
@@ -385,14 +215,13 @@ Possible Output:
389
*/
@(require_results) int127 :: proc(r: ^Rand = nil) -> (val: i128) { return i128(uint128(r) << 1 >> 1) }
@(require_results) int127 :: proc() -> (val: i128) { return i128(uint128() << 1 >> 1) }
/*
Generates a random 31 bit value in the range `[0, n)` using the provided random number generator. If no generator is provided the global random number generator will be used.
Inputs:
- n: The upper bound of the generated number, this value is exclusive
- r: The random number generator to use, or nil for the global generator
Returns:
- val: A random 31 bit value in the range `[0, n)`
@@ -404,11 +233,7 @@ Example:
import "core:fmt"
int31_max_example :: proc() {
// Using the global random number generator
fmt.println(rand.int31_max(16))
// Using local random number generator
my_rand := rand.create(1)
fmt.println(rand.int31_max(1024, &my_rand))
}
Possible Output:
@@ -418,17 +243,17 @@ Possible Output:
*/
@(require_results)
int31_max :: proc(n: i32, r: ^Rand = nil) -> (val: i32) {
int31_max :: proc(n: i32) -> (val: i32) {
if n <= 0 {
panic("Invalid argument to int31_max")
}
if n&(n-1) == 0 {
return int31(r) & (n-1)
return int31() & (n-1)
}
max := i32((1<<31) - 1 - (1<<31)%u32(n))
v := int31(r)
v := int31()
for v > max {
v = int31(r)
v = int31()
}
return v % n
}
@@ -438,7 +263,6 @@ Generates a random 63 bit value in the range `[0, n)` using the provided random
Inputs:
- n: The upper bound of the generated number, this value is exclusive
- r: The random number generator to use, or nil for the global generator
Returns:
- val: A random 63 bit value in the range `[0, n)`
@@ -450,11 +274,7 @@ Example:
import "core:fmt"
int63_max_example :: proc() {
// Using the global random number generator
fmt.println(rand.int63_max(16))
// Using local random number generator
my_rand := rand.create(1)
fmt.println(rand.int63_max(1024, &my_rand))
}
Possible Output:
@@ -464,17 +284,17 @@ Possible Output:
*/
@(require_results)
int63_max :: proc(n: i64, r: ^Rand = nil) -> (val: i64) {
int63_max :: proc(n: i64) -> (val: i64) {
if n <= 0 {
panic("Invalid argument to int63_max")
}
if n&(n-1) == 0 {
return int63(r) & (n-1)
return int63() & (n-1)
}
max := i64((1<<63) - 1 - (1<<63)%u64(n))
v := int63(r)
v := int63()
for v > max {
v = int63(r)
v = int63()
}
return v % n
}
@@ -484,7 +304,6 @@ Generates a random 127 bit value in the range `[0, n)` using the provided random
Inputs:
- n: The upper bound of the generated number, this value is exclusive
- r: The random number generator to use, or nil for the global generator
Returns:
- val: A random 127 bit value in the range `[0, n)`
@@ -496,11 +315,7 @@ Example:
import "core:fmt"
int127_max_example :: proc() {
// Using the global random number generator
fmt.println(rand.int127_max(16))
// Using local random number generator
my_rand := rand.create(1)
fmt.println(rand.int127_max(1024, &my_rand))
}
Possible Output:
@@ -510,17 +325,17 @@ Possible Output:
*/
@(require_results)
int127_max :: proc(n: i128, r: ^Rand = nil) -> (val: i128) {
int127_max :: proc(n: i128) -> (val: i128) {
if n <= 0 {
panic("Invalid argument to int127_max")
}
if n&(n-1) == 0 {
return int127(r) & (n-1)
return int127() & (n-1)
}
max := i128((1<<127) - 1 - (1<<127)%u128(n))
v := int127(r)
v := int127()
for v > max {
v = int127(r)
v = int127()
}
return v % n
}
@@ -530,7 +345,6 @@ Generates a random integer value in the range `[0, n)` using the provided random
Inputs:
- n: The upper bound of the generated number, this value is exclusive
- r: The random number generator to use, or nil for the global generator
Returns:
- val: A random integer value in the range `[0, n)`
@@ -542,11 +356,7 @@ Example:
import "core:fmt"
int_max_example :: proc() {
// Using the global random number generator
fmt.println(rand.int_max(16))
// Using local random number generator
my_rand := rand.create(1)
fmt.println(rand.int_max(1024, &my_rand))
}
Possible Output:
@@ -556,23 +366,20 @@ Possible Output:
*/
@(require_results)
int_max :: proc(n: int, r: ^Rand = nil) -> (val: int) {
int_max :: proc(n: int) -> (val: int) {
if n <= 0 {
panic("Invalid argument to int_max")
}
when size_of(int) == 4 {
return int(int31_max(i32(n), r))
return int(int31_max(i32(n)))
} else {
return int(int63_max(i64(n), r))
return int(int63_max(i64(n)))
}
}
/*
Generates a random double floating point value in the range `[0, 1)` using the provided random number generator. If no generator is provided the global random number generator will be used.
Inputs:
- r: The random number generator to use, or nil for the global generator
Returns:
- val: A random double floating point value in the range `[0, 1)`
@@ -581,11 +388,7 @@ Example:
import "core:fmt"
float64_example :: proc() {
// Using the global random number generator
fmt.println(rand.float64())
// Using local random number generator
my_rand := rand.create(1)
fmt.println(rand.float64(&my_rand))
}
Possible Output:
@@ -594,14 +397,11 @@ Possible Output:
0.511
*/
@(require_results) float64 :: proc(r: ^Rand = nil) -> (val: f64) { return f64(int63_max(1<<53, r)) / (1 << 53) }
@(require_results) float64 :: proc() -> (val: f64) { return f64(int63_max(1<<53)) / (1 << 53) }
/*
Generates a random single floating point value in the range `[0, 1)` using the provided random number generator. If no generator is provided the global random number generator will be used.
Inputs:
- r: The random number generator to use, or nil for the global generator
Returns:
- val: A random single floating point value in the range `[0, 1)`
@@ -610,11 +410,7 @@ Example:
import "core:fmt"
float32_example :: proc() {
// Using the global random number generator
fmt.println(rand.float32())
// Using local random number generator
my_rand := rand.create(1)
fmt.println(rand.float32(&my_rand))
}
Possible Output:
@@ -623,7 +419,7 @@ Possible Output:
0.511
*/
@(require_results) float32 :: proc(r: ^Rand = nil) -> (val: f32) { return f32(int31_max(1<<24, r)) / (1 << 24) }
@(require_results) float32 :: proc() -> (val: f32) { return f32(int31_max(1<<24)) / (1 << 24) }
/*
Generates a random double floating point value in the range `[low, high)` using the provided random number generator. If no generator is provided the global random number generator will be used.
@@ -633,7 +429,6 @@ WARNING: Panics if `high < low`
Inputs:
- low: The lower bounds of the value, this value is inclusive
- high: The upper bounds of the value, this value is exclusive
- r: The random number generator to use, or nil for the global generator
Returns:
- val: A random double floating point value in the range [low, high)
@@ -643,11 +438,7 @@ Example:
import "core:fmt"
float64_range_example :: proc() {
// Using the global random number generator
fmt.println(rand.float64_range(-10, 300))
// Using local random number generator
my_rand := rand.create(1)
fmt.println(rand.float64_range(600, 900, &my_rand))
}
Possible Output:
@@ -656,9 +447,9 @@ Possible Output:
673.130
*/
@(require_results) float64_range :: proc(low, high: f64, r: ^Rand = nil) -> (val: f64) {
@(require_results) float64_range :: proc(low, high: f64) -> (val: f64) {
assert(low <= high, "low must be lower than or equal to high")
val = (high-low)*float64(r) + low
val = (high-low)*float64() + low
if val >= high {
val = max(low, high * (1 - math.F64_EPSILON))
}
@@ -671,7 +462,6 @@ Generates a random single floating point value in the range `[low, high)` using
Inputs:
- low: The lower bounds of the value, this value is inclusive
- high: The upper bounds of the value, this value is exclusive
- r: The random number generator to use, or nil for the global generator
Returns:
- val: A random single floating point value in the range [low, high)
@@ -683,11 +473,7 @@ Example:
import "core:fmt"
float32_range_example :: proc() {
// Using the global random number generator
fmt.println(rand.float32_range(-10, 300))
// Using local random number generator
my_rand := rand.create(1)
fmt.println(rand.float32_range(600, 900, &my_rand))
}
Possible Output:
@@ -696,9 +482,9 @@ Possible Output:
673.130
*/
@(require_results) float32_range :: proc(low, high: f32, r: ^Rand = nil) -> (val: f32) {
@(require_results) float32_range :: proc(low, high: f32) -> (val: f32) {
assert(low <= high, "low must be lower than or equal to high")
val = (high-low)*float32(r) + low
val = (high-low)*float32() + low
if val >= high {
val = max(low, high * (1 - math.F32_EPSILON))
}
@@ -711,7 +497,6 @@ Due to floating point precision there is no guarantee if the upper and lower bou
Inputs:
- p: The byte slice to fill
- r: The random number generator to use, or nil for the global generator
Returns:
- n: The number of bytes generated
@@ -721,7 +506,6 @@ Example:
import "core:fmt"
read_example :: proc() {
// Using the global random number generator
data: [8]byte
n := rand.read(data[:])
fmt.println(n)
@@ -735,12 +519,12 @@ Possible Output:
*/
@(require_results)
read :: proc(p: []byte, r: ^Rand = nil) -> (n: int) {
read :: proc(p: []byte) -> (n: int) {
pos := i8(0)
val := i64(0)
for n = 0; n < len(p); n += 1 {
if pos == 0 {
val = int63(r)
val = int63()
pos = 7
}
p[n] = byte(val)
@@ -757,7 +541,6 @@ Creates a slice of `int` filled with random values using the provided random num
Inputs:
- n: The size of the created slice
- r: The random number generator to use, or nil for the global generator
- allocator: (default: context.allocator)
Returns:
@@ -770,16 +553,10 @@ Example:
import "core:fmt"
perm_example :: proc() -> (err: mem.Allocator_Error) {
// Using the global random number generator and using the context allocator
data := rand.perm(4) or_return
fmt.println(data)
defer delete(data, context.allocator)
// Using local random number generator and temp allocator
my_rand := rand.create(1)
data_tmp := rand.perm(4, &my_rand, context.temp_allocator) or_return
fmt.println(data_tmp)
return
}
@@ -790,10 +567,10 @@ Possible Output:
*/
@(require_results)
perm :: proc(n: int, r: ^Rand = nil, allocator := context.allocator) -> (res: []int, err: mem.Allocator_Error) #optional_allocator_error {
perm :: proc(n: int, allocator := context.allocator) -> (res: []int, err: mem.Allocator_Error) #optional_allocator_error {
m := make([]int, n, allocator) or_return
for i := 0; i < n; i += 1 {
j := int_max(i+1, r)
j := int_max(i+1)
m[i] = m[j]
m[j] = i
}
@@ -805,14 +582,12 @@ Randomizes the ordering of elements for the provided slice. If no generator is p
Inputs:
- array: The slice to randomize
- r: The random number generator to use, or nil for the global generator
Example:
import "core:math/rand"
import "core:fmt"
shuffle_example :: proc() {
// Using the global random number generator
data: [4]int = { 1, 2, 3, 4 }
fmt.println(data) // the contents are in order
rand.shuffle(data[:])
@@ -825,14 +600,14 @@ Possible Output:
[2, 4, 3, 1]
*/
shuffle :: proc(array: $T/[]$E, r: ^Rand = nil) {
shuffle :: proc(array: $T/[]$E) {
n := i64(len(array))
if n < 2 {
return
}
for i := i64(n - 1); i > 0; i -= 1 {
j := int63_max(i + 1, r)
j := int63_max(i + 1)
array[i], array[j] = array[j], array[i]
}
}
@@ -842,7 +617,6 @@ Returns a random element from the provided slice. If no generator is provided th
Inputs:
- array: The slice to choose an element from
- r: The random number generator to use, or nil for the global generator
Returns:
- res: A random element from `array`
@@ -852,7 +626,6 @@ Example:
import "core:fmt"
choice_example :: proc() {
// Using the global random number generator
data: [4]int = { 1, 2, 3, 4 }
fmt.println(rand.choice(data[:]))
fmt.println(rand.choice(data[:]))
@@ -869,17 +642,17 @@ Possible Output:
*/
@(require_results)
choice :: proc(array: $T/[]$E, r: ^Rand = nil) -> (res: E) {
choice :: proc(array: $T/[]$E) -> (res: E) {
n := i64(len(array))
if n < 1 {
return E{}
}
return array[int63_max(n, r)]
return array[int63_max(n)]
}
@(require_results)
choice_enum :: proc($T: typeid, r: ^Rand = nil) -> T
choice_enum :: proc($T: typeid) -> T
where
intrinsics.type_is_enum(T),
size_of(T) <= 8,
@@ -887,11 +660,11 @@ choice_enum :: proc($T: typeid, r: ^Rand = nil) -> T
{
when intrinsics.type_is_unsigned(intrinsics.type_core_type(T)) &&
u64(max(T)) > u64(max(i64)) {
i := uint64(r) % u64(len(T))
i := uint64() % u64(len(T))
i += u64(min(T))
return T(i)
} else {
i := int63_max(i64(len(T)), r)
i := int63_max(i64(len(T)))
i += i64(min(T))
return T(i)
}