All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.tommyettinger.gand.utils.FlowRandom Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2023 See AUTHORS file.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package com.github.tommyettinger.gand.utils;

import com.badlogic.gdx.utils.Json;
import com.badlogic.gdx.utils.JsonValue;
import com.github.tommyettinger.gdcrux.Distributor;

import java.util.Random;

/**
 * A drop-in replacement for {@link Random} that adds few new APIs, but is faster, has better statistical quality, and
 * has a guaranteed longer minimum period (also called cycle length). This is nearly identical to FlowRandom in the
 * Juniper library, and uses the same algorithm, but only extends Random, not Juniper's EnhancedRandom class. If you
 * depend on Juniper, you lose nothing from using the EnhancedRandom classes (they also extend Random), but this class
 * doesn't have as many features as Juniper's FlowRandom. This doesn't depend on anything outside the JDK, though,
 * so it fits easily as a small addition into gand.
 * 
* This is meant to be used more like a hashing function than a random number generator, and its internal structure is * in fact based on a hashing function like the finalizer in MurmurHash3, called on a combination of two counters. In * this regard it is very close to Java 8's SplittableRandom class, and this also has streams and could, in theory, * split. A difference is that SplittableRandom only changes one counter, rather than the two here that change in * lockstep; SplittableRandom's stream is a variable like the second counter here, but doesn't change. This happens to * give FlowRandom twice as many possible "streams" at the expense of making no single stream equidistributed. However, * if all FlowRandom streams were concatenated and run through (which would take a few million years on current * hardware), the result would actually be 1-dimensionally equidistributed. *
* You should use this random number generator if you don't target GWT or TeaVM, and providing one or two long values to * {@link #setSeed(long)} or {@link #setState(long, long)} fits your needs. If you target GWT or TeaVM, you may prefer * {@link Choo32Random}, which is much faster on those targets, and takes one or four int values for its * {@link Choo32Random#setSeed(int)} or {@link Choo32Random#setState(int, int, int, int)} methods. It has a shorter * guaranteed minimum cycle length, but a much longer expected actual cycle length (2 to the * 32), but a much longer expected actual cycle length (longer than the others here, at least 2 to the 80 expected). * There is also {@link Taxon32Random}, which is in between the two on speed on GWT, but the slowest of the three on * desktop JVMs (and likely also on Android or iOS). It takes one or two int values for its seed/state, and has the same * cycle length as FlowRandom, 2 to the 64 (which generally takes years to exhaust). FlowRandom has many streams, and * the others do not. *
* FlowRandom is substantially faster at almost all operations than {@link Choo32Random} or {@link Taxon32Random} when * running on a desktop JDK. It is substantially slower at most operations than those two when run on GWT; this * disadvantage may persist on TeaVM as well. The reason for this is simple: GWT and TeaVM compile to JavaScript, which * doesn't natively support 64-bit integers, and all of FlowRandom's math is done on 64-bit integers. JavaScript does * fully support bitwise operations on 32-bit integers, and supports arithmetic on them with some caveats. *
* This class implements interfaces that allow it to be serialized by libGDX {@link Json}. If you use * Apache Fury, then {@code fury.register(FlowRandom.class);} is all you need. */ public class FlowRandom extends Random implements Json.Serializable { /** * The first state; can be any long. */ public long stateA; /** * The second state; can be any long. */ public long stateB; private static long seedFromMath () { return (long)((Math.random() - 0.5) * 0x1p52) ^ (long)((Math.random() - 0.5) * 0x1p64); } /** * Creates a new FlowRandom with a random state. */ public FlowRandom() { super(); stateA = seedFromMath(); stateB = seedFromMath(); } /** * Creates a new FlowRandom with the given seed; all {@code long} values are permitted. * The seed will be passed to {@link #setSeed(long)} to attempt to adequately distribute the seed randomly. * * @param seed any {@code long} value */ public FlowRandom(long seed) { super(seed); setSeed(seed); } /** * Creates a new FlowRandom with the given two states; all {@code long} values are permitted for * stateA and for stateB. These states are not changed during assignment. * * @param stateA any {@code long} value * @param stateB any {@code long} value */ public FlowRandom(long stateA, long stateB) { super(stateA); this.stateA = stateA; this.stateB = stateB; } /** * This initializes both states of the generator to the two given states, verbatim. * (2 to the 128) possible initial generator states can be produced here. * * @param stateA the first state * @param stateB the second state */ public void setState (long stateA, long stateB) { this.stateA = stateA; this.stateB = stateB; } /** * This initializes both states of the generator to random values based on the given seed. * (2 to the 64) possible initial generator states can be produced here. * * @param seed the initial seed; may be any long */ public void setSeed (long seed) { stateA = seed; stateB = ~seed; } public long nextLong () { long x = (stateA += 0xD1B54A32D192ED03L); long y = (stateB += 0x8CB92BA72F3D8DD7L); x = (x ^ (y << 37 | y >>> 27)) * 0x3C79AC492BA7B653L; x = (x ^ x >>> 33) * 0x1C69B3F74AC4AE35L; return x ^ x >>> 27; } public int next (int bits) { long x = (stateA += 0xD1B54A32D192ED03L); long y = (stateB += 0x8CB92BA72F3D8DD7L); x = (x ^ (y << 37 | y >>> 27)) * 0x3C79AC492BA7B653L; x = (x ^ x >>> 33) * 0x1C69B3F74AC4AE35L; return (int)(x ^ x >>> 27) >>> (32 - bits); } public int nextInt() { long x = (stateA += 0xD1B54A32D192ED03L); long y = (stateB += 0x8CB92BA72F3D8DD7L); x = (x ^ (y << 37 | y >>> 27)) * 0x3C79AC492BA7B653L; x = (x ^ x >>> 33) * 0x1C69B3F74AC4AE35L; return (int)(x ^ x >>> 27); } public int nextInt (int bound) { long x = (stateA += 0xD1B54A32D192ED03L); long y = (stateB += 0x8CB92BA72F3D8DD7L); x = (x ^ (y << 37 | y >>> 27)) * 0x3C79AC492BA7B653L; x = (x ^ x >>> 33) * 0x1C69B3F74AC4AE35L; return (int)(bound * ((x ^ x >>> 27) & 0xFFFFFFFFL) >> 32) & ~(bound >> 31); } public boolean nextBoolean() { long x = (stateA += 0xD1B54A32D192ED03L); long y = (stateB += 0x8CB92BA72F3D8DD7L); x = (x ^ (y << 37 | y >>> 27)) * 0x3C79AC492BA7B653L; x = (x ^ x >>> 33) * 0x1C69B3F74AC4AE35L; return (x ^ x >>> 27) < 0L; } public float nextFloat () { long x = (stateA += 0xD1B54A32D192ED03L); long y = (stateB += 0x8CB92BA72F3D8DD7L); x = (x ^ (y << 37 | y >>> 27)) * 0x3C79AC492BA7B653L; x = (x ^ x >>> 33) * 0x1C69B3F74AC4AE35L; return (x >>> 40) * 0x1p-24f; } public double nextDouble () { long x = (stateA += 0xD1B54A32D192ED03L); long y = (stateB += 0x8CB92BA72F3D8DD7L); x = (x ^ (y << 37 | y >>> 27)) * 0x3C79AC492BA7B653L; x = (x ^ x >>> 33) * 0x1C69B3F74AC4AE35L; return ((x ^ x >>> 27) >>> 11) * 0x1.0p-53; } public double nextGaussian() { long x = (stateA += 0xD1B54A32D192ED03L); long y = (stateB += 0x8CB92BA72F3D8DD7L); x = (x ^ (y << 37 | y >>> 27)) * 0x3C79AC492BA7B653L; x = (x ^ x >>> 33) * 0x1C69B3F74AC4AE35L; return Distributor.normal(x ^ x >>> 27); } @Override public void nextBytes (byte[] bytes) { for (int i = 0; i < bytes.length; ) { for (long r = nextLong(), n = Math.min(bytes.length - i, 8); n-- > 0; r >>>= 8) { bytes[i++] = (byte)r; } } } /** * Produces a String that holds the entire state of this FlowRandom. You can recover this state from such a * String by calling {@link #deserializeFromString(String)} on any FlowRandom, which will set that * FlowRandom's state. This does not serialize any fields inherited from {@link Random}, so the methods that * use Random's side entirely, such as the Stream methods, won't be affected if this state is loaded. * @return a String holding the current state of this FlowRandom, to be loaded by {@link #deserializeFromString(String)} */ public String serializeToString() { return stateA + "~" + stateB; } /** * Given a String produced by {@link #serializeToString()}, this sets the state of this FlowRandom to the state * stored in that String.This does not deserialize any fields inherited from {@link Random}, so the methods that * use Random's side entirely, such as the Stream methods, won't be affected by this state. * @param data a String produced by {@link #serializeToString()} * @return this FlowRandom, after its state has been loaded from the given String */ public FlowRandom deserializeFromString(String data) { if(data == null || data.length() < 3) return this; int tilde = data.indexOf('~'); stateA = Long.parseLong(data.substring(0, tilde)); stateB = Long.parseLong(data.substring(tilde+1)); return this; } public FlowRandom copy() { return new FlowRandom(stateA, stateB); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; FlowRandom random = (FlowRandom) o; if (stateA != random.stateA) return false; return stateB == random.stateB; } @Override public int hashCode() { int result = (int) (stateA ^ (stateA >>> 32)); result = 31 * result + (int) (stateB ^ (stateB >>> 32)); return result; } @Override public String toString() { return "FlowRandom{" + "stateA=" + stateA + ", stateB=" + stateB + '}'; } @Override public void write(Json json) { json.writeObjectStart("flow"); json.writeValue("a", stateA); json.writeValue("b", stateB); json.writeObjectEnd(); } @Override public void read(Json json, JsonValue jsonData) { jsonData = jsonData.get("flow"); stateA = jsonData.getLong("a"); stateB = jsonData.getLong("b"); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy