Skip to content

Commit 4098612

Browse files
authored
rand: add full precision f32 and f64 random functions; fix f32/f64 multipliers (#16875)
1 parent 550cae9 commit 4098612

File tree

6 files changed

+337
-15
lines changed

6 files changed

+337
-15
lines changed

vlib/math/bits/unsafe_bits.js.v

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
module bits
2+
3+
// f32_bits returns the IEEE 754 binary representation of f,
4+
// with the sign bit of f and the result in the same bit position.
5+
// f32_bits(f32_from_bits(x)) == x.
6+
pub fn f32_bits(f f32) u32 {
7+
p := u32(0)
8+
#let buffer = new ArrayBuffer(4)
9+
#let floatArr = new Float32Array(buffer)
10+
#floatArr[0] = f.val
11+
#let uintArr = new Uint32Array(buffer)
12+
#p.val = uintArr[0]
13+
14+
return p
15+
}
16+
17+
// f32_from_bits returns the floating-point number corresponding
18+
// to the IEEE 754 binary representation b, with the sign bit of b
19+
// and the result in the same bit position.
20+
// f32_from_bits(f32_bits(x)) == x.
21+
pub fn f32_from_bits(b u32) f32 {
22+
p := f32(0.0)
23+
#let buffer = new ArrayBuffer(4)
24+
#let floatArr = new Float32Array(buffer)
25+
#let uintArr = new Uint32Array(buffer)
26+
#uintArr[0] = Number(b.val)
27+
#p.val = floatArr[0]
28+
29+
return p
30+
}
31+
32+
// f64_bits returns the IEEE 754 binary representation of f,
33+
// with the sign bit of f and the result in the same bit position,
34+
// and f64_bits(f64_from_bits(x)) == x.
35+
pub fn f64_bits(f f64) u64 {
36+
p := u64(0)
37+
#let buffer = new ArrayBuffer(8)
38+
#let floatArr = new Float64Array(buffer)
39+
#floatArr[0] = f.val
40+
#let uintArr = new BigUint64Array(buffer)
41+
#p.val = uintArr[0]
42+
43+
return p
44+
}
45+
46+
// f64_from_bits returns the floating-point number corresponding
47+
// to the IEEE 754 binary representation b, with the sign bit of b
48+
// and the result in the same bit position.
49+
// f64_from_bits(f64_bits(x)) == x.
50+
pub fn f64_from_bits(b u64) f64 {
51+
p := 0.0
52+
#let buffer = new ArrayBuffer(8)
53+
#let floatArr = new Float64Array(buffer)
54+
#let uintArr = new BigUint64Array(buffer)
55+
#uintArr[0] = b.val
56+
#p.val = floatArr[0]
57+
58+
return p
59+
}

vlib/math/bits/unsafe_bits.v

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Copyright (c) 2019-2022 Alexander Medvednikov. All rights reserved.
2+
// Use of this source code is governed by an MIT license
3+
// that can be found in the LICENSE file.
4+
module bits
5+
6+
// f32_bits returns the IEEE 754 binary representation of f,
7+
// with the sign bit of f and the result in the same bit position.
8+
// f32_bits(f32_from_bits(x)) == x.
9+
[inline]
10+
pub fn f32_bits(f f32) u32 {
11+
p := *unsafe { &u32(&f) }
12+
return p
13+
}
14+
15+
// f32_from_bits returns the floating-point number corresponding
16+
// to the IEEE 754 binary representation b, with the sign bit of b
17+
// and the result in the same bit position.
18+
// f32_from_bits(f32_bits(x)) == x.
19+
[inline]
20+
pub fn f32_from_bits(b u32) f32 {
21+
p := *unsafe { &f32(&b) }
22+
return p
23+
}
24+
25+
// f64_bits returns the IEEE 754 binary representation of f,
26+
// with the sign bit of f and the result in the same bit position,
27+
// and f64_bits(f64_from_bits(x)) == x.
28+
[inline]
29+
pub fn f64_bits(f f64) u64 {
30+
p := *unsafe { &u64(&f) }
31+
return p
32+
}
33+
34+
// f64_from_bits returns the floating-point number corresponding
35+
// to the IEEE 754 binary representation b, with the sign bit of b
36+
// and the result in the same bit position.
37+
// f64_from_bits(f64_bits(x)) == x.
38+
[inline]
39+
pub fn f64_from_bits(b u64) f64 {
40+
p := *unsafe { &f64(&b) }
41+
return p
42+
}

vlib/rand/README.md

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import rand
1111
...
1212
1313
// Optionally seed the default generator
14-
rand.seed([u32(3110), 50714])
14+
rand.seed([u32(3223878742), 1732001562])
1515
1616
...
1717
@@ -61,21 +61,40 @@ Otherwise, there is feature parity between the generator functions and the top-l
6161
A PRNG is a Pseudo Random Number Generator.
6262
Computers cannot generate truly random numbers without an external source of noise or entropy.
6363
We can use algorithms to generate sequences of seemingly random numbers,
64-
but their outputs will always be deterministic.
65-
This is often useful for simulations that need the same starting seed.
64+
but their outputs will always be deterministic, according to the seed values.
65+
66+
This is often useful for simulations that need the same starting seeds.
67+
You may be debugging a program and want to restart it with the same
68+
seeds, or you want to verify a working program is still
69+
operating identically after compiler or operating system updates.
6670

6771
If you need truly random numbers that are going to be used for cryptography,
6872
use the `crypto.rand` module.
6973

7074
# Seeding Functions
7175

72-
All the generators are time-seeded.
76+
All the generators are initialized with time-based seeds.
7377
The helper functions publicly available in `rand.seed` module are:
7478

7579
1. `time_seed_array()` - returns a `[]u32` that can be directly plugged into the `seed()` functions.
7680
2. `time_seed_32()` and `time_seed_64()` - 32-bit and 64-bit values respectively
7781
that are generated from the current time.
7882

83+
When composing your own seeds, use "typical" u32 numbers, not small numbers. This
84+
is especially important for PRNGs with large state, such as `mt19937`. You can create
85+
random unsigned integers with openssl `rand` or with `v repl` as follows:
86+
87+
```
88+
$ openssl rand -hex 4
89+
e3655862
90+
$ openssl rand -hex 4
91+
97c4b1db
92+
$ v repl
93+
>>> import rand
94+
>>> [rand.u32(),rand.u32()]
95+
[2132382944, 2443871665]
96+
```
97+
7998
# Caveats
8099

81100
Note that the `sys.SysRNG` struct (in the C backend) uses `C.srand()` which sets the seed globally.

vlib/rand/constants/constants.v

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,16 @@ module constants
22

33
// Commonly used constants across RNGs - some taken from "Numerical Recipes".
44
pub const (
5-
lower_mask = u64(0x00000000FFFFFFFF)
6-
max_u32 = u32(0xFFFFFFFF)
7-
max_u64 = u64(0xFFFFFFFFFFFFFFFF)
8-
max_u32_as_f32 = f32(max_u32) + 1
9-
max_u64_as_f64 = f64(max_u64) + 1
10-
u31_mask = u32(0x7FFFFFFF)
11-
u63_mask = u64(0x7FFFFFFFFFFFFFFF)
5+
lower_mask = u64(0x00000000FFFFFFFF)
6+
max_u32 = u32(0xFFFFFFFF)
7+
max_u64 = u64(0xFFFFFFFFFFFFFFFF)
8+
u31_mask = u32(0x7FFFFFFF)
9+
u63_mask = u64(0x7FFFFFFFFFFFFFFF)
10+
// 23 bits for f32
11+
ieee754_mantissa_f32_mask = (u32(1) << 23) - 1
12+
// 52 bits for f64
13+
ieee754_mantissa_f64_mask = (u64(1) << 52) - 1
14+
// smallest mantissa with exponent 0 (un normalized)
15+
reciprocal_2_23rd = 1.0 / f64(u32(1) << 23)
16+
reciprocal_2_52nd = 1.0 / f64(u64(1) << 52)
1217
)

vlib/rand/fp_test.v

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import rand
2+
3+
struct Histo {
4+
mut:
5+
lo f64
6+
ct int
7+
}
8+
9+
const (
10+
// The sample size to be used; keep cpu time less than 5 seconds
11+
count = 9100100
12+
// Two sets of seeds
13+
seeds = [[u32(2742798260), 2159764996], [u32(2135051596), 958016781]]
14+
)
15+
16+
fn test_f32() {
17+
mut histo := []Histo{}
18+
mut candidate := f32(0.0)
19+
20+
histo << Histo{1.0e-9, 0}
21+
histo << Histo{1.0e-6, 0}
22+
histo << Histo{1.0e-4, 0}
23+
histo << Histo{1.0e-2, 0}
24+
histo << Histo{1.0e00, 0}
25+
26+
for seed in seeds {
27+
rand.seed(seed)
28+
for _ in 0 .. count {
29+
candidate = rand.f32()
30+
for mut p in histo {
31+
if candidate < p.lo {
32+
p.ct += 1
33+
}
34+
}
35+
}
36+
}
37+
println(' f32 ')
38+
println(histo)
39+
assert histo[0].ct == 1
40+
assert histo[1].ct == 16
41+
assert histo[2].ct == 1802
42+
assert histo[3].ct == 181963
43+
assert histo[4].ct == 18200200
44+
for mut p in histo {
45+
p.ct = 0
46+
}
47+
for seed in seeds {
48+
rand.seed(seed)
49+
for _ in 0 .. count {
50+
candidate = rand.f32cp()
51+
for mut p in histo {
52+
if candidate < p.lo {
53+
p.ct += 1
54+
}
55+
}
56+
}
57+
}
58+
println(' f32cp')
59+
println(histo)
60+
assert histo[0].ct == 0
61+
assert histo[1].ct == 16
62+
assert histo[2].ct == 1863
63+
assert histo[3].ct == 142044
64+
assert histo[4].ct == 18200200
65+
}
66+
67+
fn test_f64() {
68+
mut histo := []Histo{}
69+
mut candidate := f64(0.0)
70+
71+
histo << Histo{1.0e-9, 0}
72+
histo << Histo{1.0e-6, 0}
73+
histo << Histo{1.0e-4, 0}
74+
histo << Histo{1.0e-2, 0}
75+
histo << Histo{1.0e00, 0}
76+
77+
for seed in seeds {
78+
rand.seed(seed)
79+
for _ in 0 .. count {
80+
candidate = rand.f64()
81+
for mut p in histo {
82+
if candidate < p.lo {
83+
p.ct += 1
84+
}
85+
}
86+
}
87+
}
88+
println(' f64 ')
89+
println(histo)
90+
assert histo[0].ct == 0
91+
assert histo[1].ct == 23
92+
assert histo[2].ct == 1756
93+
assert histo[3].ct == 182209
94+
assert histo[4].ct == 18200200
95+
for mut p in histo {
96+
p.ct = 0
97+
}
98+
for seed in seeds {
99+
rand.seed(seed)
100+
for _ in 0 .. count {
101+
candidate = rand.f64cp()
102+
for mut p in histo {
103+
if candidate < p.lo {
104+
p.ct += 1
105+
}
106+
}
107+
}
108+
}
109+
println(' f64cp')
110+
println(histo)
111+
assert histo[0].ct == 0
112+
assert histo[1].ct == 17
113+
assert histo[2].ct == 1878
114+
assert histo[3].ct == 181754
115+
assert histo[4].ct == 18200200
116+
}

0 commit comments

Comments
 (0)