use wrapn::{Wrap, wrap};
use crate::{
_internal::{i2f_bits, impl_seed, u2f_01},
rng::Rng,
rng64::SplitMix64,
};
const THREEFISH_C240: u64 = 0x1BD11BDAA9FC1A22;
const THREE_FISH_N_ROUNDS: usize = 72;
const THREEFISH_PI: [usize; 4] = [0, 3, 2, 1];
const KS_N: usize = THREE_FISH_N_ROUNDS / 4 + 1; const KS_K_IDX: [[usize; 4]; KS_N] = {
let mut t = [[0usize; 4]; KS_N];
let mut s = 0;
while s < KS_N {
t[s] = [s % 5, (s + 1) % 5, (s + 2) % 5, (s + 3) % 5];
s += 1;
}
t
};
const KS_TW_IDX: [[usize; 2]; KS_N] = {
let mut t = [[0usize; 2]; KS_N];
let mut s = 0;
while s < KS_N {
t[s] = [s % 3, (s + 1) % 3];
s += 1;
}
t
};
const THREEFISH_R_256: [[u32; 2]; 8] = [
[14, 16],
[52, 57],
[23, 40],
[5, 37],
[25, 33],
[46, 12],
[58, 22],
[32, 32],
];
#[repr(C, align(64))]
pub struct Threefish256 {
c: [Wrap<u64>; 4],
k: [Wrap<u64>; 5],
tw: [Wrap<u64>; 3],
index: Wrap<usize>,
buffer: [Wrap<u64>; 4],
}
impl Threefish256 {
pub fn new(seed: u64) -> Self {
let mut seedgen = SplitMix64::new(seed);
let mut k = wrap![0u64; 5];
k[0] = seedgen.nextu().into();
k[1] = seedgen.nextu().into();
k[2] = seedgen.nextu().into();
k[3] = seedgen.nextu().into();
k[4] = k[0] ^ k[1] ^ k[2] ^ k[3] ^ THREEFISH_C240;
let tw0 = seedgen.nextu().into();
let tw1 = seedgen.nextu().into();
Self {
c: wrap![0; 4],
k,
tw: [tw0, tw1, tw0 ^ tw1],
index: 4.into(),
buffer: wrap![0; 4],
}
}
#[inline(always)]
fn mix(x0: Wrap<u64>, x1: Wrap<u64>, r: u32) -> [Wrap<u64>; 2] {
let y0 = x0 + x1;
[y0, x1.rotate_left(r) ^ y0]
}
#[inline(always)]
fn key_schedule(k: &[Wrap<u64>; 5], tw: &[Wrap<u64>; 3], s: usize) -> [Wrap<u64>; 4] {
let ki = KS_K_IDX[s];
let ti = KS_TW_IDX[s];
[
k[ki[0]],
k[ki[1]] + tw[ti[0]],
k[ki[2]] + tw[ti[1]],
k[ki[3]] + s as u64,
]
}
#[inline(always)]
fn next_block(&mut self) -> [Wrap<u64>; 4] {
let mut v = self.c;
for r in 0..THREE_FISH_N_ROUNDS {
let mut e = wrap![0u64; 4];
if (r & 0b011) == 0 {
let ksi = Self::key_schedule(&self.k, &self.tw, r >> 2);
e[0] = v[0] + ksi[0];
e[1] = v[1] + ksi[1];
e[2] = v[2] + ksi[2];
e[3] = v[3] + ksi[3];
} else {
e = v;
}
let mut f = wrap! [0u64; 4];
let r_sh = THREEFISH_R_256[r & 7]; let mx0 = Self::mix(e[0], e[1], r_sh[0]);
f[0] = mx0[0];
f[1] = mx0[1];
let mx1 = Self::mix(e[2], e[3], r_sh[1]);
f[2] = mx1[0];
f[3] = mx1[1];
for i in 0..v.len() {
v[i] = f[THREEFISH_PI[i]];
}
}
let ksi = Self::key_schedule(&self.k, &self.tw, THREE_FISH_N_ROUNDS.div_ceil(4));
let dst = [
(v[0] + ksi[0]) ^ self.c[0],
(v[1] + ksi[1]) ^ self.c[1],
(v[2] + ksi[2]) ^ self.c[2],
(v[3] + ksi[3]) ^ self.c[3],
];
self.c[0] += 1;
if self.c[0] == 0 {
self.c[1] += 1;
if self.c[1] == 0 {
self.c[2] += 1;
if self.c[2] == 0 {
self.c[3] += 1;
}
}
}
dst
}
#[inline]
pub fn nextu(&mut self) -> [u64; 4] {
if self.index >= 4 {
self.buffer = self.next_block();
self.index = 0.into();
}
let val = self.buffer;
self.index += 4;
val.map(|x| x.value())
}
#[inline]
pub fn nextf(&mut self) -> [f64; 4] {
self.nextu().map(|x| u2f_01!(f64, 64, x))
}
#[inline]
pub fn randi(&mut self, min: i64, max: i64) -> [i64; 4] {
let range = (max as i128 - min as i128 + 1) as u128;
self.nextu()
.map(|x| ((x as u128 * range) >> 64) as i64 + min)
}
#[inline]
pub fn randf(&mut self, min: f64, max: f64) -> [f64; 4] {
let range = max - min;
self.nextu().map(|x| u2f_01!(f64, 64, x) * range + min)
}
}
impl_seed!(Threefish256, 64);
#[cfg(test)]
mod tests {
use super::*;
crate::safe_test!(Threefish256);
}