001package squidpony.squidmath;
002
003import java.io.Serializable;
004
005/**
006 * Somewhat experimental RNG that can be configured to smoothly transition between producing mostly values in the
007 * center of its range, to producing more values at or near the extremes, as well as favoring high or low results.
008 * The probability distribution is... unusual, with lumps that rise or fall based on centrality.
009 * <br>
010 * Even though this is experimental, it's still usable. Mostly the useful parts of this relate to changing centrality,
011 * making results occur more or less frequently in the center of the output range. You can also change the "favor" to
012 * bias results towards higher or lower parts of the same output range, though if favor is non-zero it may have
013 * counterintuitive results for {@link #nextLong()}.
014 * <br>
015 * Internally, this acts as a {@link TangleRNG}, which is a fairly solid, very fast algorithm, and uses its results two
016 * at a time to give to an atan2 calculation (specifically, {@link NumberTools#atan2_(float, float)} or
017 * {@link NumberTools#atan2_(double, double)}. These particular approximations of atan2 range from 0.0 to 1.0 instead of
018 * -pi to pi. This means atan2 inputs with positive x and small y are likely to return values near 1.0 or near 0.0, but
019 * not between 0.25 and 0.75. The opposite is true for inputs with negative x and small y; that is likely to be near 0.5
020 * and can't be between 0.0 and 0.25 or between 0.75 and 1.0. TweakRNG uses this property to implement centrality,
021 * changing the inputs to its internal atan2 usage so x is positive when centrality is positive, or negative when
022 * centrality is negative. Likewise, favor is implemented by changing y, though reversed; positive favor makes the atan2
023 * calculation adjusted with negative y, making it more likely to be between 0.5 and 1.0, while negative favor pushes it
024 * back to between 0.0 and 0.5.
025 * <br>
026 * <a href="https://i.imgur.com/VCvtlSc.gifv">Here's an animation of the distribution graph changing.</a>
027 * <br>
028 * Created by Tommy Ettinger on 10/6/2019.
029 */
030public class TweakRNG extends AbstractRNG implements Serializable {
031
032    private static final long serialVersionUID = 1L;
033
034    private long stateA, stateB, centrality, favor;
035    public TweakRNG() {
036        this((long) ((Math.random() - 0.5) * 0x10000000000000L)
037                        ^ (long) (((Math.random() - 0.5) * 2.0) * 0x8000000000000000L),
038                (long) ((Math.random() - 0.5) * 0x10000000000000L)
039                        ^ (long) (((Math.random() - 0.5) * 2.0) * 0x8000000000000000L),
040                0L);
041    }
042
043    public TweakRNG(long seed) {
044        this((seed = ((seed = (((seed * 0x632BE59BD9B4E019L) ^ 0x9E3779B97F4A7C15L) * 0xC6BC279692B5CC83L)) ^ seed >>> 27) * 0xAEF17502108EF2D9L) ^ seed >>> 25, 
045                ((seed = ((seed = (((seed * 0x632BE59BD9B4E019L) ^ 0x9E3779B97F4A7C15L) * 0xC6BC279692B5CC83L)) ^ seed >>> 27) * 0xAEF17502108EF2D9L) ^ seed >>> 25),
046                0L);
047    }
048
049    public TweakRNG(final long seedA, final long seedB, final long centrality) {
050        stateA = seedA;
051        stateB = seedB | 1L;
052        this.centrality = centrality % 0x10000L;
053        this.favor = 0L;
054    }
055
056    public TweakRNG(final long seedA, final long seedB, final long centrality, final long favor) {
057        stateA = seedA;
058        stateB = seedB | 1L;
059        this.centrality = centrality % 0x10000L;
060        this.favor = favor % 0x10000L;
061    }
062
063    @Override
064    public int next(int bits) {
065        return (int)(nextLong() >>> 64 - bits);
066    }
067
068    @Override
069    public int nextInt() {
070        return (int) (nextLong() >>> 32);
071    }
072
073    @Override
074    public long nextLong() {
075        final long r = internalLong(), s = internalLong();
076        return (long) (NumberTools.atan2_(((r & 0xFF) - (r >>> 8 & 0xFF)) * ((r >>> 16 & 0xFF) - (r >>> 24 & 0xFF)) - (double)favor,
077                ((r >>> 32 & 0xFF) - (r >>> 40 & 0xFF)) * ((r >>> 48 & 0xFF) - (r >>> 56 & 0xFF)) + centrality) * 0x6000000000000000L)
078                + (s & 0x1FFFFFFFFFFFFFFFL) << 1 ^ (s >>> 63);
079    }
080
081    @Override
082    public boolean nextBoolean() {
083        return nextLong() < 0;
084    }
085    
086    @Override
087    public double nextDouble() {
088        final long r = internalLong();
089        return NumberTools.atan2_(((r & 0xFF) - (r >>> 8 & 0xFF)) * ((r >>> 16 & 0xFF) - (r >>> 24 & 0xFF)) - favor,
090                ((r >>> 32 & 0xFF) - (r >>> 40 & 0xFF)) * ((r >>> 48 & 0xFF) - (r >>> 56 & 0xFF)) + (double)centrality) * 0.75
091                + (internalLong() & 0xfffffffffffffL) * 0x1p-54;
092    }
093
094    @Override
095    public float nextFloat() {
096        final long r = internalLong();
097        return NumberTools.atan2_(((r & 0xFF) - (r >>> 8 & 0xFF)) * ((r >>> 16 & 0xFF) - (r >>> 24 & 0xFF)) - favor,
098                ((r >>> 32 & 0xFF) - (r >>> 40 & 0xFF)) * ((r >>> 48 & 0xFF) - (r >>> 56 & 0xFF)) + centrality) * 0.75f
099                + (internalLong() & 0x7fffffL) * 0x1p-25f;
100    }
101    
102    @Override
103    public TweakRNG copy() {
104        return this;
105    }
106
107    @Override
108    public Serializable toSerializable() {
109        return this;
110    }
111
112    public long getStateA() {
113        return stateA;
114    }
115
116    public void setStateA(long stateA) {
117        this.stateA = stateA;
118    }
119
120    public long getStateB() {
121        return stateB;
122    }
123
124    public void setStateB(long stateB) {
125        this.stateB = stateB | 1L;
126    }
127
128    public long getCentrality() {
129        return centrality;
130    }
131
132    /**
133     * Adjusts the central bias of this TweakRNG, often to positive numbers (which bias toward the center of the range),
134     * but also often to negative numbers (which bias toward extreme values, though still within the range).
135     * @param centrality should be between -65535 and 65535; positive values bias toward the center of the range
136     */
137    public void setCentrality(long centrality) {
138        this.centrality = centrality % 65536L;
139    }
140
141
142    public long getFavor() {
143        return favor;
144    }
145
146    /**
147     * Adjusts the value bias of this TweakRNG, often to positive numbers (which bias toward higher results), but also
148     * often to negative numbers (which bias toward lower results). All results will still be in their normal range, but
149     * will change how often high or low values occur. Unusual results will occur if favor is non-zero and you get a
150     * long with {@link #nextLong()}; in that case, the values are treated as higher when unsigned, so positive favor
151     * makes both high positive values and all negative values more common. Doubles and floats will behave normally.
152     * @param favor should be between -65535 and 65535; positive values bias toward higher (unsigned for longs) results
153     */
154    public void setFavor(long favor) {
155        this.favor = favor % 65536L;
156    }
157
158    /**
159     *{@link TangleRNG}'s algorithm; not all longs will be returned by any individual generator, but all generators as a
160     * whole will return all longs with equal likelihood.
161     * @return a random long in the full range; each state is advanced by 1 step.
162     */
163    private long internalLong()
164    {
165        final long s = (stateA += 0xC6BC279692B5C323L);
166        final long z = (s ^ s >>> 31) * (stateB += 0x9E3779B97F4A7C16L);
167        return z ^ z >>> 26 ^ z >>> 6;
168    }
169}