From 97103f680a8f5d3ce45e2a53ef204c6a502b59f7 Mon Sep 17 00:00:00 2001 From: Subhomoy Haldar Date: Tue, 26 Jan 2021 19:25:09 +0530 Subject: [PATCH] rand: separate rand.util and rand.seed submodules (#8353) --- vlib/io/util/util.v | 18 ++++-------- vlib/rand/mt19937/mt19937.v | 32 ++++++++++----------- vlib/rand/mt19937/mt19937_test.v | 6 ++-- vlib/rand/musl/musl_rng.v | 3 +- vlib/rand/musl/musl_rng_test.v | 6 ++-- vlib/rand/pcg32/pcg32.v | 5 ++-- vlib/rand/pcg32/pcg32_test.v | 4 +-- vlib/rand/rand.v | 18 ++++++------ vlib/rand/random_identifiers_test.v | 2 +- vlib/rand/seed/seed.v | 40 ++++++++++++++++++++++++++ vlib/rand/splitmix64/splitmix64.v | 3 +- vlib/rand/splitmix64/splitmix64_test.v | 6 ++-- vlib/rand/sys/system_rng.c.v | 17 +++++------ vlib/rand/util/util.v | 36 ----------------------- vlib/rand/wyrand/wyrand.v | 9 +++--- vlib/rand/wyrand/wyrand_test.v | 6 ++-- 16 files changed, 107 insertions(+), 104 deletions(-) create mode 100644 vlib/rand/seed/seed.v diff --git a/vlib/io/util/util.v b/vlib/io/util/util.v index 44eaedf26b..ff4922aed0 100644 --- a/vlib/io/util/util.v +++ b/vlib/io/util/util.v @@ -3,7 +3,7 @@ module util import os import rand import rand.wyrand -import rand.util as rutil +import rand.seed as rseed const ( retries = 10000 @@ -26,9 +26,7 @@ pub fn temp_file(tfo TempFileOptions) ?(os.File, string) { } d = d.trim_right(os.path_separator) mut rng := rand.new_default(rand.PRNGConfigStruct{}) - prefix, suffix := prefix_and_suffix(tfo.pattern) or { - return error(@FN + ' ' + err) - } + prefix, suffix := prefix_and_suffix(tfo.pattern) or { return error(@FN + ' ' + err) } for retry := 0; retry < retries; retry++ { path := os.join_path(d, prefix + random_number(mut rng) + suffix) mut mode := 'rw+' @@ -36,7 +34,7 @@ pub fn temp_file(tfo TempFileOptions) ?(os.File, string) { mode = 'w+' } mut file := os.open_file(path, mode, 0o600) or { - rng.seed(rutil.time_seed_array(2)) + rng.seed(rseed.time_seed_array(2)) continue } if os.exists(path) && os.is_file(path) { @@ -64,13 +62,11 @@ pub fn temp_dir(tdo TempFileOptions) ?string { } d = d.trim_right(os.path_separator) mut rng := rand.new_default(rand.PRNGConfigStruct{}) - prefix, suffix := prefix_and_suffix(tdo.pattern) or { - return error(@FN + ' ' + err) - } + prefix, suffix := prefix_and_suffix(tdo.pattern) or { return error(@FN + ' ' + err) } for retry := 0; retry < retries; retry++ { path := os.join_path(d, prefix + random_number(mut rng) + suffix) os.mkdir_all(path) or { - rng.seed(rutil.time_seed_array(2)) + rng.seed(rseed.time_seed_array(2)) continue } if os.is_dir(path) && os.exists(path) { @@ -96,9 +92,7 @@ fn prefix_and_suffix(pattern string) ?(string, string) { if pat.contains(os.path_separator) { return error('pattern cannot contain path separators ($os.path_separator).') } - pos := pat.last_index('*') or { - -1 - } + pos := pat.last_index('*') or { -1 } mut prefix := '' mut suffix := '' if pos != -1 { diff --git a/vlib/rand/mt19937/mt19937.v b/vlib/rand/mt19937/mt19937.v index e337627d52..f509642db4 100644 --- a/vlib/rand/mt19937/mt19937.v +++ b/vlib/rand/mt19937/mt19937.v @@ -4,7 +4,7 @@ module mt19937 import math.bits -import rand.util +import rand.seed /* C++ functions for MT19937, with initialization improved 2002/2/10. @@ -59,8 +59,8 @@ const ( // MT19937RNG is generator that uses the Mersenne Twister algorithm with period 2^19937. pub struct MT19937RNG { mut: - state []u64 = calculate_state(util.time_seed_array(2), mut []u64{len: nn}) - mti int = nn + state []u64 = calculate_state(seed.time_seed_array(2), mut []u64{len: mt19937.nn}) + mti int = mt19937.nn next_rnd u32 has_next bool } @@ -70,7 +70,7 @@ fn calculate_state(seed_data []u32, mut state []u64) []u64 { lo := u64(seed_data[0]) hi := u64(seed_data[1]) state[0] = u64((hi << 32) | lo) - for j := 1; j < nn; j++ { + for j := 1; j < mt19937.nn; j++ { state[j] = u64(6364136223846793005) * (state[j - 1] ^ (state[j - 1] >> 62)) + u64(j) } return *state @@ -84,7 +84,7 @@ pub fn (mut rng MT19937RNG) seed(seed_data []u32) { exit(1) } rng.state = calculate_state(seed_data, mut rng.state) - rng.mti = nn + rng.mti = mt19937.nn rng.next_rnd = 0 rng.has_next = false } @@ -105,21 +105,21 @@ pub fn (mut rng MT19937RNG) u32() u32 { // u64 returns a pseudorandom 64bit int in range `[0, 2⁶⁴)`. [inline] pub fn (mut rng MT19937RNG) u64() u64 { - mag01 := [u64(0), u64(matrix_a)] + mag01 := [u64(0), u64(mt19937.matrix_a)] mut x := u64(0) mut i := int(0) - if rng.mti >= nn { - for i = 0; i < nn - mm; i++ { - x = (rng.state[i] & um) | (rng.state[i + 1] & lm) - rng.state[i] = rng.state[i + mm] ^ (x >> 1) ^ mag01[int(x & 1)] + if rng.mti >= mt19937.nn { + for i = 0; i < mt19937.nn - mt19937.mm; i++ { + x = (rng.state[i] & mt19937.um) | (rng.state[i + 1] & mt19937.lm) + rng.state[i] = rng.state[i + mt19937.mm] ^ (x >> 1) ^ mag01[int(x & 1)] } - for i < nn - 1 { - x = (rng.state[i] & um) | (rng.state[i + 1] & lm) - rng.state[i] = rng.state[i + (mm - nn)] ^ (x >> 1) ^ mag01[int(x & 1)] + for i < mt19937.nn - 1 { + x = (rng.state[i] & mt19937.um) | (rng.state[i + 1] & mt19937.lm) + rng.state[i] = rng.state[i + (mt19937.mm - mt19937.nn)] ^ (x >> 1) ^ mag01[int(x & 1)] i++ } - x = (rng.state[nn - 1] & um) | (rng.state[0] & lm) - rng.state[nn - 1] = rng.state[mm - 1] ^ (x >> 1) ^ mag01[int(x & 1)] + x = (rng.state[mt19937.nn - 1] & mt19937.um) | (rng.state[0] & mt19937.lm) + rng.state[mt19937.nn - 1] = rng.state[mt19937.mm - 1] ^ (x >> 1) ^ mag01[int(x & 1)] rng.mti = 0 } x = rng.state[rng.mti] @@ -279,7 +279,7 @@ pub fn (mut rng MT19937RNG) f32() f32 { // f64 returns 64bit real (`f64`) in range `[0, 1)`. [inline] pub fn (mut rng MT19937RNG) f64() f64 { - return f64(rng.u64() >> 11) * inv_f64_limit + return f64(rng.u64() >> 11) * mt19937.inv_f64_limit } // f32n returns a 32bit real (`f32`) in range [0, max)`. diff --git a/vlib/rand/mt19937/mt19937_test.v b/vlib/rand/mt19937/mt19937_test.v index cc3e6a6e6d..02e9daf83c 100644 --- a/vlib/rand/mt19937/mt19937_test.v +++ b/vlib/rand/mt19937/mt19937_test.v @@ -1,6 +1,6 @@ import math import rand.mt19937 -import rand.util +import rand.seed const ( range_limit = 40 @@ -26,7 +26,7 @@ fn mt19937_basic_test() { fn gen_randoms(seed_data []u32, bound int) []u64 { bound_u64 := u64(bound) - mut randoms := []u64{len:(20)} + mut randoms := []u64{len: (20)} mut rnd := mt19937.MT19937RNG{} rnd.seed(seed_data) for i in 0 .. 20 { @@ -36,7 +36,7 @@ fn gen_randoms(seed_data []u32, bound int) []u64 { } fn test_mt19937_reproducibility() { - seed_data := util.time_seed_array(2) + seed_data := seed.time_seed_array(2) randoms1 := gen_randoms(seed_data, 1000) randoms2 := gen_randoms(seed_data, 1000) assert randoms1.len == randoms2.len diff --git a/vlib/rand/musl/musl_rng.v b/vlib/rand/musl/musl_rng.v index 55d07a3c37..998428e896 100644 --- a/vlib/rand/musl/musl_rng.v +++ b/vlib/rand/musl/musl_rng.v @@ -4,12 +4,13 @@ module musl import math.bits +import rand.seed import rand.util // MuslRNG ported from https://git.musl-libc.org/cgit/musl/tree/src/prng/rand_r.c pub struct MuslRNG { mut: - state u32 = util.time_seed_32() + state u32 = seed.time_seed_32() } // seed sets the current random state based on `seed_data`. diff --git a/vlib/rand/musl/musl_rng_test.v b/vlib/rand/musl/musl_rng_test.v index c0f6ea0a7d..e6406f90bd 100644 --- a/vlib/rand/musl/musl_rng_test.v +++ b/vlib/rand/musl/musl_rng_test.v @@ -1,6 +1,6 @@ import math import rand.musl -import rand.util +import rand.seed const ( range_limit = 40 @@ -16,7 +16,7 @@ const ( fn gen_randoms(seed_data []u32, bound int) []u64 { bound_u64 := u64(bound) - mut randoms := []u64{len:(20)} + mut randoms := []u64{len: (20)} mut rnd := musl.MuslRNG{} rnd.seed(seed_data) for i in 0 .. 20 { @@ -26,7 +26,7 @@ fn gen_randoms(seed_data []u32, bound int) []u64 { } fn test_musl_reproducibility() { - seed_data := util.time_seed_array(1) + seed_data := seed.time_seed_array(1) randoms1 := gen_randoms(seed_data, 1000) randoms2 := gen_randoms(seed_data, 1000) assert randoms1.len == randoms2.len diff --git a/vlib/rand/pcg32/pcg32.v b/vlib/rand/pcg32/pcg32.v index c28c100303..bb7818f501 100644 --- a/vlib/rand/pcg32/pcg32.v +++ b/vlib/rand/pcg32/pcg32.v @@ -3,6 +3,7 @@ // that can be found in the LICENSE file. module pcg32 +import rand.seed import rand.util // PCG32RNG ported from http://www.pcg-random.org/download.html, @@ -10,8 +11,8 @@ import rand.util // https://github.com/imneme/pcg-c-basic/blob/master/pcg_basic.h pub struct PCG32RNG { mut: - state u64 = u64(0x853c49e6748fea9b) ^ util.time_seed_64() - inc u64 = u64(0xda3e39cb94b95bdb) ^ util.time_seed_64() + state u64 = u64(0x853c49e6748fea9b) ^ seed.time_seed_64() + inc u64 = u64(0xda3e39cb94b95bdb) ^ seed.time_seed_64() } // seed seeds the PCG32RNG with 4 `u32` values. diff --git a/vlib/rand/pcg32/pcg32_test.v b/vlib/rand/pcg32/pcg32_test.v index 16d41ab8bd..c0a1334d49 100644 --- a/vlib/rand/pcg32/pcg32_test.v +++ b/vlib/rand/pcg32/pcg32_test.v @@ -1,6 +1,6 @@ import math import rand.pcg32 -import rand.util +import rand.seed const ( range_limit = 40 @@ -25,7 +25,7 @@ fn gen_randoms(seed_data []u32, bound int) []u32 { } fn test_pcg32_reproducibility() { - seed_data := util.time_seed_array(4) + seed_data := seed.time_seed_array(4) randoms1 := gen_randoms(seed_data, 1000) randoms2 := gen_randoms(seed_data, 1000) assert randoms1.len == randoms2.len diff --git a/vlib/rand/rand.v b/vlib/rand/rand.v index 774eb84377..d1780c4172 100644 --- a/vlib/rand/rand.v +++ b/vlib/rand/rand.v @@ -3,13 +3,13 @@ // that can be found in the LICENSE file. module rand -import rand.util +import rand.seed import rand.wyrand import time // PRNGConfigStruct is a configuration struct for creating a new instance of the default RNG. pub struct PRNGConfigStruct { - seed []u32 = util.time_seed_array(2) + seed []u32 = seed.time_seed_array(2) } __global ( default_rng &wyrand.WyRandRNG ) @@ -141,10 +141,10 @@ pub fn string(len int) string { mut buf := malloc(len) for i in 0 .. len { unsafe { - buf[i] = chars[intn(chars.len)] + buf[i] = rand.chars[intn(rand.chars.len)] } } - return unsafe {buf.vstring_with_len(len)} + return unsafe { buf.vstring_with_len(len) } } // uuid_v4 generates a random (v4) UUID @@ -184,7 +184,7 @@ pub fn uuid_v4() string { buf[14] = `4` buf[buflen] = 0 } - return unsafe {buf.vstring_with_len(buflen)} + return unsafe { buf.vstring_with_len(buflen) } } const ( @@ -209,7 +209,7 @@ pub fn ulid_at_millisecond(unix_time_milli u64) string { mut i := 9 for i >= 0 { unsafe { - buf[i] = ulid_encoding[t & 0x1F] + buf[i] = rand.ulid_encoding[t & 0x1F] } t = t >> 5 i-- @@ -219,7 +219,7 @@ pub fn ulid_at_millisecond(unix_time_milli u64) string { i = 10 for i < 19 { unsafe { - buf[i] = ulid_encoding[x & 0x1F] + buf[i] = rand.ulid_encoding[x & 0x1F] } x = x >> 5 i++ @@ -228,7 +228,7 @@ pub fn ulid_at_millisecond(unix_time_milli u64) string { x = default_rng.u64() for i < 26 { unsafe { - buf[i] = ulid_encoding[x & 0x1F] + buf[i] = rand.ulid_encoding[x & 0x1F] } x = x >> 5 i++ @@ -236,5 +236,5 @@ pub fn ulid_at_millisecond(unix_time_milli u64) string { unsafe { buf[26] = 0 } - return unsafe {buf.vstring_with_len(buflen)} + return unsafe { buf.vstring_with_len(buflen) } } diff --git a/vlib/rand/random_identifiers_test.v b/vlib/rand/random_identifiers_test.v index 127c4e3906..290a133398 100644 --- a/vlib/rand/random_identifiers_test.v +++ b/vlib/rand/random_identifiers_test.v @@ -40,7 +40,7 @@ fn test_ulids_generated_in_the_same_millisecond_have_the_same_prefix() { t := time.utc().unix_time_milli() mut ulid1 := '' mut ulid2 := '' - mut ulid3 := '' + mut ulid3 := '' ulid1 = rand.ulid_at_millisecond(t) ulid2 = rand.ulid_at_millisecond(t) ulid3 = rand.ulid_at_millisecond(t) diff --git a/vlib/rand/seed/seed.v b/vlib/rand/seed/seed.v new file mode 100644 index 0000000000..5e9fb76ad2 --- /dev/null +++ b/vlib/rand/seed/seed.v @@ -0,0 +1,40 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module seed + +import time + +// nr_next returns a next value based on the previous value `prev`. +[inline] +fn nr_next(prev u32) u32 { + return prev * 1664525 + 1013904223 +} + +// time_seed_array returns the required number of u32s generated from system time. +[inline] +pub fn time_seed_array(count int) []u32 { + ctime := time.now() + mut seed := u32(ctime.unix_time() ^ ctime.microsecond) + mut seed_data := []u32{cap: count} + for _ in 0 .. count { + seed = nr_next(seed) + seed_data << nr_next(seed) + } + return seed_data +} + +// time_seed_32 returns a 32-bit seed generated from system time. +[inline] +pub fn time_seed_32() u32 { + return time_seed_array(1)[0] +} + +// time_seed_64 returns a 64-bit seed generated from system time. +[inline] +pub fn time_seed_64() u64 { + seed_data := time_seed_array(2) + lower := u64(seed_data[0]) + upper := u64(seed_data[1]) + return lower | (upper << 32) +} diff --git a/vlib/rand/splitmix64/splitmix64.v b/vlib/rand/splitmix64/splitmix64.v index 01cd076f7f..d1a659ac7f 100644 --- a/vlib/rand/splitmix64/splitmix64.v +++ b/vlib/rand/splitmix64/splitmix64.v @@ -3,12 +3,13 @@ // that can be found in the LICENSE file. module splitmix64 +import rand.seed import rand.util // SplitMix64RNG ported from http://xoshiro.di.unimi.it/splitmix64.c pub struct SplitMix64RNG { mut: - state u64 = util.time_seed_64() + state u64 = seed.time_seed_64() has_extra bool extra u32 } diff --git a/vlib/rand/splitmix64/splitmix64_test.v b/vlib/rand/splitmix64/splitmix64_test.v index 3159e02d06..669da3ccd3 100644 --- a/vlib/rand/splitmix64/splitmix64_test.v +++ b/vlib/rand/splitmix64/splitmix64_test.v @@ -1,6 +1,6 @@ import math import rand.splitmix64 -import rand.util +import rand.seed const ( range_limit = 40 @@ -16,7 +16,7 @@ const ( fn gen_randoms(seed_data []u32, bound int) []u64 { bound_u64 := u64(bound) - mut randoms := []u64{len:(20)} + mut randoms := []u64{len: (20)} mut rnd := splitmix64.SplitMix64RNG{} rnd.seed(seed_data) for i in 0 .. 20 { @@ -26,7 +26,7 @@ fn gen_randoms(seed_data []u32, bound int) []u64 { } fn test_splitmix64_reproducibility() { - seed_data := util.time_seed_array(2) + seed_data := seed.time_seed_array(2) randoms1 := gen_randoms(seed_data, 1000) randoms2 := gen_randoms(seed_data, 1000) assert randoms1.len == randoms2.len diff --git a/vlib/rand/sys/system_rng.c.v b/vlib/rand/sys/system_rng.c.v index f8f4507c30..1c426b6764 100644 --- a/vlib/rand/sys/system_rng.c.v +++ b/vlib/rand/sys/system_rng.c.v @@ -4,6 +4,7 @@ module sys import math.bits +import rand.seed import rand.util // Implementation note: @@ -23,8 +24,8 @@ const ( ) fn calculate_iterations_for(bits int) int { - base := bits / rand_bitsize - extra := if bits % rand_bitsize == 0 { 0 } else { 1 } + base := bits / sys.rand_bitsize + extra := if bits % sys.rand_bitsize == 0 { 0 } else { 1 } return base + extra } @@ -36,7 +37,7 @@ fn C.rand() int // SysRNG is the PRNG provided by default in the libc implementiation that V uses. pub struct SysRNG { mut: - seed u32 = util.time_seed_32() + seed u32 = seed.time_seed_32() } // r.seed() sets the seed of the accepting SysRNG to the given data. @@ -46,7 +47,7 @@ pub fn (mut r SysRNG) seed(seed_data []u32) { exit(1) } r.seed = seed_data[0] - unsafe {C.srand(int(r.seed))} + unsafe { C.srand(int(r.seed)) } } // r.default_rand() exposes the default behavior of the system's RNG @@ -63,8 +64,8 @@ pub fn (r SysRNG) default_rand() int { [inline] pub fn (r SysRNG) u32() u32 { mut result := u32(C.rand()) - for i in 1 .. u32_iter_count { - result = result ^ (u32(C.rand()) << (rand_bitsize * i)) + for i in 1 .. sys.u32_iter_count { + result = result ^ (u32(C.rand()) << (sys.rand_bitsize * i)) } return result } @@ -73,8 +74,8 @@ pub fn (r SysRNG) u32() u32 { [inline] pub fn (r SysRNG) u64() u64 { mut result := u64(C.rand()) - for i in 1 .. u64_iter_count { - result = result ^ (u64(C.rand()) << (rand_bitsize * i)) + for i in 1 .. sys.u64_iter_count { + result = result ^ (u64(C.rand()) << (sys.rand_bitsize * i)) } return result } diff --git a/vlib/rand/util/util.v b/vlib/rand/util/util.v index 9f24c4dca9..b3282fcd44 100644 --- a/vlib/rand/util/util.v +++ b/vlib/rand/util/util.v @@ -3,8 +3,6 @@ // that can be found in the LICENSE file. module util -import time - // Commonly used constants across RNGs - some taken from "Numerical Recipes". pub const ( lower_mask = u64(0x00000000FFFFFFFF) @@ -15,37 +13,3 @@ pub const ( u31_mask = u32(0x7FFFFFFF) u63_mask = u64(0x7FFFFFFFFFFFFFFF) ) - -// nr_next returns a next value based on the previous value `prev`. -[inline] -fn nr_next(prev u32) u32 { - return prev * 1664525 + 1013904223 -} - -// time_seed_array returns the required number of u32s generated from system time. -[inline] -pub fn time_seed_array(count int) []u32 { - ctime := time.now() - mut seed := u32(ctime.unix_time() ^ ctime.microsecond) - mut seed_data := []u32{cap: count} - for _ in 0 .. count { - seed = nr_next(seed) - seed_data << nr_next(seed) - } - return seed_data -} - -// time_seed_32 returns a 32-bit seed generated from system time. -[inline] -pub fn time_seed_32() u32 { - return time_seed_array(1)[0] -} - -// time_seed_64 returns a 64-bit seed generated from system time. -[inline] -pub fn time_seed_64() u64 { - seed_data := time_seed_array(2) - lower := u64(seed_data[0]) - upper := u64(seed_data[1]) - return lower | (upper << 32) -} diff --git a/vlib/rand/wyrand/wyrand.v b/vlib/rand/wyrand/wyrand.v index 253983632f..f052a1e15d 100644 --- a/vlib/rand/wyrand/wyrand.v +++ b/vlib/rand/wyrand/wyrand.v @@ -4,8 +4,9 @@ module wyrand import math.bits +import rand.seed import rand.util -import hash as wyhash +import hash // Redefinition of some constants that we will need for pseudorandom number generation. const ( @@ -16,7 +17,7 @@ const ( // WyRandRNG is a RNG based on the WyHash hashing algorithm. pub struct WyRandRNG { mut: - state u64 = util.time_seed_64() + state u64 = seed.time_seed_64() has_extra bool extra u32 } @@ -51,9 +52,9 @@ pub fn (mut rng WyRandRNG) u32() u32 { pub fn (mut rng WyRandRNG) u64() u64 { unsafe { mut seed1 := rng.state - seed1 += wyp0 + seed1 += wyrand.wyp0 rng.state = seed1 - return wyhash.wymum(seed1 ^ wyp1, seed1) + return hash.wymum(seed1 ^ wyrand.wyp1, seed1) } return 0 } diff --git a/vlib/rand/wyrand/wyrand_test.v b/vlib/rand/wyrand/wyrand_test.v index 86ad8b5ad1..4cdfdb654b 100644 --- a/vlib/rand/wyrand/wyrand_test.v +++ b/vlib/rand/wyrand/wyrand_test.v @@ -1,5 +1,5 @@ import math -import rand.util +import rand.seed import rand.wyrand const ( @@ -16,7 +16,7 @@ const ( fn gen_randoms(seed_data []u32, bound int) []u64 { bound_u64 := u64(bound) - mut randoms := []u64{len:(20)} + mut randoms := []u64{len: (20)} mut rnd := wyrand.WyRandRNG{} rnd.seed(seed_data) for i in 0 .. 20 { @@ -26,7 +26,7 @@ fn gen_randoms(seed_data []u32, bound int) []u64 { } fn test_wyrand_reproducibility() { - seed_data := util.time_seed_array(2) + seed_data := seed.time_seed_array(2) randoms1 := gen_randoms(seed_data, 1000) randoms2 := gen_randoms(seed_data, 1000) assert randoms1.len == randoms2.len