#!/usr/bin/env perl
###############################################################################
# Perl implementation of SFC64 - https://github.com/scottchiefbaker/SmokeRand/blob/main/generators/sfc64.c
#
# # 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 SFC64
# (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
###############################################################################
#
#/**
# * @brief sfc64 state.
# */
#typedef struct {
# uint64_t a;
# uint64_t b;
# uint64_t c;
# uint64_t counter;
#} Sfc64State;
#
#static inline uint64_t get_bits_raw(void *state)
#{
# enum {BARREL_SHIFT = 24, RSHIFT = 11, LSHIFT = 3};
# Sfc64State *obj = state;
# const uint64_t tmp = obj->a + obj->b + obj->counter++;
# obj->a = obj->b ^ (obj->b >> RSHIFT);
# obj->b = obj->c + (obj->c << LSHIFT);
# obj->c = ((obj->c << BARREL_SHIFT) | (obj->c >> (64-BARREL_SHIFT))) + tmp;
# return tmp;
#}
###############################################################################
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 $cnt = 1;
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, $cnt];
print color
('yellow', "Seeding PRNG with: $s1 / $s2 / $s3\n\n");
for (my $i = 1; $i <= $iterations; $i++) {
my $num64 = sfc64($seeds);
printf("%2d) %20u\n", $i, $num64);
}
################################################################################
################################################################################
################################################################################
#my $seeds = [12, 34, 56];
#my $rand = sfc64($seeds);
sub sfc64 {
my $seeds = $_[0];
use integer;
my $tmp = $seeds->[0] + $seeds->[1] + $seeds->[3]++;
# Make sure tmp is a UV
$tmp |= 0;
# Bitwise shifts need to be 'no integer' so the shift is unsigned
$seeds->[0] = $seeds->[1] ^ ($seeds->[1] >> 11);
my $x = ($seeds->[2] << 3);
use integer;
$seeds->[1] = $seeds->[2] + $x;
$seeds->[2] = (($seeds->[2] << 24) | ($seeds->[2] >> 40));
use integer;
$seeds->[2] += $tmp;
}
###############################################################################
###############################################################################
# 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 {
*trim = sub {
my ($s) = (@_, $_); # Passed in var, or default to $_
$s =~ s/^\s*//;
$s =~ s/\s*$//;
}
}
*k = sub { Dump::Krumo::kx(@_) };
*kd = sub { Dump::Krumo::kxd(@_) };
} else {
*k = sub { print Data
::Dumper::Dumper(\
@_) };
*kd = sub { print Data
::Dumper::Dumper(\
@_); die; };
}
}
# vim: tabstop=4 shiftwidth=4 noexpandtab autoindent softtabstop=4