authorgravatar for marc@tiehu.isMarc Tiehuis <marc@tiehu.is> 2018-04-14 21:08:49+12:00
committergravatar for marc@tiehu.isMarc Tiehuis <marc@tiehu.is> 2018-04-16 20:06:50+12:00
logc7cb5c31e5b0cb9a88365c1264bfddf3c50ed107
tree05a8829e99dd5074c750878f55a7dc5f6e83235f
parentcaefaf781e22a7b053426621719a6f1d0f69d7cb

Add exp/norm distributed random float generation


3 files changed, 166 insertions(+), 5 deletions(-)

CMakeLists.txt+1
......@@ -515,6 +515,7 @@ set(ZIG_STD_FILES
515515 "os/windows/util.zig"
516516 "os/zen.zig"
517517 "rand/index.zig"
518 "rand/ziggurat.zig"
518519 "sort.zig"
519520 "special/bootstrap.zig"
520521 "special/bootstrap_lib.zig"
std/rand/index.zig+19-5
......@@ -19,6 +19,7 @@ const builtin = @import("builtin");
1919const assert = std.debug.assert;
2020const mem = std.mem;
2121const math = std.math;
22const ziggurat = @import("ziggurat.zig");
2223
2324// When you need fast unbiased random numbers
2425pub const DefaultPrng = Xoroshiro128;
......@@ -109,15 +110,28 @@ pub const Random = struct {
109110 }
110111 }
111112
112 /// Return a floating point value normally distributed in the range [0, 1].
113 /// Return a floating point value normally distributed with mean = 0, stddev = 1.
114 ///
115 /// To use different parameters, use: floatNorm(...) * desiredStddev + desiredMean.
113116 pub fn floatNorm(r: &Random, comptime T: type) T {
114 // TODO(tiehuis): See https://www.doornik.com/research/ziggurat.pdf
115 @compileError("floatNorm is unimplemented");
117 const value = ziggurat.next_f64(r, ziggurat.NormDist);
118 switch (T) {
119 f32 => return f32(value),
120 f64 => return value,
121 else => @compileError("unknown floating point type"),
122 }
116123 }
117124
118 /// Return a exponentially distributed float between (0, @maxValue(f64))
125 /// Return an exponentially distributed float with a rate parameter of 1.
126 ///
127 /// To use a different rate parameter, use: floatExp(...) / desiredRate.
119128 pub fn floatExp(r: &Random, comptime T: type) T {
120 @compileError("floatExp is unimplemented");
129 const value = ziggurat.next_f64(r, ziggurat.ExpDist);
130 switch (T) {
131 f32 => return f32(value),
132 f64 => return value,
133 else => @compileError("unknown floating point type"),
134 }
121135 }
122136
123137 /// Shuffle a slice into a random order.
std/rand/ziggurat.zig created+146
......@@ -0,0 +1,146 @@
1// Implements ZIGNOR [1].
2//
3// [1]: Jurgen A. Doornik (2005). [*An Improved Ziggurat Method to Generate Normal Random Samples*]
4// (https://www.doornik.com/research/ziggurat.pdf). Nuffield College, Oxford.
5//
6// rust/rand used as a reference;
7//
8// NOTE: This seems interesting but reference code is a bit hard to grok:
9// https://sbarral.github.io/etf.
10
11const std = @import("../index.zig");
12const math = std.math;
13const Random = std.rand.Random;
14
15pub fn next_f64(random: &Random, comptime tables: &const ZigTable) f64 {
16 while (true) {
17 // We manually construct a float from parts as we can avoid an extra random lookup here by
18 // using the unused exponent for the lookup table entry.
19 const bits = random.scalar(u64);
20 const i = usize(bits & 0xff);
21
22 const u = blk: {
23 if (tables.is_symmetric) {
24 // Generate a value in the range [2, 4) and scale into [-1, 1)
25 const repr = ((0x3ff + 1) << 52) | (bits >> 12);
26 break :blk @bitCast(f64, repr) - 3.0;
27 } else {
28 // Generate a value in the range [1, 2) and scale into (0, 1)
29 const repr = (0x3ff << 52) | (bits >> 12);
30 break :blk @bitCast(f64, repr) - (1.0 - math.f64_epsilon / 2.0);
31 }
32 };
33
34 const x = u * tables.x[i];
35 const test_x = if (tables.is_symmetric) math.fabs(x) else x;
36
37 // equivalent to |u| < tables.x[i+1] / tables.x[i] (or u < tables.x[i+1] / tables.x[i])
38 if (test_x < tables.x[i + 1]) {
39 return x;
40 }
41
42 if (i == 0) {
43 return tables.zero_case(random, u);
44 }
45
46 // equivalent to f1 + DRanU() * (f0 - f1) < 1
47 if (tables.f[i + 1] + (tables.f[i] - tables.f[i + 1]) * random.float(f64) < tables.pdf(x)) {
48 return x;
49 }
50 }
51}
52
53pub const ZigTable = struct {
54 r: f64,
55 x: [257]f64,
56 f: [257]f64,
57
58 // probability density function used as a fallback
59 pdf: fn(f64) f64,
60 // whether the distribution is symmetric
61 is_symmetric: bool,
62 // fallback calculation in the case we are in the 0 block
63 zero_case: fn(&Random, f64) f64,
64};
65
66// zigNorInit
67fn ZigTableGen(comptime is_symmetric: bool, comptime r: f64, comptime v: f64, comptime f: fn(f64) f64,
68 comptime f_inv: fn(f64) f64, comptime zero_case: fn(&Random, f64) f64) ZigTable {
69 var tables: ZigTable = undefined;
70
71 tables.is_symmetric = is_symmetric;
72 tables.r = r;
73 tables.pdf = f;
74 tables.zero_case = zero_case;
75
76 tables.x[0] = v / f(r);
77 tables.x[1] = r;
78
79 for (tables.x[2..256]) |*entry, i| {
80 const last = tables.x[2 + i - 1];
81 *entry = f_inv(v / last + f(last));
82 }
83 tables.x[256] = 0;
84
85 for (tables.f[0..]) |*entry, i| {
86 *entry = f(tables.x[i]);
87 }
88
89 return tables;
90}
91
92// N(0, 1)
93pub const NormDist = blk: {
94 @setEvalBranchQuota(30000);
95 break :blk ZigTableGen(true, norm_r, norm_v, norm_f, norm_f_inv, norm_zero_case);
96};
97
98const norm_r = 3.6541528853610088;
99const norm_v = 0.00492867323399;
100
101fn norm_f(x: f64) f64 { return math.exp(-x * x / 2.0); }
102fn norm_f_inv(y: f64) f64 { return math.sqrt(-2.0 * math.ln(y)); }
103fn norm_zero_case(random: &Random, u: f64) f64 {
104 var x: f64 = 1;
105 var y: f64 = 0;
106
107 while (-2.0 * y < x * x) {
108 x = math.ln(random.float(f64)) / norm_r;
109 y = math.ln(random.float(f64));
110 }
111
112 if (u < 0) {
113 return x - norm_r;
114 } else {
115 return norm_r - x;
116 }
117}
118
119test "ziggurant normal dist sanity" {
120 var prng = std.rand.DefaultPrng.init(0);
121 var i: usize = 0;
122 while (i < 1000) : (i += 1) {
123 _ = prng.random.floatNorm(f64);
124 }
125}
126
127// Exp(1)
128pub const ExpDist = blk: {
129 @setEvalBranchQuota(30000);
130 break :blk ZigTableGen(false, exp_r, exp_v, exp_f, exp_f_inv, exp_zero_case);
131};
132
133const exp_r = 7.69711747013104972;
134const exp_v = 0.0039496598225815571993;
135
136fn exp_f(x: f64) f64 { return math.exp(-x); }
137fn exp_f_inv(y: f64) f64 { return -math.ln(y); }
138fn exp_zero_case(random: &Random, _: f64) f64 { return exp_r - math.ln(random.float(f64)); }
139
140test "ziggurant exp dist sanity" {
141 var prng = std.rand.DefaultPrng.init(0);
142 var i: usize = 0;
143 while (i < 1000) : (i += 1) {
144 _ = prng.random.floatExp(f64);
145 }
146}