Last active
June 8, 2026 00:34
-
-
Save rawiriblundell/d54ca69a61bd28d91c803bffa67cca61 to your computer and use it in GitHub Desktop.
An implementation of a xorshift128+ RNG in shell code
This file contains hidden or 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/bash | |
| #set -x | |
| # Function to generate a reliable seed for whatever method requires one | |
| # Because 'date +%s' isn't entirely portable, we try other methods to get the epoch | |
| generate_seed() { | |
| # First we check if /dev/urandom is available. | |
| if [ -c /dev/urandom ] && command -v od >/dev/null 2>&1; then | |
| # Get a string of bytes from /dev/urandom using od | |
| od -N 4 -A n -t uL /dev/urandom | tr -d '\n' | awk '{$1=$1+0};1' | |
| # Otherwise, we see if 'date' will emit an epoch timestamp | |
| elif date '+%s' >/dev/null 2>&1; then | |
| date '+%s' | |
| # In the rare case that we have a limited version of 'date' | |
| # We generate an epoch timestamp with octal correction | |
| # Based on http://www.etalabs.net/sh_tricks.html | |
| else | |
| local int_secs int_mins int_hours int_days int_years int_yearoffset | |
| int_secs=$(TZ=GMT0 date +%S) | |
| int_mins=$(TZ=GMT0 date +%M) | |
| int_hours=$(TZ=GMT0 date +%H) | |
| int_days=$(TZ=GMT0 date +%j | sed 's/^0*//') | |
| int_yearoffset=$(( $(TZ=GMT0 date +%Y) - 1600 )) | |
| int_years=$(( ( | |
| int_yearoffset * 365 | |
| + int_yearoffset / 4 | |
| - int_yearoffset / 100 | |
| + int_yearoffset / 400 | |
| + int_days | |
| - 135140 | |
| ) * 86400 )) | |
| printf '%s\n' "$(( \ | |
| int_years \ | |
| + (${int_hours#0} * 3600) \ | |
| + (${int_mins#0} * 60) \ | |
| + ${int_secs#0} \ | |
| ))" | |
| fi | |
| } | |
| # Start assembling our vars | |
| int_count="${1:-1}" | |
| int_min="${2:-1}" | |
| # Figure out our default maxRand, using 'getconf' | |
| case "$(getconf LONG_BIT)" in | |
| (64) | |
| rand_ceiling=9223372036854775807 | |
| ;; | |
| (32) | |
| rand_ceiling=2147483647 | |
| ;; | |
| (*) | |
| rand_ceiling=32767 | |
| ;; | |
| esac | |
| int_max="${3:-$rand_ceiling}" | |
| rand_range=$(( int_max - int_min + 1 )) | |
| (( rand_range > 0 )) || { | |
| printf -- '%s\n' "Invalid range" >&2 | |
| exit 1 | |
| } | |
| full_range=0 | |
| if (( int_min == 0 && int_max == rand_ceiling )); then | |
| full_range=1 | |
| else | |
| threshold=$(( (rand_ceiling - rand_range + 1) % rand_range )) | |
| fi | |
| # Initialise the seed state | |
| seed="${4:-$(generate_seed)}" | |
| (( seed == 0 )) && seed=4 # RFC 1149.5 | |
| seed0="${seed}" | |
| seed1=$(( seed ^ 0x9e3779b9 )) | |
| # Get the ints ready for the loop | |
| int0="${seed0}" | |
| int1="${seed1}" | |
| while (( int_count > 0 )); do | |
| old_int0="${int0}" | |
| old_int1="${int1}" | |
| # xorshift128+ state transition | |
| int0="${old_int1}" | |
| old_int0=$(( old_int0 ^ (old_int0 << 23) )) | |
| int1=$(( \ | |
| old_int0 \ | |
| ^ old_int1 \ | |
| ^ (old_int0 >> 17) \ | |
| ^ (old_int1 >> 26) \ | |
| )) | |
| rnd=$(( int1 + old_int1 )) | |
| if (( full_range )); then | |
| if (( rnd >= 0 )); then | |
| printf -- '%u\n' "$rnd" | |
| (( int_count-- )) | |
| fi | |
| continue | |
| fi | |
| if (( rnd >= 0 && rnd >= threshold )); then | |
| printf -- '%u\n' "$(( (rnd % rand_range) + int_min ))" | |
| (( int_count-- )) | |
| fi | |
| done |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment