Skip to content

Instantly share code, notes, and snippets.

@loreb
Created February 14, 2013 17:30
Show Gist options
  • Save loreb/4954490 to your computer and use it in GitHub Desktop.
Save loreb/4954490 to your computer and use it in GitHub Desktop.
The vimscript translation of a small rng published by Daniel J. Bernstein.
" https://groups.google.com/forum/?hl=en&fromgroups=#!topic/sci.physics/SWhCr8SEVWw
" TLDR: a PRNG by Dan Bernstein ported to vimscript
" The generator shown below is faster than Marsaglia's on my Pentium-100.
" It passes the Diehard tests without trouble.
"
" Use RANDOM for one 32-bit random number. Do not use RANDOM twice in one
" line. You can put a 64-bit seed into random_in[2 and 3]. The effect of
" seed m is to jump ahead by 2^68 m in a single sequence of period 2^132.
" It's easy to determine the seed from the sequence, but not with the sort
" of code that shows up in scientific and statistical applications.
"
" If you want even better speed, compile random_fill() separately with gcc
" -O0 -fomit-frame-pointer. Higher gcc optimization levels produce worse
" code. (``Real programmers use asm.'')
"
" ---Dan
if 0x7fffffff <= 0 || 0x7fffffff+1 >= 0
" Vim uses plain C signed int's (long if <32 bits);
" hence it's usually 32 bits everywhere.
echoerr 'this RNG requires 32 bit arithmetic!'
finish
endif
" These fail miserably for shifts>=31 - canthappen, shifts are hard-coded
let s:powerof2 = [
\ 0x1, 0x2, 0x4, 0x8,
\ 0x10, 0x20, 0x40, 0x80,
\ 0x100, 0x200, 0x400, 0x800,
\ 0x1000, 0x2000, 0x4000, 0x8000,
\ 0x10000, 0x20000, 0x40000, 0x80000,
\ 0x100000, 0x200000, 0x400000, 0x800000,
\ 0x1000000, 0x2000000, 0x4000000, 0x8000000,
\ 0x10000000, 0x20000000, 0x40000000
\ ]
function! s:leftshift(x, offset)
return a:x * s:powerof2[a:offset]
endfunction
function! s:rightshift(x, offset)
if a:offset == 0
return a:x
endif
if a:x < 0
" Debian Squeeze ships a Vim without and/or/xor.
" Ditto for mingw (20130208) -- sorry guys.
let x = and(a:x, 0x7fffffff) " shift the sign bit
let x = x / 2
let x = or(x, 0x40000000) " readd the sign bit (shifted)
return s:rightshift(x, a:offset - 1)
endif
return a:x / s:powerof2[a:offset]
endfunction
function! s:ROTATE(x, b)
return or(s:leftshift(a:x, a:b), s:rightshift(a:x, 32 - a:b))
endfunction
function! s:foobar(state, xy, nbits, idx, sum)
"let x = ROTATE(x,5) | x^=y | y=random_t[0] | x+=y | y=sum | random_t[0] =x | y+=x
let x = a:xy[0]
let y = a:xy[1]
let x = s:ROTATE(x, a:nbits)
let x = xor(x, y)
let y = a:state.t[a:idx]
let x += y
let y = a:sum
let a:state.t[a:idx] = x
let y += x
let a:xy[0] = x
let a:xy[1] = y
endfunction
function! s:random_fill(state)
"register uint32 y, x = 0, sum = 0
" No need to worry about signed/unsigned: all that matters is that
" the operations performed on them result in the same bits of output.
let y = 'hi' | let x = 0 | let sum = 0
for i in range(16)
let a:state.t[i] = 0
endfor
for i in [0,4,8,12]
let a:state.t[i] = a:state.in[i/4]
endfor
let s = a:state
for rounds in range(3)
let sum += 0x9e3779b9 | let y=x | let y+=sum
let xy = [x,y]
" loop unrolling ftw!
call s:foobar(s, xy, 5, 0, sum)
call s:foobar(s, xy, 7, 1, sum)
call s:foobar(s, xy, 9, 2, sum)
call s:foobar(s, xy, 13, 3, sum)
call s:foobar(s, xy, 5, 4, sum)
call s:foobar(s, xy, 7, 5, sum)
call s:foobar(s, xy, 9, 6, sum)
call s:foobar(s, xy, 13, 7, sum)
call s:foobar(s, xy, 5, 8, sum)
call s:foobar(s, xy, 7, 9, sum)
call s:foobar(s, xy, 9, 10, sum)
call s:foobar(s, xy, 13, 11, sum)
call s:foobar(s, xy, 5, 12, sum)
call s:foobar(s, xy, 7, 13, sum)
call s:foobar(s, xy, 9, 14, sum)
"ROTATE(x,9); x^=y; y=random_t[14]; x+=y; y=sum; random_t[14]=x; y+=x;
let x = xy[0] | let y = xy[1]
"ROTATE(x,13); x^=y; y=random_t[15]; x+=y; random_t[15] = x;
let x = s:ROTATE(x,13) | let x = xor(x, y) | let y=s.t[15] | let x+=y
let s.t[15] = x
endfor
" if (!++random_in[0]) if (!++random_in[1]) if (!++random_in[2]) ++random_in[3]
let s.in[0] += 1
if !s.in[0]
let s.in[1] += 1
if !s.in[1]
let s.in[2] += 1
if !s.in[2]
let s.in[3] += 1
endif
endif
endif
let a:state.pos = 15
return a:state.t[15]
endfunction
" You can give a seed or two if you wish:
" let PRNG0 = RANDOM_new()
" let PRNG1 = RANDOM_new(localtime())
" let PRNG2 = RANDOM_new(localtime(), getpid())
" XXX iiuc djb only uses random_in[2,3] because they jump a longer distance
" -- one could also use random_in[0,1]
function! RANDOM_new(...)
let random_in = [0,0,0,0]
if a:0 == 1
let random_in[2] = a:1
elseif a:0 == 2
let random_in[2] = a:1
let random_in[3] = a:2
elseif a:0 > 2
throw 'too many arguments, try 0/1/2'
endif
let prng = {}
let prng.in = random_in
let prng.t = repeat([0], 16)
let prng.pos = 0
return prng
endfunction
" 1800 MHz, single cpu => 10k calls in ~4 seconds!
function! RANDOM(state)
if a:state.pos > 0
let a:state.pos -= 1
return a:state.t[a:state.pos]
endif
return s:random_fill(a:state)
endfunction
if len($DEBUG) > 0
" There used to be a stupid bug in s:rightshift
" Overflowing 2G is ok here.
let expected = [
\ 2418795960, 3003258757, 151880706, 3215773290,
\ 513313070, 3657817576, 208155427, 3566403919,
\ 1539252833, 318396758, 2608091252, 3668097793,
\ 2254643236, 1130049323, 1880154880, 2419921727,
\ 2754539177, 258988989, 2664258628, 462086491,
\ 3366175608, 3441317232, 2596750669, 1208093774,
\ 2763004728, 1748815996, 3549270765, 1323780817,
\ 1122145413, 1637054897, 2757387850, 227111478,
\ 3853466287, 364384882, 2116932039, 532721731,
\ ]
let rng = RANDOM_new(0,0)
for i in range(len(expected))
let got = RANDOM(rng)
if got == expected[i]
continue
endif
echoerr printf("%d RANDOM.c says %d, .vim %d", i, expected[i], got)
endfor
" test pi?
unlet rng expected
echomsg "tested ok"
endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment