Added xorshift generator and longest runs test

This commit is contained in:
Asher 2025-06-29 23:41:44 +01:00
parent 85196b5d84
commit 7719660bac
17 changed files with 370 additions and 251 deletions

16
rng/rng/.vscode/c_cpp_properties.json vendored Normal file
View File

@ -0,0 +1,16 @@
{
"configurations": [
{
"name": "Mac",
"includePath": [
"${workspaceFolder}/**"
],
"defines": [],
"compilerPath": "/usr/bin/clang",
"cStandard": "c17",
"cppStandard": "c++20",
"intelliSenseMode": "macos-clang-arm64"
}
],
"version": 4
}

View File

@ -1,63 +0,0 @@
{
"files.associations": {
"*.ts": "typescriptreact",
"array": "cpp",
"iostream": "cpp",
"__bit_reference": "cpp",
"__hash_table": "cpp",
"__locale": "cpp",
"__node_handle": "cpp",
"__split_buffer": "cpp",
"__threading_support": "cpp",
"__verbose_abort": "cpp",
"bitset": "cpp",
"cctype": "cpp",
"clocale": "cpp",
"cmath": "cpp",
"complex": "cpp",
"cstdarg": "cpp",
"cstddef": "cpp",
"cstdint": "cpp",
"cstdio": "cpp",
"cstdlib": "cpp",
"cstring": "cpp",
"ctime": "cpp",
"cwchar": "cpp",
"cwctype": "cpp",
"deque": "cpp",
"execution": "cpp",
"memory": "cpp",
"fstream": "cpp",
"initializer_list": "cpp",
"iomanip": "cpp",
"ios": "cpp",
"iosfwd": "cpp",
"istream": "cpp",
"limits": "cpp",
"locale": "cpp",
"mutex": "cpp",
"new": "cpp",
"optional": "cpp",
"ostream": "cpp",
"print": "cpp",
"queue": "cpp",
"ratio": "cpp",
"sstream": "cpp",
"stack": "cpp",
"stdexcept": "cpp",
"streambuf": "cpp",
"string": "cpp",
"string_view": "cpp",
"tuple": "cpp",
"typeinfo": "cpp",
"unordered_map": "cpp",
"variant": "cpp",
"vector": "cpp",
"algorithm": "cpp",
"__tree": "cpp",
"csignal": "cpp",
"map": "cpp",
"set": "cpp",
"unordered_set": "cpp"
}
}

View File

@ -6,38 +6,26 @@
//
#include "../../rng.h"
#include "../generator.h"
// parameters recommended by Nakazawa & Nakazawa
// https://en.wikipedia.org/wiki/Lehmer_random_number_generator
uint64_t lehmer_minstd(uint64_t prev){
uint64_t a = 7759097958782935LL;
uint64_t m = 18055400005099021LL;
uint64_t result = (a * prev) % m;
return result;
}
class lehmer_generator {
uint64_t seed;
namespace splat {
class lehmer_generator : public PRNG {
public:
uint32_t generate(){
seed = lehmer_minstd(seed);
return seed;
}
lehmer_generator(uint64_t genSeed){
lehmer_generator(uint32_t genSeed) : PRNG(genSeed) {
seed = genSeed;
}
};
// std::vector<std::bitset<32>> lehmer_generate(int seed, int amount){
// std::vector<std::bitset<32>> bits(amount);
// for(int i=0; i<amount; i++){
// seed = lehmer_minstd(seed);
// bits[i]=seed;
// }
// return bits;
// }
uint32_t generate() override {
seed = (a * seed) % m;
return seed >> 32;
}
std::string getName() override {
return "lehmer";
}
private:
uint64_t a = 7759097958782935LL;
uint64_t m = 18055400005099021LL;
};
}

View File

@ -0,0 +1,18 @@
#include "../rng.h"
#pragma once
namespace splat {
class PRNG {
public:
virtual uint32_t generate() {return -1;};
virtual ~PRNG() = default;
virtual std::string getName() {return "N/A";};
PRNG(uint32_t genseed){
seed = genseed;
}
protected:
uint32_t seed;
};
}

View File

@ -1,4 +1,5 @@
#include "../rng.h"
#include "./generator.h"
// more specifically this will be mt19937 - 32 bit
@ -63,10 +64,14 @@ std::array<uint32_t,624> mt19937_temper(std::array<uint32_t,624> state){
return tempered;
}
class mt19937_generator {
namespace splat {
class mt19937_generator : public PRNG {
public:
uint32_t seed;
uint32_t generate() {
mt19937_generator(uint32_t genSeed) : PRNG(genSeed) {
state = mt19937_init(seed);
nextblock();
}
uint32_t generate() override {
uint32_t generated = random_values[position];
position++;
if(position>=624){
@ -74,13 +79,8 @@ class mt19937_generator {
}
return generated;
}
double generate_01(){
return ((double)generate())/(4294967295);
}
mt19937_generator(uint32_t genSeed){
seed = genSeed;
state = mt19937_init(seed);
nextblock();
std::string getName() override {
return "mt19937-32";
}
private:
std::array<uint32_t,624> state;
@ -92,4 +92,5 @@ class mt19937_generator {
random_values = mt19937_temper(state);
position = 0;
}
};
};
}

View File

@ -0,0 +1,25 @@
#include "../rng.h"
#include "./generator.h"
// parameters recommended by Nakazawa & Nakazawa
// https://en.wikipedia.org/wiki/Lehmer_random_number_generator
namespace splat {
class xorshift32 : public PRNG {
public:
xorshift32(uint32_t genSeed) : PRNG(genSeed) {
state = genSeed;
}
uint32_t generate() override {
state ^= state << 13;
state ^= state >> 17;
state ^= state << 5;
return state;
}
std::string getName() override {
return "xorshift32";
}
private:
uint32_t state;
};
}

View File

@ -8,45 +8,78 @@
#include "rng.h"
#include "generators/LCG/lehmer.cpp"
#include "generators/mt19937.cpp"
#include "generators/xorshift.cpp"
#include "randomness_tests/frequency_monobit.cpp"
#include "randomness_tests/frequency_block.cpp"
#include "randomness_tests/runs.cpp"
#include "randomness_tests/runs_ones.cpp"
#include "generators/generator.h"
#include "randomness_tests/rngtest.h"
namespace splat {
template <typename T>
void addGen(std::vector<std::unique_ptr<PRNG>> &generators, uint32_t &seed){
generators.push_back(std::make_unique<T>(seed));
}
std::vector<std::unique_ptr<PRNG>> getAllGenerators(uint32_t seed){
std::vector<std::unique_ptr<PRNG>> generators;
// add generators here
addGen<mt19937_generator>(generators, seed);
addGen<lehmer_generator>(generators, seed);
addGen<xorshift32>(generators,seed);
return generators;
}
template <typename T>
void addTest(std::vector<std::unique_ptr<RNGTEST>> &tests, std::vector<std::bitset<32>> &data){
tests.push_back(std::make_unique<T>(data));
}
std::vector<std::unique_ptr<RNGTEST>> getAllTests(std::vector<std::bitset<32>> &data){
std::vector<std::unique_ptr<RNGTEST>> tests;
// add generators here
addTest<frequency_monobit_test>(tests, data);
addTest<frequency_block_test>(tests, data);
addTest<runs_test>(tests, data);
addTest<runs_ones_test>(tests, data);
return tests;
}
}
int main(int argc, const char * argv[]) {
int seed = 8898;
int seed = 235211213;
int blocksgenerated = 1000000;
mt19937_generator mt2 = mt19937_generator(seed);
lehmer_generator lg = lehmer_generator(seed);
std::vector<std::bitset<32>> mtbits;
std::vector<std::bitset<32>> lgbits;
std::vector<std::unique_ptr<splat::PRNG>> generators = splat::getAllGenerators(1238124);
for(const std::unique_ptr<splat::PRNG> &generator : generators){
std::cout << generator->getName() << ": \n";
std::vector<std::bitset<32>> bits;
for(int i=0; i<blocksgenerated; i++){
mtbits.push_back(mt2.generate());
lgbits.push_back(lg.generate());
bits.push_back(generator->generate());
}
// for(auto bs : lgbits){
// for(int i=0; i<32; i++){
// std::cout << bs[i];
// }
// }
int freqblocksize = (blocksgenerated * 32) / 120;
std::cout << "Linear Congruential Generator:\n";
std::cout << " - FreqMonobit = "<< std::fixed << std::setprecision(10) << test_frequency_monobit(lgbits) << "\n";
std::cout << " - FreqBlock (n/120) = "<< std::fixed << std::setprecision(10) << test_frequency_block(lgbits, freqblocksize) << "\n";
std::cout << " - Runs = "<< std::fixed << std::setprecision(10) << test_runs(lgbits) << "\n";
std::cout << "Mersenne Twister mt19937-32:\n";
std::cout << " - FreqMonobit = "<< std::fixed << std::setprecision(10) << test_frequency_monobit(mtbits) << "\n";
std::cout << " - FreqBlock (n/120) = "<< std::fixed << std::setprecision(10) << test_frequency_block(mtbits, freqblocksize) << "\n";
std::cout << " - Runs = "<< std::fixed << std::setprecision(10) << test_runs(mtbits) << "\n";
std::vector<std::unique_ptr<splat::RNGTEST>> tests = splat::getAllTests(bits);
for(const std::unique_ptr<splat::RNGTEST> &test : tests){
std::string namestr = test->getName();
while(namestr.length() < 30){
namestr.append(" ");
}
std::cout << " - " << namestr << " = " << (test->passed()?"PASS":"FAIL") << " ("<< std::fixed << std::setprecision(8) << test->value() <<")\n";
}
}
return 0;
}
};

BIN
rng/rng/main.o Executable file

Binary file not shown.

View File

@ -1,20 +0,0 @@
import matplotlib.pyplot as plt
x_coords = []
y_coords = []
with open("data.txt", "r") as file:
for line in file:
x_coords.append(float(line.split(",")[0]))
y_coords.append(float(line.split(",")[1]))
plt.figure(figsize=(10, 8))
plt.scatter(x_coords, y_coords, alpha=0.6, s=1)
plt.grid(True, alpha=0.3)
plt.axis('equal')
plt.xlim(-0.05, 1.05)
plt.ylim(-0.05, 1.05)
plt.tight_layout()
plt.show()

View File

@ -8,12 +8,22 @@
#include "../rng.h"
#include "../math/incomplete_gamma.cpp"
/**
* Returns the p value from the block frequency test
*/
double test_frequency_block(std::vector<std::bitset<32>> data, u_long block_size){
#include "./rngtest.h"
// std::cout << "BLOCK SIZE: "<< block_size<<"\n";
namespace splat {
class frequency_block_test : public RNGTEST {
public:
frequency_block_test(std::vector<std::bitset<32>> &testData) : RNGTEST(testData) {
block_size = data.size() * 32 / 150;
testPValue = runTest(data);
testPassed = testPValue > 0.01;
}
std::string getName() override {
return std::format("Frequency Block [{}]", block_size);
}
double runTest(std::vector<std::bitset<32>> &data) override {
// std::cout << "DEBUG: "<<data.size() << "\n";
long long bitcount = data.size() * 32;
long long chunks = bitcount / block_size;
@ -39,13 +49,9 @@ double test_frequency_block(std::vector<std::bitset<32>> data, u_long block_size
// std::cout << "[debug] calling igamc with "<<(chunks/2) <<','<<(x2stat/2) << "\n";
double p = igam(chunks/2, x2stat/2);
return p;
}
}
private:
long long block_size;
/**
* It is recommended that each sequence to be tested consist of a minimum of 100 bits (i.e., n 100). Note
that n MN. The block size M should be selected such that M 20, M > .01n and N < 100.
*/
bool pass_frequency_monobit(std::vector<std::bitset<32>> data, int chunkSize){
double pv = test_frequency_block(data, chunkSize);
return pv >= 0.01;
};
}

View File

@ -6,30 +6,28 @@
//
#include "../rng.h"
#include "./rngtest.h"
double test_frequency_monobit(std::vector<std::bitset<32>> data){
namespace splat {
class frequency_monobit_test : public RNGTEST {
public:
frequency_monobit_test(std::vector<std::bitset<32>> &testData) : RNGTEST(testData) {
testPValue = runTest(data);
testPassed = testPValue > 0.01;
}
std::string getName() override {
return "Frequency Monobit";
}
double runTest(std::vector<std::bitset<32>> &data) override {
long long s = 0;
for(auto bitset : data){
for(int i=0; i<32; i++){
s += (2*bitset[i])-1;
}
}
// std::cout << " [sval: "<<s<<"] ";
double sobs = ((double)std::abs(s))/std::sqrt(data.size()*32);
double pvalue = std::erfc(sobs/std::sqrt(2));
return pvalue;
}
bool pass_frequency_monobit(std::vector<std::bitset<32>> data){
double pv = test_frequency_monobit(data);
return pv >= 0.01;
}
};
}

View File

@ -0,0 +1,25 @@
#include "../rng.h"
#pragma once
namespace splat {
class RNGTEST {
public:
bool passed(){
return testPassed;
}
double value(){
return testPValue;
}
virtual ~RNGTEST() = default;
virtual std::string getName() {return "N/A";};
RNGTEST(std::vector<std::bitset<32>> &testData) : data(testData){
};
protected:
std::vector<std::bitset<32>> &data;
bool testPassed;
double testPValue;
virtual double runTest(std::vector<std::bitset<32>> &data) {return -1.0;};
};
}

View File

@ -1,7 +1,17 @@
#include "../rng.h"
#include "./rngtest.h"
double test_runs(std::vector<std::bitset<32>> data){
namespace splat {
class runs_test : public RNGTEST {
public:
runs_test(std::vector<std::bitset<32>> &testData) : RNGTEST(testData) {
testPValue = runTest(data);
testPassed = testPValue > 0.01;
}
std::string getName() override {
return "Runs";
}
double runTest(std::vector<std::bitset<32>> &data) override {
long long totalSize = data.size() * 32;
double vnobs = 1;
@ -23,4 +33,6 @@ double test_runs(std::vector<std::bitset<32>> data){
/
(2*std::sqrt(2*totalSize)*onesproportion*(1-onesproportion))
);
}
};
}

View File

@ -0,0 +1,78 @@
#include "../rng.h"
#include "./rngtest.h"
#include "../math/incomplete_gamma.cpp"
#define vecdebug(list) for(auto vecdebugitem : list){ std::cout << vecdebugitem <<","; }std::cout << "\n";
namespace splat {
class runs_ones_test : public RNGTEST {
public:
runs_ones_test(std::vector<std::bitset<32>> &testData) : RNGTEST(testData) {
testPValue = runTest(data);
testPassed = testPValue > 0.01;
}
std::string getName() override {
return "Runs of 1s";
}
double runTest(std::vector<std::bitset<32>> &data) override {
long long totalSize = data.size() * 32;
if(totalSize < 750000){
std::cout << "Error, runs of ones test requires more than 750K bits of input\n";
return 0;
}
// we do it with M=10^4 here
std::vector<double> counts(7,0);
double chunks = totalSize / 10000;
for(int chunkIndex=0; chunkIndex<chunks; chunkIndex++){
int longestRun = 0;
int ones = 0;
for(int i=0; i<10000; i++){
long long index = chunkIndex*10000 + i;
int dataIndex = (index / 32);
int bitIndex = (index % 32);
if(data[dataIndex][bitIndex]){
ones++;
}else{
if(ones>longestRun) longestRun = ones;
ones = 0;
}
}
if(longestRun <=10){
counts[0]++;
}else if(longestRun>=16){
counts[6]++;
}else{
counts[longestRun-10]++;
}
}
double x2 = 0;
std::vector<double> probabilities = {
0.0882,
0.2092,
0.2483,
0.1933,
0.1208,
0.0675,
0.0727
};
for(int i=0; i<7; i++){
x2 += std::pow((counts[i] - (chunks * probabilities[i])),2.0) /(chunks * probabilities[i]);
}
return igamc(3, x2/2.0);
}
};
}

2
rng/rng/readme.md Normal file
View File

@ -0,0 +1,2 @@
🫟 Splatter
c++ rng library with generators and tests

View File

@ -14,3 +14,4 @@
#include <vector>
#include <cmath>
#include <iomanip>
#include <format>

View File

@ -1,3 +1,2 @@
g++ -std=c++11 main.cpp -o main.o
g++ -std=c++20 main.cpp -o main.o
./main.o
rm main.o