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 "../../rng.h"
#include "../generator.h"
// parameters recommended by Nakazawa & Nakazawa // parameters recommended by Nakazawa & Nakazawa
// https://en.wikipedia.org/wiki/Lehmer_random_number_generator // https://en.wikipedia.org/wiki/Lehmer_random_number_generator
uint64_t lehmer_minstd(uint64_t prev){ namespace splat {
uint64_t a = 7759097958782935LL; class lehmer_generator : public PRNG {
uint64_t m = 18055400005099021LL; public:
lehmer_generator(uint32_t genSeed) : PRNG(genSeed) {
uint64_t result = (a * prev) % m; seed = genSeed;
}
return result; uint32_t generate() override {
} seed = (a * seed) % m;
return seed >> 32;
class lehmer_generator { }
uint64_t seed; std::string getName() override {
public: return "lehmer";
uint32_t generate(){ }
seed = lehmer_minstd(seed); private:
return seed; uint64_t a = 7759097958782935LL;
} uint64_t m = 18055400005099021LL;
lehmer_generator(uint64_t 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;
// }

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 "../rng.h"
#include "./generator.h"
// more specifically this will be mt19937 - 32 bit // more specifically this will be mt19937 - 32 bit
@ -63,33 +64,33 @@ std::array<uint32_t,624> mt19937_temper(std::array<uint32_t,624> state){
return tempered; return tempered;
} }
class mt19937_generator { namespace splat {
public: class mt19937_generator : public PRNG {
uint32_t seed; public:
uint32_t generate() { mt19937_generator(uint32_t genSeed) : PRNG(genSeed) {
uint32_t generated = random_values[position]; state = mt19937_init(seed);
position++;
if(position>=624){
nextblock(); nextblock();
} }
return generated; uint32_t generate() override {
} uint32_t generated = random_values[position];
double generate_01(){ position++;
return ((double)generate())/(4294967295); if(position>=624){
} nextblock();
mt19937_generator(uint32_t genSeed){ }
seed = genSeed; return generated;
state = mt19937_init(seed); }
nextblock(); std::string getName() override {
} return "mt19937-32";
private: }
std::array<uint32_t,624> state; private:
std::array<uint32_t,624> random_values; std::array<uint32_t,624> state;
int position; std::array<uint32_t,624> random_values;
// goes to next block of 624 values int position;
void nextblock() { // goes to next block of 624 values
state = mt19937_twist(state); void nextblock() {
random_values = mt19937_temper(state); state = mt19937_twist(state);
position = 0; 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 "rng.h"
#include "generators/LCG/lehmer.cpp" #include "generators/LCG/lehmer.cpp"
#include "generators/mt19937.cpp" #include "generators/mt19937.cpp"
#include "generators/xorshift.cpp"
#include "randomness_tests/frequency_monobit.cpp" #include "randomness_tests/frequency_monobit.cpp"
#include "randomness_tests/frequency_block.cpp" #include "randomness_tests/frequency_block.cpp"
#include "randomness_tests/runs.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 main(int argc, const char * argv[]) {
int seed = 8898; int seed = 235211213;
int blocksgenerated = 1000000; int blocksgenerated = 1000000;
mt19937_generator mt2 = mt19937_generator(seed); std::vector<std::unique_ptr<splat::PRNG>> generators = splat::getAllGenerators(1238124);
lehmer_generator lg = lehmer_generator(seed);
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++){
bits.push_back(generator->generate());
}
std::vector<std::bitset<32>> mtbits; std::vector<std::unique_ptr<splat::RNGTEST>> tests = splat::getAllTests(bits);
std::vector<std::bitset<32>> lgbits;
for(int i=0; i<blocksgenerated; i++){ for(const std::unique_ptr<splat::RNGTEST> &test : tests){
mtbits.push_back(mt2.generate()); std::string namestr = test->getName();
lgbits.push_back(lg.generate()); while(namestr.length() < 30){
namestr.append(" ");
}
std::cout << " - " << namestr << " = " << (test->passed()?"PASS":"FAIL") << " ("<< std::fixed << std::setprecision(8) << test->value() <<")\n";
}
} }
// 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";
return 0; 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,44 +8,50 @@
#include "../rng.h" #include "../rng.h"
#include "../math/incomplete_gamma.cpp" #include "../math/incomplete_gamma.cpp"
/** #include "./rngtest.h"
* Returns the p value from the block frequency test
*/
double test_frequency_block(std::vector<std::bitset<32>> data, u_long block_size){
// std::cout << "BLOCK SIZE: "<< block_size<<"\n";
long long bitcount = data.size() * 32; namespace splat {
long long chunks = bitcount / block_size; 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 {
double x2stat = 0; // std::cout << "DEBUG: "<<data.size() << "\n";
for(int chunkIndex = 0; chunkIndex < chunks; chunkIndex++){
// std::cout << "NEWCHUNK: ";
double onecount = 0;
for(int i=0; i<block_size; i++){
int dataIndex = (((chunkIndex * block_size)+i) / 32);
int bitIndex = (((chunkIndex * block_size)+i) % 32);
onecount += data[dataIndex][bitIndex];
// std::cout << data[dataIndex][bitIndex];
}
double oneproportion = onecount / (double)block_size;
x2stat += 4*block_size*std::pow((oneproportion - 0.5),2); long long bitcount = data.size() * 32;
long long chunks = bitcount / block_size;
// std::cout << "\n"; double x2stat = 0;
}
for(int chunkIndex = 0; chunkIndex < chunks; chunkIndex++){
// std::cout << "NEWCHUNK: ";
double onecount = 0;
for(int i=0; i<block_size; i++){
int dataIndex = (((chunkIndex * block_size)+i) / 32);
int bitIndex = (((chunkIndex * block_size)+i) % 32);
onecount += data[dataIndex][bitIndex];
// std::cout << data[dataIndex][bitIndex];
}
double oneproportion = onecount / (double)block_size;
// std::cout << "[debug] calling igamc with "<<(chunks/2) <<','<<(x2stat/2) << "\n"; x2stat += 4*block_size*std::pow((oneproportion - 0.5),2);
double p = igam(chunks/2, x2stat/2);
return p;
}
/** // std::cout << "\n";
* 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.
*/ // std::cout << "[debug] calling igamc with "<<(chunks/2) <<','<<(x2stat/2) << "\n";
bool pass_frequency_monobit(std::vector<std::bitset<32>> data, int chunkSize){ double p = igam(chunks/2, x2stat/2);
double pv = test_frequency_block(data, chunkSize); return p;
return pv >= 0.01; }
private:
long long block_size;
};
} }

View File

@ -6,30 +6,28 @@
// //
#include "../rng.h" #include "../rng.h"
#include "./rngtest.h"
namespace splat {
double test_frequency_monobit(std::vector<std::bitset<32>> data){ class frequency_monobit_test : public RNGTEST {
public:
long long s = 0; frequency_monobit_test(std::vector<std::bitset<32>> &testData) : RNGTEST(testData) {
testPValue = runTest(data);
for(auto bitset : data){ testPassed = testPValue > 0.01;
for(int i=0; i<32; i++){
s += (2*bitset[i])-1;
} }
} std::string getName() override {
return "Frequency Monobit";
// std::cout << " [sval: "<<s<<"] "; }
double runTest(std::vector<std::bitset<32>> &data) override {
double sobs = ((double)std::abs(s))/std::sqrt(data.size()*32); long long s = 0;
for(auto bitset : data){
for(int i=0; i<32; i++){
s += (2*bitset[i])-1;
double pvalue = std::erfc(sobs/std::sqrt(2)); }
}
return pvalue; 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,26 +1,38 @@
#include "../rng.h" #include "../rng.h"
#include "./rngtest.h"
double test_runs(std::vector<std::bitset<32>> data){ namespace splat {
class runs_test : public RNGTEST {
long long totalSize = data.size() * 32; public:
runs_test(std::vector<std::bitset<32>> &testData) : RNGTEST(testData) {
double vnobs = 1; testPValue = runTest(data);
testPassed = testPValue > 0.01;
double onesproportion = 0;
for(int i=0; i<totalSize-1; i++){
if(data[i/32][i%32]!=data[(i+1)/32][(i+1)%32]){
vnobs++;
} }
onesproportion+=data[i/32][i%32]; std::string getName() override {
} return "Runs";
}
double runTest(std::vector<std::bitset<32>> &data) override {
long long totalSize = data.size() * 32;
onesproportion/=totalSize; double vnobs = 1;
return std::erfc( double onesproportion = 0;
std::abs(vnobs-(2*totalSize*onesproportion*(1-onesproportion)))
/ for(int i=0; i<totalSize-1; i++){
(2*std::sqrt(2*totalSize)*onesproportion*(1-onesproportion))
); if(data[i/32][i%32]!=data[(i+1)/32][(i+1)%32]){
vnobs++;
}
onesproportion+=data[i/32][i%32];
}
onesproportion/=totalSize;
return std::erfc(
std::abs(vnobs-(2*totalSize*onesproportion*(1-onesproportion)))
/
(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 <vector>
#include <cmath> #include <cmath>
#include <iomanip> #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 ./main.o
rm main.o