com.github.tommyettinger.random.TricycleRandom 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;
/**
* An unusual RNG that's extremely fast on HotSpot JDK 16 and higher, and still fairly fast on earlier JDKs. It has
* three {@code long} states, which as far as I can tell can be initialized to any values without hitting any known
* problems for initialization. These states, a, b, and c, are passed around so a is determined by the previous c, b is
* determined by the previous a, b, and c, and c is determined by the previous b. This updates a with a multiplication,
* b with two XOR operations, and c with a bitwise-left-rotate by 41 and then an addition with a constant. If you want
* to alter this generator so results will be harder to reproduce, the simplest way is to change the constant added to
* c -- it can be any substantially-large odd number, though preferably one with a {@link Long#bitCount(long)} of 32.
*
* Other useful traits of this generator are that it almost certainly has a longer period than you need for a game, and
* that all values are permitted for the states (that we know of). It is possible that some initialization will put the
* generator in a shorter-period subcycle, but the odds of this being a subcycle that's small enough to run out of
* period during a game are effectively 0. It's also possible that the generator only has one cycle of length 2 to the
* 192, though this doesn't seem at all likely. TricycleRandom implements all optional methods in EnhancedRandom except
* {@link #skip(long)}; it does implement {@link #previousLong()} without using skip().
*
* This is closely related to Mark Overton's Romu generators, specifically
* RomuTrio, but this gets a little faster than RomuTrio in some situations by using just one less rotation. Unlike
* RomuTrio, there isn't a clear problematic state with a period of 1 (which happens when all of its states are 0).
* This is often slightly slower than RomuTrio, but only by a tiny margin. This generator isn't an ARX generator any
* more (a previous version was), but its performance isn't much different (like RomuTrio, the one multiplication this
* uses pipelines very well, so it doesn't slow down the generator).
*
* TricycleRandom passes 64TB of testing with PractRand, which uses a suite of tests to look for a variety of potential
* problems. It has also passed a whopping 4 petabytes of testing with hwd, can test a much larger amount of data but
* only runs a single test. The test hwd uses looks for long-range bit-dependencies, where one bit's state earlier in
* the generated numbers determines the state of a future bit with a higher-than-reasonable likelihood. All the
* generators here are considered stable.
*
* It is strongly recommended that you seed this with {@link #setSeed(long)} instead of
* {@link #setState(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.
*/
public class TricycleRandom extends EnhancedRandom {
/**
* The first 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 stateA;
/**
* The second state; can be any long.
*/
protected long stateB;
/**
* The third state; can be any long.
*/
protected long stateC;
/**
* Creates a new TricycleRandom with a random state.
*/
public TricycleRandom () {
super();
stateA = EnhancedRandom.seedFromMath();
stateB = EnhancedRandom.seedFromMath();
stateC = EnhancedRandom.seedFromMath();
}
/**
* Creates a new TricycleRandom 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 TricycleRandom (long seed) {
super(seed);
setSeed(seed);
}
/**
* Creates a new TricycleRandom with the given three 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
*/
public TricycleRandom (long stateA, long stateB, long stateC) {
super(stateA);
this.stateA = stateA;
this.stateB = stateB;
this.stateC = stateC;
}
@Override
public String getTag() {
return "TriR";
}
/**
* This generator has 3 {@code long} states, so this returns 3.
*
* @return 3 (three)
*/
@Override
public int getStateCount () {
return 3;
}
/**
* Gets the state determined by {@code selection}, as-is.
*
* @param selection used to select which state variable to get; generally 0, 1, or 2
* @return the value of the selected state
*/
@Override
public long getSelectedState (int selection) {
switch (selection & 3) {
case 0:
return stateA;
case 1:
return stateB;
default:
return stateC;
}
}
/**
* Sets one of the states, determined by {@code selection}, to {@code value}, as-is.
* Selections 0, 1, and 2 refer to states A, B, and C, and if the selection is anything
* else, this treats it as 2 and sets stateC.
*
* @param selection used to select which state variable to set; generally 0, 1, or 2
* @param value the exact value to use for the selected state, if valid
*/
@Override
public void setSelectedState (int selection, long value) {
switch (selection & 3) {
case 0:
stateA = value;
break;
case 1:
stateB = value;
break;
default:
stateC = value;
break;
}
}
/**
* This initializes all 3 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 stateA} 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;
}
public long getStateA () {
return stateA;
}
/**
* Sets the first part of the state. Note that if you call {@link #nextLong()}
* immediately after this, it will return the given {@code stateA} as-is, so you
* may want to call some random generation methods (such as nextLong()) and discard
* the results after setting 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;
}
/**
* Sets the state completely to the given three state variables.
* This is the same as calling {@link #setStateA(long)}, {@link #setStateB(long)},
* and {@link #setStateC(long)} as a group. You may want to call {@link #nextLong()}
* a few times after setting the states like this, unless the value for stateA (in
* particular) is already adequately random; the first call to {@link #nextLong()},
* if it is made immediately after calling this, will return {@code stateA} as-is.
*
* @param stateA the first state; this will be returned as-is if the next call is to {@link #nextLong()}
* @param stateB the second state; can be any long
* @param stateC the third state; can be any long
*/
@Override
public void setState (long stateA, long stateB, long stateC) {
this.stateA = stateA;
this.stateB = stateB;
this.stateC = stateC;
}
@Override
public long nextLong () {
final long fa = stateA;
final long fb = stateB;
final long fc = stateC;
stateA = 0xD1342543DE82EF95L * fc;
stateB = fa ^ fb ^ fc;
stateC = (fb << 41 | fb >>> 23) + 0xC6BC279692B5C323L;
return fa;
}
@Override
public long previousLong () {
final long fa = stateA;
final long fb = stateB;
final long fc = stateC - 0xC6BC279692B5C323L;
stateC = 0x572B5EE77A54E3BDL * fa;
stateB = (fc >>> 41 | fc << 23);
stateA = fb ^ stateB ^ stateC;
return stateA;
}
@Override
public int next (int bits) {
final long fa = stateA;
final long fb = stateB;
final long fc = stateC;
stateA = 0xD1342543DE82EF95L * fc;
stateB = fa ^ fb ^ fc;
stateC = (fb << 41 | fb >>> 23) + 0xC6BC279692B5C323L;
return (int)fa >>> (32 - bits);
}
@Override
public TricycleRandom copy () {
return new TricycleRandom(stateA, stateB, stateC);
}
@Override
public boolean equals (Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
TricycleRandom that = (TricycleRandom)o;
if (stateA != that.stateA)
return false;
if (stateB != that.stateB)
return false;
return stateC == that.stateC;
}
public String toString () {
return "TricycleRandom{" + "stateA=" + (stateA) + "L, stateB=" + (stateB) + "L, stateC=" + (stateC) + "L}";
}
}