com.github.tommyettinger.random.FourWheelRandom Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of juniper Show documentation
Show all versions of juniper Show documentation
Serializable pseudo-random number generators and distributions.
/*
* Copyright (c) 2022-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.random;
/**
* A random number generator that is extremely fast on Java 16, and has a very large probable period.
* This generator is measurably faster than {@link TricycleRandom} on Java 16 but slightly slower than it on Java 8.
* It can be considered stable, like the other EnhancedRandom implementations here. Testing performed should be sufficient,
* but more can always be done; this passes at least 64TB of PractRand and 2PB of hwd without issues. The second test, hwd,
* only checks for a specific type of quality issue, but also fails if the period is exhausted; going through 2 to the 52
* bytes of data (taking over a week to do so) without exhausting the period should be a strong sign that it will have
* enough period for most tasks. While this is known to fail one test ("remortality," a check for how long it takes for the
* bitwise AND/OR of sequential results to reach all 0 bits or all 1 bits), it takes 300PB of data processed to reach
* a failure point, which is astronomically more than most apps will ever produce. {@link StrangerRandom} is probably
* stronger, but not as fast; {@link TrimRandom} is much stronger but also not quite as fast as this class (it is close).
*
* The algorithm used here has four states purely to exploit instruction-level parallelism; it isn't trying to extend the
* period of the generator beyond about 2 to the 64 (the expected bare minimum, though some cycles will likely be much
* longer). There's a complex tangle of dependencies across the four states, but it is possible to invert the generator
* given a full 256-bit state; this is vital for its period and quality. State A and state B operate like a staggered LCG
* that starts with stateD; this part is why 2 to the 64 is expected as the bare minimum period. State C and state D take
* two of the other states and combine them; C rotates state B and subtracts state D, while D simply XORs states B and C.
* This returns the state D that the previous step generated. This performs better than TricycleRandom simply because each
* of the states can be updated in parallel (using ILP) and all the updates depend on either one or two states, instead
* of one, two, or three with TricycleRandom.
*
* It is strongly recommended that you seed this with {@link #setSeed(long)} instead of
* {@link #setState(long, long, long, long)}, because if you give sequential seeds to both setSeed() and setState(), the
* former will start off random, while the latter will start off repeating the seed sequence. After about 20-40 random
* numbers generated, any correlation between similarly seeded generators will probably be completely gone, though.
*
* This implements all optional methods in EnhancedRandom except
* {@link #skip(long)}; it does implement {@link #previousLong()} without using skip().
*/
public class FourWheelRandom extends EnhancedRandom {
/**
* The first state; can be any long.
*/
protected long stateA;
/**
* The second state; can be any long.
*/
protected long stateB;
/**
* The third state; can be any long.
*/
protected long stateC;
/**
* The fourth state; can be any long. If this has just been set to some value, then the next call to
* {@link #nextLong()} will return that value as-is. Later calls will be more random.
*/
protected long stateD;
/**
* Creates a new FourWheelRandom with a random state.
*/
public FourWheelRandom () {
super();
stateA = EnhancedRandom.seedFromMath();
stateB = EnhancedRandom.seedFromMath();
stateC = EnhancedRandom.seedFromMath();
stateD = EnhancedRandom.seedFromMath();
}
/**
* Creates a new FourWheelRandom 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 FourWheelRandom (long seed) {
super(seed);
setSeed(seed);
}
/**
* Creates a new FourWheelRandom with the given four states; all {@code long} values are permitted.
* These states will be used verbatim.
*
* @param stateA any {@code long} value
* @param stateB any {@code long} value
* @param stateC any {@code long} value
* @param stateD any {@code long} value
*/
public FourWheelRandom (long stateA, long stateB, long stateC, long stateD) {
super(stateA);
this.stateA = stateA;
this.stateB = stateB;
this.stateC = stateC;
this.stateD = stateD;
}
@Override
public String getTag() {
return "FoWR";
}
/**
* This generator has 4 {@code long} states, so this returns 4.
*
* @return 4 (four)
*/
@Override
public int getStateCount () {
return 4;
}
/**
* Gets the state determined by {@code selection}, as-is. The value for selection should be
* between 0 and 3, inclusive; if it is any other value this gets state D as if 3 was given.
*
* @param selection used to select which state variable to get; generally 0, 1, 2, or 3
* @return the value of the selected state
*/
@Override
public long getSelectedState (int selection) {
switch (selection) {
case 0:
return stateA;
case 1:
return stateB;
case 2:
return stateC;
default:
return stateD;
}
}
/**
* Sets one of the states, determined by {@code selection}, to {@code value}, as-is.
* Selections 0, 1, 2, and 3 refer to states A, B, C, and D, and if the selection is anything
* else, this treats it as 3 and sets stateD.
*
* @param selection used to select which state variable to set; generally 0, 1, 2, or 3
* @param value the exact value to use for the selected state, if valid
*/
@Override
public void setSelectedState (int selection, long value) {
switch (selection) {
case 0:
stateA = value;
break;
case 1:
stateB = value;
break;
case 2:
stateC = value;
break;
default:
stateD = value;
break;
}
}
/**
* This initializes all 4 states of the generator to random values based on the given seed.
* (2 to the 64) possible initial generator states can be produced here, all with a different
* first value returned by {@link #nextLong()} (because {@code stateD} is guaranteed to be
* different for every different {@code seed}).
*
* @param seed the initial seed; may be any long
*/
@Override
public void setSeed (long seed) {
long x = (seed += 0x9E3779B97F4A7C15L);
x ^= x >>> 27;
x *= 0x3C79AC492BA7B653L;
x ^= x >>> 33;
x *= 0x1C69B3F74AC4AE35L;
stateA = x ^ x >>> 27;
x = (seed += 0x9E3779B97F4A7C15L);
x ^= x >>> 27;
x *= 0x3C79AC492BA7B653L;
x ^= x >>> 33;
x *= 0x1C69B3F74AC4AE35L;
stateB = x ^ x >>> 27;
x = (seed += 0x9E3779B97F4A7C15L);
x ^= x >>> 27;
x *= 0x3C79AC492BA7B653L;
x ^= x >>> 33;
x *= 0x1C69B3F74AC4AE35L;
stateC = x ^ x >>> 27;
x = (seed + 0x9E3779B97F4A7C15L);
x ^= x >>> 27;
x *= 0x3C79AC492BA7B653L;
x ^= x >>> 33;
x *= 0x1C69B3F74AC4AE35L;
stateD = x ^ x >>> 27;
}
public long getStateA () {
return stateA;
}
/**
* Sets the first part of the state.
*
* @param stateA can be any long
*/
public void setStateA (long stateA) {
this.stateA = stateA;
}
public long getStateB () {
return stateB;
}
/**
* Sets the second part of the state.
*
* @param stateB can be any long
*/
public void setStateB (long stateB) {
this.stateB = stateB;
}
public long getStateC () {
return stateC;
}
/**
* Sets the third part of the state.
*
* @param stateC can be any long
*/
public void setStateC (long stateC) {
this.stateC = stateC;
}
public long getStateD () {
return stateD;
}
/**
* Sets the fourth part of the state. Note that if you call {@link #nextLong()}
* immediately after this, it will return the given {@code stateD} as-is, so you
* may want to call some random generation methods (such as nextLong()) and discard
* the results after setting the state.
*
* @param stateD can be any long
*/
public void setStateD (long stateD) {
this.stateD = stateD;
}
/**
* Sets the state completely to the given four state variables.
* This is the same as calling {@link #setStateA(long)}, {@link #setStateB(long)},
* {@link #setStateC(long)}, and {@link #setStateD(long)} as a group. You may want
* to call {@link #nextLong()} a few times after setting the states like this, unless
* the value for stateD (in particular) is already adequately random; the first call
* to {@link #nextLong()}, if it is made immediately after calling this, will return {@code stateD} as-is.
*
* @param stateA the first state; can be any long
* @param stateB the second state; can be any long
* @param stateC the third state; can be any long
* @param stateD the fourth state; this will be returned as-is if the next call is to {@link #nextLong()}
*/
@Override
public void setState (long stateA, long stateB, long stateC, long stateD) {
this.stateA = stateA;
this.stateB = stateB;
this.stateC = stateC;
this.stateD = stateD;
}
@Override
public long nextLong () {
final long fa = stateA;
final long fb = stateB;
final long fc = stateC;
final long fd = stateD;
stateA = 0xD1342543DE82EF95L * fd;
stateB = fa + 0xC6BC279692B5C323L;
stateC = (fb << 47 | fb >>> 17) - fd;
stateD = fb ^ fc;
return fd;
}
@Override
public long previousLong () {
final long fa = stateA;
final long fb = stateB;
final long fd = stateD;
stateD = 0x572B5EE77A54E3BDL * fa;
final long fc = stateC + stateD;
stateA = fb - 0xC6BC279692B5C323L;
stateB = (fc >>> 47 | fc << 17);
stateC = fd ^ stateB;
return stateD;
}
@Override
public int next (int bits) {
final long fa = stateA;
final long fb = stateB;
final long fc = stateC;
final long fd = stateD;
stateA = 0xD1342543DE82EF95L * fd;
stateB = fa + 0xC6BC279692B5C323L;
stateC = (fb << 47 | fb >>> 17) - fd;
stateD = fb ^ fc;
return (int)fd >>> (32 - bits);
}
@Override
public FourWheelRandom copy () {
return new FourWheelRandom(stateA, stateB, stateC, stateD);
}
@Override
public boolean equals (Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
FourWheelRandom that = (FourWheelRandom)o;
if (stateA != that.stateA)
return false;
if (stateB != that.stateB)
return false;
if (stateC != that.stateC)
return false;
return stateD == that.stateD;
}
public String toString () {
return "FourWheelRandom{" + "stateA=" + (stateA) + "L, stateB=" + (stateB) + "L, stateC=" + (stateC) + "L, stateD=" + (stateD) + "L}";
}
}