|
demos are
explained here;
a menu at top column right indexes actual topic demos.
Here we demo rand.
problem
Pseudo random number generators are useful for several things, and Wil often uses one standard implementation based on Park and Miller's Oct1988 Random number generators: good ones are hard to find, in which they identify three different algorithms with good statistical behavior when implemented in a way avoiding 32-bit overflow despite 32-bit arithmetic. One of the nice properties of this generator is the period: it visits every integer value from one to 2*31-2 exactly one time before repeating. Among other things, this can be used to generate floating point random numbers with a uniform distribution between zero and one without ever being equal to zero or one. #define yk_kMersenne ((long) 0x7FFFFFFF) /* mersenne prime */
#define yk_kMul ((long) 48271) /* prime multiplier */
#define yk_kQuo ((long) 44488) /* mersenne / mult */
#define yk_kRem ((long) 3399) /* mersenne % mult */
u32 h32rand(u32 seed) { // Park & Miller pseudo rand number «
if ( seed ) { // is seed non-zero (we won't return zero)?
long high = (long) seed / yk_kQuo;
long low = (long) seed % yk_kQuo;
long test = (yk_kMul * low) - (yk_kRem * high);
seed = (u32) (( test > 0 )? test : test + yk_kMersenne);
}
else
seed = 1; // never return zero
return seed;
}
Variant r64rand() below is nearly identical, differing only in converion to double and division by 0x7FFFFFFF which is one more than the largest value returned by h32rand(). As a result, r64rand() returns values strictly more than zero and strictly less than one, distributed uniformly between zero and one (because the numerator uses all integers between zero and 0x7FFFFFFF: each equally likely). double r64rand(h32 inSelf, h32* outSeed) { // random in (0,1) «
if ( inSelf ) { // is seed non-zero (we won't return zero)?
long high = (long) inSelf / yk_kQuo;
long low = (long) inSelf % yk_kQuo;
long test = (yk_kMul * low) - (yk_kRem * high);
inSelf = (h32) (( test > 0 )? test : test + yk_kMersenne);
}
else
inSelf = 1; // never return zero
if ( outSeed )
*outSeed = inSelf;
return ((double) inSelf) / ((double) yk_kMersenne);
}
Note neither r64rand() nor h32rand() is the fastest way to calculate these answers. Wil just decided to stick with the variant shown in Park and Miller's paper as long as performance was not the greatest concern. Sometimes Wil uses h32rand() for hashing, but most of the time Wil only uses these methods in monte carlo testing with pseudo random data sets.
everett carter
Below is the original version of code for Everett F. Carter's Box-Muller transformation. The yr64g class (top column right) is based directly on this code. This was originally posted on treedragon in March 2001, but you can find a copy on this site in Aug2007. See yr64g for an explanation. See http://www.taygeta.com/random/gaussian.html for Dr. Carter's explanation. But the main idea you should get is this: box_muller() returns values distributed in a normal distribution in the shape of a bell curve. The mean and standard deviation are based on args you pass to the function. /* ftp://www.taygeta.com/pub/c/boxmuller.c */
/* boxmuller.c
Implements the Polar form of
the Box-Muller Transformation
(c) Copyright 1994, Everett F. Carter Jr.
Permission is granted by the author to use
this software for any application provided
this copyright notice is preserved.
*/
#include <math.h>
extern float ranf(); /* ranf() is uniform in 0..1 */
float box_muller(float m, float s)
/* normal random variate generator */
/* mean m, standard deviation s */
{
float x1, x2, w, y1;
static float y2;
static int use_last = 0;
if (use_last) /* use value from previous call */
{
y1 = y2;
use_last = 0;
}
else
{
do {
x1 = 2.0 * ranf() - 1.0;
x2 = 2.0 * ranf() - 1.0;
w = x1 * x1 + x2 * x2;
} while ( w >= 1.0 );
w = sqrt( (-2.0 * log( w ) ) / w );
y1 = x1 * w;
y2 = x2 * w;
use_last = 1;
}
return( m + y1 * s );
}
Dr. Carter's conditions for use are specified above.
license
Most code on this site is available only under the BriarPig mu-babel license described fully on the rights page. But the random number generators on this page are an exception: if you remove the y prefix and attribute "BriarPig was here", you can use the BSD license as a dual license, for code on this page only. You do not have permission to reprint this page in any way. No feeds or repackaging allowed. You can link this page if you want folks to read it. (RSS feeds are forbidden, in case you have trouble working out that special case.)
jigsaw puzzles
Sample code below shows use of method ydvp::pjigsaw() listed bottom right, one column over, which constructs pseudo random partitions of byte sequences in yv format (cf «). Class ydvp appears in the iovec demo (cf «) representing an iovec array in iterator form; yd256vp is a variant with 256 iovecs built-in (cf «). The following code uses a yd256vp instance to hold a randomly constructed iovec array partitioning a string into contiguous, adjacent, disjoint pieces. yv lohi("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTYUWXYZ");
yd256vp puzzle; // ydvp with built-in 256 iovecs «
u32 seed = 0x12345;
puzzle.pjigsaw(lohi, &seed);
yout << "# first puzzle:" << yendl << puzzle.quote() << yendl;
puzzle.pjigsaw(lohi, &seed);
yout << "# second puzzle:" << yendl << puzzle.quote() << yendl;
yout << ynow;
Each time ydvp::pjigsaw() is called, it creates a one-dimensional jigsaw puzzle of vector lohi which still contains all the same bytes of lohi, and in the same order, but partitioned into a different set of iovecs whose sizes are pseudo randomly assigned with a target average size and standard deviation. (Why would anyone want to do this? An example appears in a future compare demo, where Wil tests code comparing content in one iovec array to another in a generalized scatter gather pattern finding method seeking long matches. Known but scrambled matches can be generated with ydvp::pjigsaw() then searched to see if expected matches are found.) Here's the output of code above: # first puzzle:
<ydvp v=0xbffff2ec n=6 i=0 x=256 sum=52 crc='0x5dac72ad:52'>
<v0 p=0xfaac n=7 crc='0x312a6aa6:7'>
00000: 61 62 63 64 65 66 67 ; abcdefg
</v0>
<v1 p=0xfab3 n=14 crc='0xbb8ce212:14'>
00007: 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 ; hijklmnopqrs
00013: 74 75 ; tu
</v1>
<v2 p=0xfac1 n=5 crc='0x52971922:5'>
00015: 76 77 78 79 7a ; vwxyz
</v2>
<v3 p=0xfac6 n=5 crc='0x72d31ad5:5'>
0001a: 41 42 43 44 45 ; ABCDE
</v3>
<v4 p=0xfacb n=7 crc='0x379f32b5:7'>
0001f: 46 47 48 49 4a 4b 4c ; FGHIJKL
</v4>
<v5 p=0xfad2 n=14 crc='0x535b24b4:14'>
00026: 4d 4e 4f 50 51 52 53 54 59 55 57 58 ; MNOPQRSTYUWX
00032: 59 5a ; YZ
</v5></ydvp>
# second puzzle:
<ydvp v=0xbffff2ec n=8 i=0 x=256 sum=52 crc='0x5dac72ad:52'>
<v0 p=0xfaac n=2 crc='0x9e83486d:2'>
00000: 61 62 ; ab
</v0>
<v1 p=0xfaae n=8 crc='0xf3990149:8'>
00002: 63 64 65 66 67 68 69 6a ; cdefghij
</v1>
<v2 p=0xfab6 n=10 crc='0x3741de04:10'>
0000a: 6b 6c 6d 6e 6f 70 71 72 73 74 ; klmnopqrst
</v2>
<v3 p=0xfac0 n=7 crc='0x9415c927:7'>
00014: 75 76 77 78 79 7a 41 ; uvwxyzA
</v3>
<v4 p=0xfac7 n=7 crc='0x75a57478:7'>
0001b: 42 43 44 45 46 47 48 ; BCDEFGH
</v4>
<v5 p=0xface n=14 crc='0xcd2d51e1:14'>
00022: 49 4a 4b 4c 4d 4e 4f 50 51 52 53 54 ; IJKLMNOPQRST
0002e: 59 55 ; YU
</v5>
<v6 p=0xfadc n=1 crc='0x270d2bda:1'>
00030: 57 ; W
</v6>
<v7 p=0xfadd n=3 crc='0x7d29f8ed:3'>
00031: 58 59 5a ; XYZ
</v7></ydvp>
Once Wil gets a unit test working well with small sample sets, he usually cranks up the number of iterations to more than a hundred thousand test cases, to ensure many variations of odd size combinations are exercised. Wil doesn't want to find out later that some edge case had a bug in single byte iovecs (for example) in certain positions when multiple coincidences occur. With pseudo random monte carlo testing, you might as well run an obscenely large number of sample data set cases if the elapsed time isn't enough to keep a developer waiting. (And even then, the developer can wait anyway for i/o tests that can't be rigorously covered in less than a few minutes of wall clock time.) In-memory unit tests are over in a flash. |
A submenu for demos appears below, letting you
go to the page on a topic written as a demo (as the
demos page defines it).
menu
thorn: todo, names, fd, iovec, assert, log, run, hex, crc, buf, in, out, quote, escape, compare, file, deck, cow, arc, blob, tree, slice, rand « Þ, time, stat, hash, heap, node, primes, page, book, pile, stack, atomic, lock, mutex, thread, map, meter, list, iter, ctype (mu: toy, peg, imm, tag, box, symbol, token, number, bigint, class, method, reader, writer, eval, env, vm, gc, world, pcode, compiler, asm, lathe, lisp, smalltalk, design, weight, jar, card, harp, debug, profile) Some demos are stubs: todo is a demo guide. See toy for mu updates on language pages; names introduces naming schemes.
boxmuller
Class yr64g below is Wil's fourth or fifth rewrite of Carter's box_muller() (bottom column left). Name yr64g means thorn real 64-bit generator. The main function grandom() returns the same value as Carter's box_muller(). Method gaussian() is an inline synonym since normal distribution is also called gaussian distribution; operator++() is yet another synonym. class yr64g { // gaussian pseudo-random r64 generator «
protected: // http://www.taygeta.com/random/gaussian.html
u32* g_u32; // seed used by r64rand() generator
double g_sdv; // standard dev (sqrt(variance))
double g_avg; // mean for values; ie average
double g_now; // last value returned by operator*()
double g_r64; // precalculated next rand r64
bool g_use_r64; // true when g_r64 is good
public:
~yr64g() { } // noop: don't care «
yr64g(u32* seed); // saved by g_u32 for future use
yr64g(u32* seed, double avg, double sdv);
void ginit(r64 avg, r64 sdv) { g_avg = avg; g_sdv = sdv; }
void ginit(u32* s, r64 avg, r64 sdv) { «
g_u32 = s; g_avg = avg; g_sdv = sdv;
}
u32* gu32() const { return g_u32; } // seed «
double gavg() const { return g_avg; } // average «
double gsdv() const { return g_sdv; } // std dev «
u32 gu32rand() { // wrapper for h32rand() «
u32 u = h32rand(*g_u32); *g_u32 = u; return u; };
u64 gu64rand();
r64 grandom(double avg, double sdv); // Box-Muller
r64 gaussian() { return this->grandom(g_avg, g_sdv); } «
// guniform() returns rand r64 such that 0.0 < r < 1.0
r64 guniform(); // pseudo-rand in [0,1] unit interval
double operator++() { return (g_now = gaussian()); } «
double operator*() { return g_now; }
};
An interesting demonstration of gaussian generator yr64g appears in the future stat demo, which empirically verifies the box-muller tranformation really does emit a distribution with the requested mean and standard deviation. (The yrsum class in the stat demo calculates the mean and standard deviation in a sequence of values.) Before diving into the code, let's consider a couple of your potential questions. How does the box-muller tranformation work? And why would you want gaussian bell curve distributions in random number generators? What's it good for? First, Wil doesn't know how it works; you can read the literature if you really want to know. Wil just treats the algorithm as a little bit of convenient magic simplifying test data set generation. After empirically seeing it do the right thing in the stat demo, Wil no longer has concerns about how it works. It just does. Second, bell curve distributions are great to model natural data sets of variable sizes that still cluster around expected values with some typical variance from the average value. Wil first used a random gaussion distribution to model what happens when caching web pages in a reverse proxy server, given a population of web pages having an expected average size and consistent standard deviation. Among other things, this is a good way to exercise algorithms that optimize data sets with locality better than totally random data sets. The pattern of clustering around the average at the top of a bell curve is a good appoximation of data access patterns with some locality. yr64g::yr64g(u32* seed) : g_u32( seed ), g_sdv( 1.0 ), «
g_avg(0.0), g_now(0.0), g_r64(0.0), g_use_r64(false) {
}
yr64g::yr64g(u32* seed, double avg, double sdv)
: g_u32(seed), g_sdv(sdv),
g_avg(avg), g_now(0.0), g_r64(0.0), g_use_r64(false) {
}
A seed to constructors passes by pointer so yr64g can share an external random number stream with other users. r64 yr64g::grandom(double avg, double sdv) { «
double r64val;
if ( g_use_r64 ) {
r64val = g_r64;
g_use_r64 = false;
}
else { // Box-Muller transformation
double x1, x2, w;
do {
x1 = 2.0 * this->guniform( ) - 1.0;
x2 = 2.0 * this->guniform( ) - 1.0;
w = x1 * x1 + x2 * x2;
}
while ( w >= 1.0 );
w = ::sqrt( (-2.0 * ::log( w )) / w);
r64val = x1 * w; // first val to return
g_r64 = x2 * w; // second value created
g_use_r64 = true; // next time use g_r64
}
return avg + (r64val * sdv);
}
This version of grandom() closely follows Carter's original. r64 yr64g::guniform() { // pseudo-rand in [0,1] unit interval «
return r64rand(*g_u32, g_u32); // cf y1.cpp
}
u64 yr64g::gu64rand() { // two u32 rand's joined as u64 «
u32 one = h32rand(*g_u32);
u32 two = h32rand(one); *g_u32 = two;
return ( ((u64) one) << 32 ) | ((u64) two);
}
The remaining two methods are applications of the Park and Miller random number generators; guniform() is central to grandom() and ought to be inlined (if speed matters).
random partitions
Code for ydvp::pjigsaw() almost appeared earlier in the iovec demo where ydvp was defined (cf «). But since that demo was already very long, this method using yr64g was saved for presentation here. Sample code showing typical effect for an input yv sequence is shown bottom column left (cf «). So let's just start with a documentation comment in the code explaining what it does. (Wil often explains complex methods with comments when intended effect is otherwise hard to grasp, as in this case.) // pjigsaw() inits this iter to cut src into a random puzzle; «
// returns the actual new psum(), expected to equal src.v_n,
// aiming for an iter whose content equals that inside src.
// This is for pseudo random monte carlo testing to exercise
// scatter-gather code. A Box-Muller guassian distribution of
// iovec sizes is generated with yr64g, using the given seed,
// with a target of using half the p_x available iovecs in this
// iter. But if this would make the average iovec less than
// eight bytes, the average size is eight bytes (the std dev
// is always two thirds the average random iovec). Min size
// for an iovec added is one byte, but max could easily get
// three stardard deviations or more above the average.
"Dang it, Wil," complained Stu. "Can't you write a slightly more crisp summary of that here?" "Yes, I could," replied Wil. "But you're getting spoiled. That description's not half bad. Did you try to read it?" "I think I have an acquired aversion to reading code comments," Stu explained. "I'm gunshy. Have pity on me." "Oh you spazz," Wil sighed. "Start at Box-Muller in the comment and read each sentence slowly. You'll get it." n32 ydvp::pjigsaw(yv const& src, u32* randseed) { // «
if (0 == p_x) // degenerate noop case? not even one iovec?
return 0;
u32 seed = h32rand(*randseed); // immune to seed changes
if (src.v_n < 8 || p_x <= 4) { // undersized? single iovec?
p_n = 1; // just one (need more iovecs or more src)
p_v[0] = (iovec) src; // yv::operator iovec()
return src.v_n; // total length added
}
This opening part of the method first checks whether the iovec array length p_x is zero, and does nothing given no iovecs. Then it creates a copy of the random seed in local seed in case some other thread is also changing it. Then it checks whether the source yv vector is too small to bother partitioning, or whether the iovec array is too small to aim to use only half; either way, all of src becomes one iovec as output. double all = src.v_n; // total target sum
double avg = all/(p_x/2); // target average iovec
if (avg < 8.0) // under desired min?
avg = 8.0;
double std = (avg * 2)/3; // two thirds std dev
yr64g boxm(&seed, avg, std); // Box-Muller generator
Here pjigsaw() aims for an average fragment size that would use half the iovecs in the ydvp iovec array, with a standard deviation of two thirds the average. But eight is the min for average. u8* p = src.v_p; // cursor over source bytes
n32 n = src.v_n; // total bytes to add (psum())
iovec* v = p_v; // cursor in destination iovecs
iovec* x = v + p_x; // one past last in vector
--x; // reserve last iovec for trailing over spill
These pointers let pjigsaw() walk both source yv and destination iovec array while reserving the last iovec as a safety catch-all iovec, in case random chance uses all the iovecs with tiny fragments. while (n && v < x) { // more src and iovec slots?
double d = boxm.gaussian(); // pseudo rand length
// makes sure quantum is signed -- can be negative
long quantum = (long) (d + 0.5); // round up by 0.5
if (quantum < 1) // oops? too small?
quantum = 1; // at least one byte per iovec
else if (quantum > (long) n) // more than remains?
quantum = n; // just what remains
yassert(n >= (n32) quantum); // quantum is okay
yv piece(p, quantum); // next puzzle piece
*v++ = (iovec) piece; // yv::operator iovec()
n -= quantum;
p += quantum;
}
if (n) { // final overspill needs adding?
yv last(p, n); // all of remainder
*v++ = (iovec) last; // yv::operator iovec()
}
p_n = v - p_v; // len is count of used iovecs
*randseed = seed; // export new seed value
return src.v_n; // guaranteed all added
}
The rest should be clear enough from earlier comments. Final iovec array length is set on the line assigning p_n toward the end. |
demos « Þ
+ todo + names + fd + iovec + assert + log + run + hex + crc + buf + in + out + quote + escape + compare + file + deck + cow + arc + blob + tree + slice + rand « Þ + time + stat + hash + heap + node + primes + page + book + pile + stack + atomic + lock + mutex + thread + map + meter + list + iter + ctype |