< Summary

Information
Class: FixedMathSharp.Utility.DeterministicRandom
Assembly: FixedMathSharp
File(s): /home/runner/work/FixedMathSharp/FixedMathSharp/src/FixedMathSharp/Random/DeterministicRandom.cs
Line coverage
97%
Covered lines: 93
Uncovered lines: 2
Coverable lines: 95
Total lines: 222
Line coverage: 97.8%
Branch coverage
85%
Covered branches: 17
Total branches: 20
Branch coverage: 85%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)50%4483.33%
FromWorldFeature(...)100%11100%
NextU64()100%11100%
Next()100%11100%
Next(...)100%22100%
Next(...)100%22100%
NextDouble()100%11100%
NextBytes(...)100%66100%
NextFixed6401()100%11100%
NextFixed64(...)100%22100%
NextFixed64(...)100%22100%
NextBounded(...)50%2288.88%
RotL(...)100%11100%
SplitMix64(...)100%11100%
Mix64(...)100%11100%

File(s)

/home/runner/work/FixedMathSharp/FixedMathSharp/src/FixedMathSharp/Random/DeterministicRandom.cs

#LineLine coverage
 1using System;
 2using System.Runtime.CompilerServices;
 3
 4namespace FixedMathSharp.Utility
 5{
 6    /// <summary>
 7    /// Fast, seedable, deterministic RNG suitable for lockstep sims and map gen.
 8    /// Uses xoroshiro128++ with splitmix64 seeding. No allocations, no time/GUID.
 9    /// </summary>
 10    public struct DeterministicRandom
 11    {
 12        // xoroshiro128++ state
 13        private ulong _s0;
 14        private ulong _s1;
 15
 16        #region Construction / Seeding
 17
 18        /// <summary>
 19        /// Initializes a new instance of the DeterministicRandom class using the specified seed value.
 20        /// </summary>
 21        /// <remarks>
 22        /// This constructor expands the provided seed into the internal state required for deterministic random number 
 23        /// The generated sequence is fully determined by the seed value.
 24        /// </remarks>
 25        /// <param name="seed">
 26        /// The initial seed value used to generate the internal state.
 27        /// Using the same seed will produce the same sequence of random numbers.
 28        /// </param>
 29        public DeterministicRandom(ulong seed)
 2630        {
 31            // Expand a single seed into two 64-bit state words via splitmix64.
 2632            _s0 = SplitMix64(ref seed);
 2633            _s1 = SplitMix64(ref seed);
 34
 35            // xoroshiro requires non-zero state; repair pathological seed.
 2636            if (_s0 == 0UL && _s1 == 0UL)
 037                _s1 = 0x9E3779B97F4A7C15UL;
 2638        }
 39
 40        /// <summary>
 41        /// Create a stream deterministically
 42        /// Derived from (worldSeed, featureKey[,index]).
 43        /// </summary>
 44        public static DeterministicRandom FromWorldFeature(ulong worldSeed, ulong featureKey, ulong index = 0)
 445        {
 46            // Simple reversible mix (swap for a stronger mix if required).
 447            ulong seed = Mix64(worldSeed, featureKey);
 448            seed = Mix64(seed, index);
 449            return new DeterministicRandom(seed);
 450        }
 51
 52        #endregion
 53
 54        #region Core PRNG
 55
 56        /// <summary>
 57        /// xoroshiro128++ next 64 bits.
 58        /// </summary>
 59        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 60        public ulong NextU64()
 5188361        {
 10376662            ulong s0 = _s0, s1 = _s1;
 5188363            ulong result = RotL(s0 + s1, 17) + s0;
 64
 5188365            s1 ^= s0;
 5188366            _s0 = RotL(s0, 49) ^ s1 ^ (s1 << 21); // a,b
 5188367            _s1 = RotL(s1, 28);                   // c
 68
 5188369            return result;
 5188370        }
 71
 72        /// <summary>
 73        /// Next non-negative Int32 in [0, int.MaxValue].
 74        /// </summary>
 75        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 76        public int Next()
 100477        {
 78            // Take high bits for better quality; mask to 31 bits non-negative.
 100479            return (int)(NextU64() >> 33);
 100480        }
 81
 82        /// <summary>
 83        /// Unbiased int in [0, maxExclusive).
 84        /// </summary>
 85        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 86        public int Next(int maxExclusive)
 3021487        {
 3021488            return maxExclusive <= 0
 3021489                ? throw new ArgumentOutOfRangeException(nameof(maxExclusive))
 3021490                : (int)NextBounded((uint)maxExclusive);
 3021291        }
 92
 93        /// <summary>
 94        /// Unbiased int in [min, maxExclusive).
 95        /// </summary>
 96        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 97        public int Next(int minInclusive, int maxExclusive)
 410198        {
 410199            if (minInclusive >= maxExclusive)
 3100                throw new ArgumentException("min >= max");
 4098101            uint range = (uint)(maxExclusive - minInclusive);
 4098102            return minInclusive + (int)NextBounded(range);
 4098103        }
 104
 105        /// <summary>
 106        /// Double in [0,1).
 107        /// </summary>
 108        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 109        public double NextDouble()
 4100110        {
 111            // 53 random bits -> [0,1)
 4100112            return (NextU64() >> 11) * (1.0 / (1UL << 53));
 4100113        }
 114
 115        /// <summary>
 116        /// Fill span with random bytes.
 117        /// </summary>
 118        public void NextBytes(Span<byte> buffer)
 13119        {
 13120            int i = 0;
 34121            while (i + 8 <= buffer.Length)
 21122            {
 21123                ulong v = NextU64();
 21124                Unsafe.WriteUnaligned(ref buffer[i], v);
 21125                i += 8;
 21126            }
 13127            if (i < buffer.Length)
 10128            {
 10129                ulong v = NextU64();
 47130                while (i < buffer.Length)
 37131                {
 37132                    buffer[i++] = (byte)v;
 37133                    v >>= 8;
 37134                }
 10135            }
 13136        }
 137
 138        #endregion
 139
 140        #region Fixed64 helpers
 141
 142        /// <summary>
 143        /// Random Fixed64 in [0,1).
 144        /// </summary>
 145        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 146        public Fixed64 NextFixed6401()
 4096147        {
 148            // Produce a raw value in [0, One.m_rawValue)
 4096149            ulong rawOne = (ulong)Fixed64.One.m_rawValue;
 4096150            ulong r = NextBounded(rawOne);
 4096151            return Fixed64.FromRaw((long)r);
 4096152        }
 153
 154        /// <summary>
 155        /// Random Fixed64 in [0, maxExclusive).
 156        /// </summary>
 157        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 158        public Fixed64 NextFixed64(Fixed64 maxExclusive)
 4098159        {
 4098160            if (maxExclusive <= Fixed64.Zero)
 2161                throw new ArgumentOutOfRangeException(nameof(maxExclusive), "max must be > 0");
 4096162            ulong rawMax = (ulong)maxExclusive.m_rawValue;
 4096163            ulong r = NextBounded(rawMax);
 4096164            return Fixed64.FromRaw((long)r);
 4096165        }
 166
 167        /// <summary>
 168        /// Random Fixed64 in [minInclusive, maxExclusive).
 169        /// </summary>
 170        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 171        public Fixed64 NextFixed64(Fixed64 minInclusive, Fixed64 maxExclusive)
 4100172        {
 4100173            if (minInclusive >= maxExclusive)
 2174                throw new ArgumentException("min >= max");
 4098175            ulong span = (ulong)(maxExclusive.m_rawValue - minInclusive.m_rawValue);
 4098176            ulong r = NextBounded(span);
 4098177            return Fixed64.FromRaw((long)r + minInclusive.m_rawValue);
 4098178        }
 179
 180        #endregion
 181
 182        #region Internals: unbiased range, splitmix64, mixing, rotations
 183
 184        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 185        private ulong NextBounded(ulong bound)
 46600186        {
 187            // Rejection to avoid modulo bias.
 188            // threshold = 2^64 % bound, but expressed as (-bound) % bound
 46600189            ulong threshold = unchecked((ulong)-(long)bound) % bound;
 46600190            while (true)
 46600191            {
 46600192                ulong r = NextU64();
 46600193                if (r >= threshold)
 46600194                    return r % bound;
 0195            }
 46600196        }
 197
 198        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 155649199        private static ulong RotL(ulong x, int k) => (x << k) | (x >> (64 - k));
 200
 201        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 202        private static ulong SplitMix64(ref ulong state)
 52203        {
 52204            ulong z = (state += 0x9E3779B97F4A7C15UL);
 52205            z = (z ^ (z >> 30)) * 0xBF58476D1CE4E5B9UL;
 52206            z = (z ^ (z >> 27)) * 0x94D049BB133111EBUL;
 52207            return z ^ (z >> 31);
 52208        }
 209
 210        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 211        private static ulong Mix64(ulong a, ulong b)
 8212        {
 213            // Simple reversible mix (variant of splitmix finalizer).
 8214            ulong x = a ^ (b + 0x9E3779B97F4A7C15UL);
 8215            x = (x ^ (x >> 30)) * 0xBF58476D1CE4E5B9UL;
 8216            x = (x ^ (x >> 27)) * 0x94D049BB133111EBUL;
 8217            return x ^ (x >> 31);
 8218        }
 219
 220        #endregion
 221    }
 222}