#!/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;
}
# 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));
}
###############################################################################
###############################################################################
# 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 randomize_seeds {
print color
(51, "Using random seeds\n");
$s1 = perl_rand64();
$s2 = perl_rand64();
$s3 = perl_rand64();
}
sub perl_rand64 {
my $high = int(rand() * (2**32-1));
my $ret = ($high << 32) | $low;
}
# 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