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}