using System; namespace Amphora.RandomComparisons { /// /// Which end of a 64-bit word a check reads. Generators differ most in their lowest bits, so every check runs on /// both. /// public enum Window { High, Low, } public static class Windows { /// /// Returns the top or bottom bits of the word as a number. /// public static ulong Take(ulong word, Window window, int bits) { if (window == Window.High) { return word >> (64 - bits); } return word & ((1UL << bits) - 1); } /// /// Returns the bit of the word at the window's outer edge, 0 or 1. /// public static int EdgeBit(ulong word, Window window) { return (int)Take(word, window, 1); } } /// /// Makes 64-bit words in the order a generator draws them. /// public abstract class WordSource { public abstract void Fill(ulong[] buffer, int start, int count); } /// /// Reads a WordSource one word at a time, so a check can ask for exactly as many as it needs. /// public sealed class WordStream { public const int BufferLength = 4096; private readonly WordSource source; private readonly ulong[] buffer = new ulong[BufferLength]; private int position = BufferLength; public WordStream(WordSource source) { this.source = source; } public ulong Next() { if (position == BufferLength) { source.Fill(buffer, 0, BufferLength); position = 0; } ulong returnMe = buffer[position]; position++; return returnMe; } } /// /// The words of one of Amphora's generators, or a control, through the same interface. /// public sealed class GeneratorWords : WordSource where GeneratorType : struct, IRandomGenerator { private GeneratorType generator; public GeneratorWords(GeneratorType generator) { this.generator = generator; } public override void Fill(ulong[] buffer, int start, int count) { for (int i = 0; i < count; i++) { buffer[start + i] = generator.NextUInt64(); } } } /// /// The words of System.Random, which has no 64-bit draw, so they are eight of its bytes. /// public sealed class SystemRandomWords : WordSource { private readonly Random random; private readonly byte[] bytes = new byte[8]; public SystemRandomWords(ulong seed) { random = new Random((int)(seed ^ (seed >> 32))); } public override void Fill(ulong[] buffer, int start, int count) { for (int i = 0; i < count; i++) { random.NextBytes(bytes); buffer[start + i] = BitConverter.ToUInt64(bytes, 0); } } } /// /// The first few words of a generator started at each of a run of adjacent seeds, one seed after another. This is /// how a seed's neighbors look when a program starts many generators from consecutive numbers. /// public sealed class AdjacentSeedWords : WordSource { private readonly Func make; private readonly int wordsPerSeed; private ulong nextSeed; public AdjacentSeedWords(Func make, ulong firstSeed, int wordsPerSeed) { this.make = make; this.wordsPerSeed = wordsPerSeed; nextSeed = firstSeed; } public override void Fill(ulong[] buffer, int start, int count) { if (count % wordsPerSeed != 0) { throw new ArgumentException("A fill must hold a whole number of seeds' words.", nameof(count)); } for (int offset = 0; offset < count; offset += wordsPerSeed) { make(nextSeed).Fill(buffer, start + offset, wordsPerSeed); nextSeed++; } } } /// /// The words of several streams taken in turn, one from each, the way parallel streams would feed one consumer. /// public sealed class InterleavedWords : WordSource { private readonly WordStream[] streams; private int next; public InterleavedWords(WordSource[] sources) { streams = new WordStream[sources.Length]; for (int i = 0; i < sources.Length; i++) { streams[i] = new WordStream(sources[i]); } } public override void Fill(ulong[] buffer, int start, int count) { for (int i = 0; i < count; i++) { buffer[start + i] = streams[next].Next(); next++; if (next == streams.Length) { next = 0; } } } } public delegate void JumpFunction(ref GeneratorType generator); }