Last active
December 14, 2015 10:29
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#! /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 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
" 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