using System; namespace Amphora.RandomComparisons { /// /// Finds the length of the shortest linear feedback shift register that makes a bit sequence, by the /// Berlekamp-Massey algorithm over GF(2), with the register and the recent bits packed into words. /// public sealed class BerlekampMassey { private readonly int words; private readonly ulong[] connection; private readonly ulong[] previous; private readonly ulong[] saved; private readonly ulong[] recent; public BerlekampMassey(int maximumLength) { words = (maximumLength + 63) / 64 + 1; connection = new ulong[words]; previous = new ulong[words]; saved = new ulong[words]; recent = new ulong[words]; } private static int ParityOf(ulong value) { value ^= value >> 32; value ^= value >> 16; value ^= value >> 8; value ^= value >> 4; value ^= value >> 2; value ^= value >> 1; return (int)(value & 1); } private void PushRecent(byte bit) { for (int i = words - 1; i > 0; i--) { recent[i] = (recent[i] << 1) | (recent[i - 1] >> 63); } recent[0] = (recent[0] << 1) | bit; } private void XorPreviousShifted(int shift) { int wordShift = shift >> 6; int bitShift = shift & 63; for (int i = words - 1; i >= wordShift; i--) { ulong value = previous[i - wordShift] << bitShift; if (bitShift != 0 && i - wordShift - 1 >= 0) { value |= previous[i - wordShift - 1] >> (64 - bitShift); } connection[i] ^= value; } } /// /// Returns the linear complexity of the first entries, each 0 or 1. /// public int Complexity(byte[] bits, int length) { Array.Clear(connection, 0, words); Array.Clear(previous, 0, words); Array.Clear(recent, 0, words); connection[0] = 1; previous[0] = 1; int complexity = 0; int lastChange = -1; for (int n = 0; n < length; n++) { PushRecent(bits[n]); ulong overlap = 0; int limit = complexity / 64 + 1; for (int i = 0; i < limit; i++) { overlap ^= connection[i] & recent[i]; } if (ParityOf(overlap) == 0) { continue; } Array.Copy(connection, saved, words); XorPreviousShifted(n - lastChange); if (2 * complexity <= n) { complexity = n + 1 - complexity; lastChange = n; Array.Copy(saved, previous, words); } } return complexity; } } /// /// Takes one bit position of consecutive draws in blocks and finds each block's linear complexity. A random block /// of M bits needs a register about M / 2 long, with a known spread. A generator that is linear in that bit needs /// one no longer than its state. /// public static class LinearComplexity { /// /// The chance of each of seven ranges of the complexity's distance from its mean, from the NIST statistical /// test suite. /// public static readonly double[] Probabilities = { 1.0 / 96, 1.0 / 32, 1.0 / 8, 1.0 / 2, 1.0 / 4, 1.0 / 16, 1.0 / 48, }; /// /// Returns which of the seven ranges a complexity falls in, for a block of an even length. /// public static int Category(int complexity, int blockLength) { double mean = blockLength / 2.0 + 4.0 / 9; double distance = complexity - mean + 2.0 / 9; if (distance <= -2.5) { return 0; } if (distance <= -1.5) { return 1; } if (distance <= -0.5) { return 2; } if (distance <= 0.5) { return 3; } if (distance <= 1.5) { return 4; } return distance <= 2.5 ? 5 : 6; } public static Outcome Run(WordStream stream, Window window, int blockLength, int blocks) { var machine = new BerlekampMassey(blockLength); var bits = new byte[blockLength]; var observed = new double[Probabilities.Length]; double totalComplexity = 0; for (int block = 0; block < blocks; block++) { for (int i = 0; i < blockLength; i++) { bits[i] = (byte)Windows.EdgeBit(stream.Next(), window); } int complexity = machine.Complexity(bits, blockLength); totalComplexity += complexity; observed[Category(complexity, blockLength)]++; } var expected = new double[Probabilities.Length]; for (int i = 0; i < expected.Length; i++) { expected[i] = Probabilities[i] * blocks; } double degrees = Stats.ChiSquareOfCategories(observed, expected, 5, out double statistic); return new Outcome(Stats.ChiSquareTwoSided(statistic, degrees), "bit " + (window == Window.High ? 63 : 0) + ", blocks of " + blockLength + ", mean complexity " + (totalComplexity / blocks).ToString("0.0") + " against about " + (blockLength / 2.0).ToString("0.0")); } } }