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);
}