#!/usr/bin/env perl use strict; use warnings; use v5.16; use Getopt::Long; ############################################################################### # Scott Baker - 2025-10-29 - https://www.perturb.org/ ############################################################################### # Pure Perl versions of the mwc128, mwc192, and mwc256 PRNGs. The math for # these generators is done in the 128 bit space. Perl cannot natively handle # numbers that large so we `use bigint` to emulate uint128 variables and then # clamp back down to 64bit numbers. # # This is several orders of magnitude slower than the C version, but that's to # be expected because of the way we're forced to do the math. ############################################################################### # /* Borrowed from: https://prng.di.unimi.it/MWC128.c */ # # #define MWC_A1 0xffebb71d94fcdaf9 # # /* The state must be initialized so that 0 < c < MWC_A1 - 1. # For simplicity, we suggest to set c = 1 and x to a 64-bit seed. */ # uint64_t x, c; # # uint64_t inline mwc128_next() { # const uint64_t result = x; // Or, result = x ^ (x << 32) (see above) # const __uint128_t t = MWC_A1 * (__uint128_t)x + c; # x = t; # c = t >> 64; # return result; # } ############################################################################### my $random; GetOptions( 'random' => \$random, ); my $iterations = $ARGV[0] || 20; ############################################################################### ############################################################################### my $_x = 16413207192273257406; # mwc128 my $_y = 3426865068386098881; # mwc128, mwc192 my $_z = 10741635696922576250; # mwc128, mwc192, and mwc256 my $_c = 1; if ($random) { $_x = perl_rand64(); $_y = perl_rand64(); $_z = perl_rand64(); print color('yellow', "Using random seeds: $_c, $_x, $_y, $_z\n\n"); } print color('white'); printf("%2s %14s %21s %21s\n", "", "MWC128", "MWC192", "MWC256"); print color('reset'); for (1 .. $iterations) { printf("%2d) %21u %21u %21u\n", $_, mwc128(), mwc192(), mwc256()); } ############################################################################### ############################################################################### sub mwc128 { use bigint; my $result = $_x; # Math is done bigint and then clamped to 128 bits my $t = (18441034436880161529 * $_x + $_c) & 340282366920938463463374607431768211455; # Clamped to 64 bits $_x = $t & 18446744073709551615; $_c = $t >> 64; return $result; } sub mwc192 { use bigint; my $result = $_y; # Math is done bigint and then clamped to 128 bits my $t = (18419808683250244998 * $_x + $_c) & 340282366920938463463374607431768211455; $_x = $_y; # Clamped to 64 bits $_y = $t & 18446744073709551615; $_c = $t >> 64; return $result; } sub mwc256 { use bigint; my $result = $_z; # Math is done bigint and then clamped to 128 bits my $t = (18443978745271340463 * $_x + $_c) & 340282366920938463463374607431768211455; $_x = $_y; $_y = $_z; # Clamped to 64 bits $_z = $t & 18446744073709551615; $_c = $t >> 64; return $result; } ############################################################################### ############################################################################### sub perl_rand64 { my $high = int(rand() * 2**32 - 1); my $low = int(rand() * 2**32 - 1); my $ret = ($high << 32) | $low; return $ret; } sub trim { my ($s) = (@_, $_); # Passed in var, or default to $_ if (!defined($s) || length($s) == 0) { return ""; } $s =~ s/^\s*//; $s =~ s/\s*$//; return $s; } # String format: '115', '165_bold', '10_on_140', 'reset', 'on_173', 'red', 'white_on_blue' sub color { my ($str, $txt) = @_; # If we're NOT connected to a an interactive terminal don't do color if (-t STDOUT == 0) { return $txt || ""; } # No string sent in, so we just reset if (!length($str) || $str eq 'reset') { return "\e[0m"; } # Some predefined colors my %color_map = qw(red 160 blue 27 green 34 yellow 226 orange 214 purple 93 white 15 black 0); $str =~ s|([A-Za-z]+)|$color_map{$1} // $1|eg; # Get foreground/background and any commands my ($fc,$cmd) = $str =~ /^(\d{1,3})?_?(\w+)?$/g; my ($bc) = $str =~ /on_(\d{1,3})$/g; if (defined($fc) && int($fc) > 255) { $fc = undef; } # above 255 is invalid # Some predefined commands my %cmd_map = qw(bold 1 italic 3 underline 4 blink 5 inverse 7); my $cmd_num = $cmd_map{$cmd // 0}; my $ret = ''; if ($cmd_num) { $ret .= "\e[${cmd_num}m"; } if (defined($fc)) { $ret .= "\e[38;5;${fc}m"; } if (defined($bc)) { $ret .= "\e[48;5;${bc}m"; } if (defined($txt)) { $ret .= $txt . "\e[0m"; } return $ret; } sub file_get_contents { open(my $fh, "<", $_[0]) or return undef; binmode($fh, ":encoding(UTF-8)"); my $array_mode = ($_[1]) || (!defined($_[1]) && wantarray); if ($array_mode) { # Line mode my @lines = readline($fh); # Right trim all lines foreach my $line (@lines) { $line =~ s/[\r\n]+$//; } return @lines; } else { # String mode local $/ = undef; # Input rec separator (slurp) return my $ret = readline($fh); } } sub file_put_contents { my ($file, $data) = @_; open(my $fh, ">", $file) or return undef; binmode($fh, ":encoding(UTF-8)"); print $fh $data; close($fh); return length($data); } # Creates methods k() and kd() to print, and print & die respectively BEGIN { if (eval { require Data::Dump::Color }) { *k = sub { Data::Dump::Color::dd(@_) }; } else { require Data::Dumper; *k = sub { print Data::Dumper::Dumper(\@_) }; } sub kd { k(@_); printf("Died at %2\$s line #%3\$s\n",caller()); exit(15); } } # vim: tabstop=4 shiftwidth=4 noexpandtab autoindent softtabstop=4