< Summary

Information
Class: FixedMathSharp.Random.DeterministicRandom
Assembly: FixedMathSharp
File(s): /home/runner/work/FixedMathSharp/FixedMathSharp/src/FixedMathSharp/Random/DeterministicRandom.cs
Line coverage
100%
Covered lines: 57
Uncovered lines: 0
Coverable lines: 57
Total lines: 218
Line coverage: 100%
Branch coverage
100%
Covered branches: 16
Total branches: 16
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

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

File(s)

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

#LineLine coverage
 1//=======================================================================
 2// DeterministicRandom.cs
 3//=======================================================================
 4// MIT License, Copyright (c) 2024–present David Oravsky (mrdav30)
 5// See LICENSE file in the project root for full license information.
 6//=======================================================================
 7
 8using System;
 9using System.Runtime.CompilerServices;
 10
 11namespace FixedMathSharp.Random
 12{
 13    /// <summary>
 14    /// Fast, seedable, deterministic RNG suitable for lockstep sims and map gen.
 15    /// Uses xoroshiro128++ with splitmix64 seeding. No allocations, no time/GUID.
 16    /// </summary>
 17    public struct DeterministicRandom
 18    {
 19        // xoroshiro128++ state
 20        private ulong _s0;
 21        private ulong _s1;
 22
 23        #region Construction / Seeding
 24
 25        /// <summary>
 26        /// Initializes a new instance of the DeterministicRandom class using the specified seed value.
 27        /// </summary>
 28        /// <remarks>
 29        /// This constructor expands the provided seed into the internal state required for deterministic random number 
 30        /// The generated sequence is fully determined by the seed value.
 31        /// </remarks>
 32        /// <param name="seed">
 33        /// The initial seed value used to generate the internal state.
 34        /// Using the same seed will produce the same sequence of random numbers.
 35        /// </param>
 36        public DeterministicRandom(ulong seed)
 37        {
 38            // Expand a single seed into two 64-bit state words via splitmix64.
 2739            _s0 = SplitMix64(ref seed);
 2740            _s1 = SplitMix64(ref seed);
 2741        }
 42
 43        /// <summary>
 44        /// Create a stream deterministically
 45        /// Derived from (worldSeed, featureKey[,index]).
 46        /// </summary>
 47        public static DeterministicRandom FromWorldFeature(ulong worldSeed, ulong featureKey, ulong index = 0)
 48        {
 49            // Simple reversible mix (swap for a stronger mix if required).
 450            ulong seed = Mix64(worldSeed, featureKey);
 451            seed = Mix64(seed, index);
 452            return new DeterministicRandom(seed);
 53        }
 54
 55        #endregion
 56
 57        #region Core PRNG
 58
 59        /// <summary>
 60        /// xoroshiro128++ next 64 bits.
 61        /// </summary>
 62        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 63        public ulong NextU64()
 64        {
 9557465            ulong s0 = _s0, s1 = _s1;
 4778766            ulong result = RotL(s0 + s1, 17) + s0;
 67
 4778768            s1 ^= s0;
 4778769            _s0 = RotL(s0, 49) ^ s1 ^ (s1 << 21); // a,b
 4778770            _s1 = RotL(s1, 28);                   // c
 71
 4778772            return result;
 73        }
 74
 75        /// <summary>
 76        /// Next non-negative Int32 in [0, int.MaxValue].
 77        /// </summary>
 78        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 79        public int Next()
 80        {
 81            // Take high bits for better quality; mask to 31 bits non-negative.
 100482            return (int)(NextU64() >> 33);
 83        }
 84
 85        /// <summary>
 86        /// Unbiased int in [0, maxExclusive).
 87        /// </summary>
 88        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 89        public int Next(int maxExclusive)
 90        {
 3021491            return maxExclusive <= 0
 3021492                ? throw new ArgumentOutOfRangeException(nameof(maxExclusive))
 3021493                : (int)NextBounded((uint)maxExclusive);
 94        }
 95
 96        /// <summary>
 97        /// Unbiased int in [min, maxExclusive).
 98        /// </summary>
 99        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 100        public int Next(int minInclusive, int maxExclusive)
 101        {
 4101102            if (minInclusive >= maxExclusive)
 3103                throw new ArgumentException("min >= max");
 104
 4098105            uint range = (uint)(maxExclusive - minInclusive);
 4098106            return minInclusive + (int)NextBounded(range);
 107        }
 108
 109        /// <summary>
 110        /// Fill span with random bytes.
 111        /// </summary>
 112        public void NextBytes(Span<byte> buffer)
 113        {
 13114            int i = 0;
 34115            while (i + 8 <= buffer.Length)
 116            {
 21117                ulong v = NextU64();
 21118                Unsafe.WriteUnaligned(ref buffer[i], v);
 21119                i += 8;
 120            }
 13121            if (i < buffer.Length)
 122            {
 10123                ulong v = NextU64();
 47124                while (i < buffer.Length)
 125                {
 37126                    buffer[i++] = (byte)v;
 37127                    v >>= 8;
 128                }
 129            }
 13130        }
 131
 132        #endregion
 133
 134        #region Fixed64 helpers
 135
 136        /// <summary>
 137        /// Random Fixed64 in [0,1).
 138        /// </summary>
 139        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 140        public Fixed64 NextFixed6401()
 141        {
 142            // Produce a raw value in [0, One.m_rawValue)
 4096143            ulong rawOne = (ulong)Fixed64.One.m_rawValue;
 4096144            ulong r = NextBounded(rawOne);
 4096145            return Fixed64.FromRaw((long)r);
 146        }
 147
 148        /// <summary>
 149        /// Random Fixed64 in [0, maxExclusive).
 150        /// </summary>
 151        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 152        public Fixed64 NextFixed64(Fixed64 maxExclusive)
 153        {
 4098154            if (maxExclusive <= Fixed64.Zero)
 2155                throw new ArgumentOutOfRangeException(nameof(maxExclusive), "max must be > 0");
 156
 4096157            ulong rawMax = (ulong)maxExclusive.m_rawValue;
 4096158            ulong r = NextBounded(rawMax);
 4096159            return Fixed64.FromRaw((long)r);
 160        }
 161
 162        /// <summary>
 163        /// Random Fixed64 in [minInclusive, maxExclusive).
 164        /// </summary>
 165        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 166        public Fixed64 NextFixed64(Fixed64 minInclusive, Fixed64 maxExclusive)
 167        {
 4101168            if (minInclusive >= maxExclusive)
 2169                throw new ArgumentException("min >= max");
 170
 4099171            ulong span = (ulong)(maxExclusive.m_rawValue - minInclusive.m_rawValue);
 4099172            ulong r = NextBounded(span);
 4099173            return Fixed64.FromRaw((long)r + minInclusive.m_rawValue);
 174        }
 175
 176        #endregion
 177
 178        #region Internals: unbiased range, splitmix64, mixing, rotations
 179
 180        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 181        private ulong NextBounded(ulong bound)
 182        {
 183            // Rejection to avoid modulo bias.
 184            // threshold = 2^64 % bound, but expressed as (-bound) % bound
 46601185            ulong threshold = unchecked((ulong)-(long)bound) % bound;
 186            while (true)
 187            {
 46602188                ulong r = NextU64();
 46602189                if (r >= threshold)
 46601190                    return r % bound;
 191            }
 192        }
 193
 194        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 143361195        private static ulong RotL(ulong x, int k) => (x << k) | (x >> (64 - k));
 196
 197        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 198        private static ulong SplitMix64(ref ulong state)
 199        {
 54200            ulong z = (state += 0x9E3779B97F4A7C15UL);
 54201            z = (z ^ (z >> 30)) * 0xBF58476D1CE4E5B9UL;
 54202            z = (z ^ (z >> 27)) * 0x94D049BB133111EBUL;
 54203            return z ^ (z >> 31);
 204        }
 205
 206        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 207        private static ulong Mix64(ulong a, ulong b)
 208        {
 209            // Simple reversible mix (variant of splitmix finalizer).
 8210            ulong x = a ^ (b + 0x9E3779B97F4A7C15UL);
 8211            x = (x ^ (x >> 30)) * 0xBF58476D1CE4E5B9UL;
 8212            x = (x ^ (x >> 27)) * 0x94D049BB133111EBUL;
 8213            return x ^ (x >> 31);
 214        }
 215
 216        #endregion
 217    }
 218}