#!/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");
}
printf("%2s %14s %21s %21s\n", "", "MWC128", "MWC192", "MWC256");
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;
}
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;
}
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;
}
###############################################################################
###############################################################################
sub perl_rand64 {
my $high = int(rand() * 2**32 - 1);
my $ret = ($high << 32) | $low;
}
sub trim {
my ($s) = (@_, $_); # Passed in var, or default to $_
$s =~ s/^\s*//;
$s =~ s/\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
# 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"; }
}
sub file_get_contents {
if ($array_mode) { # Line mode
# Right trim all lines
foreach my $line (@lines) { $line =~ s/[\r\n]+$//; }
} else { # String mode
}
}
sub file_put_contents {
my ($file, $data) = @_;
}
# Creates methods k() and kd() to print, and print & die respectively
BEGIN {
*k = sub { Data::Dump::Color::dd(@_) };
} else {
*k = sub { print Data
::Dumper::Dumper(\
@_) };
}
sub kd {
k(@_);
}
}
# vim: tabstop=4 shiftwidth=4 noexpandtab autoindent softtabstop=4