Skip to content

Instantly share code, notes, and snippets.

@loreb
Last active December 14, 2015 10:29
Show Gist options
  • Save loreb/5072178 to your computer and use it in GitHub Desktop.
Save loreb/5072178 to your computer and use it in GitHub Desktop.
George Marsaglia's favorite PRNG in vimscript and POSIX shell - fast, good, easy to use, remarkably short for what it does. The shell version breaks under mksh, but I'm too lazy to fix it now^H^H^H^H fixed.
#! /bin/sh
# Marsaglia's favorite PRNG, see his 20/01/99 post on sci.stat.math at
# https://groups.google.com/forum/?fromgroups=#!topic/sci.crypt/yoaCpGWKEk0
# https://en.wikipedia.org/wiki/Random_number_generation#Computational_methods
# = API =
# marsaglia_seed [w] [z] / both non-zero
# marsaglia() returns a random uint32 in $RAND
# -- can't use $RANDOM, setting it means seeding the PRNG in bash/ksh/zsh
#
# Notice that you __cannot__ use it as in
# $ echo `marsaglia`
# You'd get a variation of https://xkcd.com/221/ with a bit
# of Dilbert's tour of accounting mixed in.
# ./marsaglia.sh: 4: test: Illegal number: 0x7fffffff
# At least in bash/dash/zsh, test(1) fails to recognize hexadecimals.
# Shell arithmetic is ok, posix allows using hex.
# POSIX me harder.
INT32_MAX=$((0x7fffffff + 0))
UINT32_MAX=$((0xffffffff + 0))
# This PRNG returns an uint32_t, ie a number in [0, 0xFFFFFFFF];
# POSIX specifies that the shell uses at least int32 arithmetic.
isINT_MAX() {
test 0 -lt $1 && test 0 -gt $((1 + $1))
}
if isINT_MAX $INT32_MAX ; then
marsaglia() {
# negative numbers => sign bit: -1>>16 vs UINT32_MAX>>16
m_z=$((36969 * ($m_z & 65535) + (0xFFFF&($m_z >> 16)) + 0))
m_w=$((18000 * ($m_w & 65535) + (0xFFFF&($m_w >> 16)) + 0))
RAND=$(($m_z * 65536 + $m_w)) # 32-bit result
}
else
marsaglia() {
m_z=$((36969 * ($m_z & 65535) + ($m_z >> 16) + 0))
m_w=$((18000 * ($m_w & 65535) + ($m_w >> 16) + 0))
RAND=$(($m_z * 65536 + $m_w)) # overflow...
RAND=$(($RAND & 0xFFFFFFFF)) # 32-bit result
}
fi
marsaglia_seed() {
local w=1
local z=$$
if [ $# -gt 2 ] ; then
echo>&2 wtf\?
return 100
fi
[ $# -ge 1 ] && w="$1"
[ $# -ge 2 ] && z="$2"
w=$(($w & 0xFFFFFFFF)) || return
z=$(($z & 0xFFFFFFFF)) || return
if [ $w -eq 0 ] || [ $z -eq 0 ] ; then
echo>&2 "can't seed marsaglia with zeroes ($*)!"
return 100
fi
m_w=$w
m_z=$z
}
# Test vs C version
if [ x != x"$DEBUG" ] ; then
marsaglia_seed 3576326487 1644975104 # /dev/urandom
expected=''
expected="$expected 4076571226 3763205391 101373103 1117688830 869526986"
expected="$expected 3150885829 3854912735 665006352 1815267869 735519881"
n=0
for i in $expected ; do
marsaglia
got=$RAND
n=$(($n + 1))
[ $got -eq $i ] && continue
printf >&2 "test($0)#%d: wanted %d, got %d\n" $n $i $got
exit 123
done
echo>&2 'Marsaglia() tested ok'
unset expected got n
fi
# Default seeding
marsaglia_seed
# use "$@" as seed
if [ x != x"$TEST" ] ; then
marsaglia_seed "$@" || exit
set -e # die if echo(1) fails
while true ; do
marsaglia; echo $RAND
marsaglia; echo $RAND
marsaglia; echo $RAND
marsaglia; echo $RAND
done
fi
" George Marsaglia's favorite PRNG in vimscript.
"
" Someday Debian Stable will finally ships a Vim in which exists('*and'),
" thus making Wheezy my favorite Debian release.
" Tnx wikipedia for showing me this gem.
function! s:shift16right(n)
if a:n < 0
let n = a:n
" Manually shift one bit => positive, then shift 15 bits.
let n = and(0x7fffffff, n)
let n = or( 0x40000000, n/2)
return n / 0x8000
endif
return a:n / 0x10000
endfunction
function! s:intmax(n)
" Vim uses N bits signed ints, N >= 32.
" See vim/src/structs.h for details.
return a:n>0 && a:n+1<0
endfunction
" This PRNG returns an uint32_t, ie a number in [0, 0xFFFFFFFF];
" vim typically uses int32_t, meaning this can return negative numbers.
if s:intmax(0x7fffffff)
" $ grep Hz /proc/cpuinfo
" cpu MHz : 1800.000
" 100k calls in seven/eight seconds.
" 10x faster adding f_marsaglia() to src/eval.c
function! Marsaglia()
let s:m_z = 36969 * and(s:m_z, 65535) + s:shift16right(s:m_z)
let s:m_w = 18000 * and(s:m_w, 65535) + s:shift16right(s:m_w)
return (s:m_z * 65536) + s:m_w " 32-bit result
endfunction
else
let s:m_w = and(s:m_w, 0xFFFFFFFF)
let s:m_z = and(s:m_z, 0xFFFFFFFF)
function! Marsaglia()
" These 2 lines can't overflow 32 bits
let s:m_z = 36969 * and(s:m_z, 65535) + s:shift16right(s:m_z)
let s:m_w = 18000 * and(s:m_w, 65535) + s:shift16right(s:m_w)
" This line *will* overflow 32 bits
return and(0xFFFFFFFF, (s:m_z * 65536) + s:m_w) " 32-bit result
endfunction
endif
function! MarsagliaSeed(w,z)
let w = and(a:w, 0xFFFFFFFF)
let z = and(a:z, 0xFFFFFFFF)
if w == 0 || z == 0
throw printf("(m_w=%d, m_z=%d) can't have any zeroes!", a:w, a:z)
endif
let s:m_w = w
let s:m_z = z
endfunction
" Test vs C version
if len($DEBUG) > 0
call MarsagliaSeed(3576326487, 1644975104) " /dev/urandom
let expected = [
\ 4076571226, 3763205391, 101373103, 1117688830, 869526986,
\ 3150885829, 3854912735, 665006352, 1815267869, 735519881
\ ]
for i in range(len(expected))
let got = Marsaglia()
if got == expected[i]
continue
endif
throw printf("%d: wanted %d, got %d", i, expected[i], got)
endfor
echomsg 'Marsaglia() tested ok'
unlet expected got
endif
" Default seeding
" We could use many "random" values, such as:
" size/mtime of $TMP, $TEMP, $TMPDIR, /etc/passwd, and so on;
" on windows, $APPDATA, $WINDIR, and more.
" v:windowid (zero w/o gui)
" v:oldfiles (hashed once)
" Vim command history
" ...
" This crap is here *only* to remind myself of the possibilities.
function! s:hash(str)
let h = 5381 " djb
for i in range(len(a:str))
let h = h * 33
let h = xor(h, char2nr(a:str[i]))
endfor
return h
endfunction
let s:m_w = localtime() + 0x10000*getpid()
" On 64-bit systems, pid_max can be set to any value up to 2^22
" (PID_MAX_LIMIT, approximately 4 million).
let s:m_z = s:hash(hostname() . string(v:oldfiles) . getline("."))
if s:m_w == 0
let s:m_w = 0x86BE1074 " from /dev/urandom
endif
if s:m_z == 0
let s:m_z = 0x4376D02F " from /dev/urandom
endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment