#!/usr/bin/env perl ############################################################################### # Perl implementation of biski64 PRNG: https://github.com/danielcota/biski64 # # # A lot of work was done here to mimic how C handles overflow multiplication # on large uint64_t numbers. Perl converts scalars that are larger than 2^64 - 1 # to floating point on the backend. We do *NOT* want that, because biski64 # (and most PRNGs) rely on overflow math to do their magic. We utilize # 'use integer' to force Perl to do all math with integer 64bit values. When # overflow occurs Perl likes to convert those values to signed integers. In # the original C all math is done with uint64_t, so we have to convert the # IV/negative numbers back into UV/unsigned (positive) values. # # 2025-12-23 - Scott Baker ############################################################################### # # typedef struct { # uint64_t fast_loop; # uint64_t mix; # uint64_t loop_mix; # } biski64_state; # # uint64_t biski64_next(biski64_state* state) { # const uint64_t output = state->mix + state->loop_mix; # const uint64_t old_loop_mix = state->loop_mix; # # state->loop_mix = state->fast_loop ^ state->mix; # state->mix = rotate_left(state->mix, 16) + # rotate_left(old_loop_mix, 40); # state->fast_loop += 0x9999999999999999ULL; // Additive constant for the Weyl sequence. # return output; # } # ############################################################################### use strict; use warnings; use v5.16; use Getopt::Long; ############################################################################### ############################################################################### my $s1 = 0; # Default 64bit seed1 my $s2 = 0; # Default 64bit seed2 my $s3 = 0; # Default 64bit seed3 my $random = 0; my $seeds = []; GetOptions( 'seed1=i' => \$s1, 'seed2=i' => \$s2, 'seed3=i' => \$s3, 'random' => \$random, 'unit-tests' => \&run_unit_tests, ); if (!$s1 || !$s2 || !$s3 || $random) { randomize_seeds(); } my $iterations = $ARGV[0] || 10; $seeds = [$s1, $s2, $s3]; print color('yellow', "Seeding PRNG with: MIX:$s2 LOOPMIX:$s3 FL:$s1\n\n"); for (my $i = 1; $i <= $iterations; $i++) { my $num64 = biski64($seeds); printf("%2d) %20u\n", $i, $num64); } ################################################################################ ################################################################################ ################################################################################ #my $seeds = [12, 34, 56]; #my $rand = biski64($seeds); sub biski64 { my $seeds = $_[0]; use integer; my $output = $seeds->[1] + $seeds->[2]; my $old_lm = $seeds->[2]; $seeds->[2] = $seeds->[0] ^ $seeds->[1]; $seeds->[1] = rotl($seeds->[1], 16) + rotl($old_lm, 40); #$seeds->[1] |= 0; # Convert IV to UV (not needed here?) $seeds->[0] += 11068046444225730969; return $output; } # Rotate the bits in a 64bit integer to the left and wrap back # around to the right side. sub rotl { my ($num, $shift) = @_; my $ret = ($num << $shift) | ($num >> (64 - $shift)); return $ret; } ############################################################################### ############################################################################### # 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 randomize_seeds { print color(51, "Using random seeds\n"); $s1 = perl_rand64(); $s2 = perl_rand64(); $s3 = perl_rand64(); } sub perl_rand64 { my $low = int(rand() * (2**32-1)); my $high = int(rand() * (2**32-1)); my $ret = ($high << 32) | $low; return $ret; } # 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