Merge pull request #71 from kralverde/legacy_random

implement legacy_random and java string hash
This commit is contained in:
Alexander Medvedev
2024-09-01 17:58:03 +01:00
committed by GitHub
3 changed files with 411 additions and 40 deletions

View File

@@ -0,0 +1,326 @@
use super::{
gaussian::GaussianGenerator, hash_block_pos, java_string_hash, Random, RandomSplitter,
};
struct LegacyRand {
seed: u64,
internal_next_gaussian: f64,
internal_has_next_gaussian: bool,
}
impl LegacyRand {
fn next_random(&mut self) -> u64 {
let l = self.seed;
let m = l.wrapping_mul(0x5DEECE66D).wrapping_add(11) & 0xFFFFFFFFFFFF;
self.seed = m;
m
}
}
impl GaussianGenerator for LegacyRand {
fn has_next_gaussian(&self) -> bool {
self.internal_has_next_gaussian
}
fn stored_next_gaussian(&self) -> f64 {
self.internal_next_gaussian
}
fn set_has_next_gaussian(&mut self, value: bool) {
self.internal_has_next_gaussian = value;
}
fn set_stored_next_gaussian(&mut self, value: f64) {
self.internal_next_gaussian = value;
}
}
impl Random for LegacyRand {
fn from_seed(seed: u64) -> Self {
LegacyRand {
seed: (seed ^ 0x5DEECE66D) & 0xFFFFFFFFFFFF,
internal_has_next_gaussian: false,
internal_next_gaussian: 0f64,
}
}
fn next(&mut self, bits: u64) -> u64 {
self.next_random() >> (48 - bits)
}
fn split(&mut self) -> Self {
LegacyRand::from_seed(self.next_i64() as u64)
}
fn next_i32(&mut self) -> i32 {
self.next(32) as i32
}
fn next_i64(&mut self) -> i64 {
let i = self.next_i32();
let j = self.next_i32();
((i as i64) << 32).wrapping_add(j as i64)
}
fn next_f32(&mut self) -> f32 {
self.next(24) as f32 * 5.9604645E-8f32
}
fn next_f64(&mut self) -> f64 {
let i = self.next(26);
let j = self.next(27);
let l = (i << 27).wrapping_add(j);
l as f64 * 1.110223E-16f32 as f64
}
fn next_bool(&mut self) -> bool {
self.next(1) != 0
}
fn next_splitter(&mut self) -> impl RandomSplitter {
LegacySplitter::new(self.next_i64() as u64)
}
fn next_gaussian(&mut self) -> f64 {
self.calculate_gaussian()
}
fn next_bounded_i32(&mut self, bound: i32) -> i32 {
if bound & (bound - 1) == 0 {
(bound as u64).wrapping_mul(self.next(31) >> 31) as i32
} else {
loop {
let i = self.next(31) as i32;
let j = i % bound;
if (i - j + (bound - 1)) > 0 {
return j;
}
}
}
}
}
struct LegacySplitter {
seed: u64,
}
impl LegacySplitter {
fn new(seed: u64) -> Self {
LegacySplitter { seed }
}
}
impl RandomSplitter for LegacySplitter {
fn split_u64(&self, seed: u64) -> impl Random {
LegacyRand::from_seed(seed)
}
fn split_string(&self, seed: &str) -> impl Random {
let string_hash = java_string_hash(seed);
LegacyRand::from_seed((string_hash as u64) ^ self.seed)
}
fn split_pos(&self, x: i32, y: i32, z: i32) -> impl Random {
let pos_hash = hash_block_pos(x, y, z);
LegacyRand::from_seed((pos_hash as u64) ^ self.seed)
}
}
#[cfg(test)]
mod test {
use crate::random::{Random, RandomSplitter};
use super::LegacyRand;
#[test]
fn test_next_i32() {
let mut rand = LegacyRand::from_seed(0);
let values = [
-1155484576,
-723955400,
1033096058,
-1690734402,
-1557280266,
1327362106,
-1930858313,
502539523,
-1728529858,
-938301587,
];
for value in values {
assert_eq!(rand.next_i32(), value);
}
}
#[test]
fn test_next_bounded_i32() {
let mut rand = LegacyRand::from_seed(0);
let values = [0, 13, 4, 2, 5, 8, 11, 6, 9, 14];
for value in values {
assert_eq!(rand.next_bounded_i32(0xf), value);
}
}
#[test]
fn test_next_inbetween_i32() {
let mut rand = LegacyRand::from_seed(0);
let values = [1, 5, 2, 12, 12, 6, 12, 10, 4, 3];
for value in values {
assert_eq!(rand.next_inbetween_i32(1, 12), value);
}
}
#[test]
fn test_next_inbetween_exclusive_i32() {
let mut rand = LegacyRand::from_seed(0);
let values = [1, 7, 9, 6, 7, 3, 3, 7, 3, 1];
for value in values {
assert_eq!(rand.next_inbetween_i32_exclusive(1, 12), value);
}
}
#[test]
fn test_next_f64() {
let mut rand = LegacyRand::from_seed(0);
let values = [
0.730967787376657,
0.24053641567148587,
0.6374174253501083,
0.5504370051176339,
0.5975452777972018,
0.3332183994766498,
0.3851891847407185,
0.984841540199809,
0.8791825178724801,
0.9412491794821144,
];
for value in values {
assert_eq!(rand.next_f64(), value);
}
}
#[test]
fn test_next_f32() {
let mut rand = LegacyRand::from_seed(0);
let values: [f32; 10] = [
0.73096776, 0.831441, 0.24053639, 0.6063452, 0.6374174, 0.30905056, 0.550437,
0.1170066, 0.59754527, 0.7815346,
];
for value in values {
assert_eq!(rand.next_f32(), value);
}
}
#[test]
fn test_next_i64() {
let mut rand = LegacyRand::from_seed(0);
let values: [i64; 10] = [
-4962768465676381896,
4437113781045784766,
-6688467811848818630,
-8292973307042192125,
-7423979211207825555,
6146794652083548235,
7105486291024734541,
-279624296851435688,
-2228689144322150137,
-1083761183081836303,
];
for value in values {
assert_eq!(rand.next_i64(), value);
}
}
#[test]
fn test_next_bool() {
let mut rand = LegacyRand::from_seed(0);
let values = [
true, true, false, true, true, false, true, false, true, true,
];
for value in values {
assert_eq!(rand.next_bool(), value);
}
}
#[test]
fn test_next_gaussian() {
let mut rand = LegacyRand::from_seed(0);
let values = [
0.8025330637390305,
-0.9015460884175122,
2.080920790428163,
0.7637707684364894,
0.9845745328825128,
-1.6834122587673428,
-0.027290262907887285,
0.11524570286202315,
-0.39016704137993774,
-0.643388813126449,
];
for value in values {
assert_eq!(rand.next_gaussian(), value);
}
}
#[test]
fn test_next_triangular() {
let mut rand = LegacyRand::from_seed(0);
let values = [
124.52156858525856,
104.34902101162372,
113.2163439160276,
70.01738222704547,
96.89666691951828,
107.30284075808541,
106.16817675813144,
79.11264482608078,
73.96721613927062,
81.72419521080646,
];
for value in values {
assert_eq!(rand.next_triangular(100f64, 50f64), value);
}
}
#[test]
fn test_split() {
let mut original_rand = LegacyRand::from_seed(0);
let mut new_rand = original_rand.split();
{
let splitter = new_rand.next_splitter();
let mut rand1 = splitter.split_string("TEST STRING");
assert_eq!(rand1.next_i32(), -1170413697);
let mut rand2 = splitter.split_u64(10);
assert_eq!(rand2.next_i32(), -1157793070);
let mut rand3 = splitter.split_pos(1, 11, -111);
assert_eq!(rand3.next_i32(), -1213890343);
}
assert_eq!(original_rand.next_i32(), 1033096058);
assert_eq!(new_rand.next_i32(), -888301832);
}
}

View File

@@ -1,6 +1,6 @@
pub mod xoroshiro128;
mod gaussian;
pub mod legacy_rand;
pub mod xoroshiro128;
pub trait Random {
fn from_seed(seed: u64) -> Self;
@@ -35,7 +35,7 @@ pub trait Random {
fn skip(&mut self, count: i32) {
for _ in 0..count {
self.next_i32();
self.next_i64();
}
}
@@ -51,3 +51,82 @@ pub trait RandomSplitter {
fn split_pos(&self, x: i32, y: i32, z: i32) -> impl Random;
}
fn hash_block_pos(x: i32, y: i32, z: i32) -> i64 {
let l = (x.wrapping_mul(3129871) as i64) ^ ((z as i64).wrapping_mul(116129781i64)) ^ (y as i64);
let l = l
.wrapping_mul(l)
.wrapping_mul(42317861i64)
.wrapping_add(l.wrapping_mul(11i64));
l >> 16
}
fn java_string_hash(string: &str) -> u32 {
// All byte values of latin1 align with
// the values of U+0000 - U+00FF making this code
// equivalent to both java hash implementations
let mut result = 0u32;
for char_encoding in string.encode_utf16() {
result = 31u32
.wrapping_mul(result)
.wrapping_add(char_encoding as u32);
}
result
}
#[cfg(test)]
mod tests {
use crate::random::java_string_hash;
use super::hash_block_pos;
#[test]
fn block_position_hash() {
let values: [((i32, i32, i32), i64); 8] = [
((0, 0, 0), 0),
((1, 1, 1), 60311958971344),
((4, 4, 4), 120566413180880),
((25, 25, 25), 111753446486209),
((676, 676, 676), 75210837988243),
((458329, 458329, 458329), -43764888250),
((-387008604, -387008604, -387008604), 8437923733503),
((176771161, 176771161, 176771161), 18421337580760),
];
for ((x, y, z), value) in values {
assert_eq!(hash_block_pos(x, y, z), value);
}
}
#[test]
fn test_java_string_hash() {
let values = [
("", 0),
("1", 49),
("TEST", 2571410),
("TEST1", 79713759),
("TEST0123456789", 506557463),
(
" !\"#$%&'()*+,-./0123456789:\
;<=>?@ABCDEFGHIJKLMNOPQRST\
UVWXYZ[\\]^_`abcdefghijklm\
nopqrstuvwxyz{|}~¡¢£¤¥¦§¨©\
ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄ\
ÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞ\
ßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþ",
(-1992287231i32) as u32,
),
("求同存异", 847053876),
// This might look wierd because hebrew is text is right to left
("אבְּרֵאשִׁ֖ית בָּרָ֣א אֱלֹהִ֑ים אֵ֥ת הַשָּׁמַ֖יִם וְאֵ֥ת הָאָֽרֶץ:", 1372570871),
("संस्कृत-", 1748614838),
];
for (string, value) in values {
assert_eq!(java_string_hash(string), value);
}
}
}

View File

@@ -1,4 +1,4 @@
use super::{gaussian::GaussianGenerator, Random, RandomSplitter};
use super::{gaussian::GaussianGenerator, hash_block_pos, Random, RandomSplitter};
pub struct Xoroshiro {
lo: u64,
@@ -130,12 +130,6 @@ impl Random for Xoroshiro {
fn next_gaussian(&mut self) -> f64 {
self.calculate_gaussian()
}
fn skip(&mut self, count: i32) {
for _ in 0..count {
self.next_random();
}
}
}
pub struct XoroshiroSplitter {
@@ -143,19 +137,9 @@ pub struct XoroshiroSplitter {
hi: u64,
}
fn hash_pos(x: i32, y: i32, z: i32) -> i64 {
let l =
((x.wrapping_mul(3129871)) as i64) ^ ((z as i64).wrapping_mul(116129781i64)) ^ (y as i64);
let l = l
.wrapping_mul(l)
.wrapping_mul(42317861i64)
.wrapping_add(l.wrapping_mul(11i64));
l >> 16
}
impl RandomSplitter for XoroshiroSplitter {
fn split_pos(&self, x: i32, y: i32, z: i32) -> impl Random {
let l = hash_pos(x, y, z) as u64;
let l = hash_block_pos(x, y, z) as u64;
let m = l ^ self.lo;
Xoroshiro::new(m, self.hi)
}
@@ -177,28 +161,10 @@ impl RandomSplitter for XoroshiroSplitter {
mod tests {
use crate::random::{Random, RandomSplitter};
use super::{hash_pos, mix_stafford_13, Xoroshiro};
use super::{mix_stafford_13, Xoroshiro};
// Values checked against results from the equivalent Java source
#[test]
fn block_position_hash() {
let values: [((i32, i32, i32), i64); 8] = [
((0, 0, 0), 0),
((1, 1, 1), 60311958971344),
((4, 4, 4), 120566413180880),
((25, 25, 25), 111753446486209),
((676, 676, 676), 75210837988243),
((458329, 458329, 458329), -43764888250),
((-387008604, -387008604, -387008604), 8437923733503),
((176771161, 176771161, 176771161), 18421337580760),
];
for ((x, y, z), value) in values {
assert_eq!(hash_pos(x, y, z), value);
}
}
#[test]
fn test_mix_stafford_13() {
let values: [(u64, i64); 31] = [