/* $Id: t_dibigdRandomTests.cpp $ */
/*
* Copyright (C) 2026 David Ireland, D.I. Management Services Pty Limited
* <https://di-mgt.com.au/contact/> <https://di-mgt.com.au/bigdigits.html>
* SPDX-License-Identifier: MPL-2.0
*
* Last updated:
* $Date: 2026-05-05 02:02 $
* $Revision: 1.0.1 $
* $Author: dai $
*/
/* Test the internal random number generator and the prime generator. */
#ifdef NDEBUG
#undef NDEBUG
#endif
#include <iostream>
//#include <string>
#include <stdexcept>
#include <assert.h>
#include <time.h>
#include "dibigd.hpp"
using std::cout;
using std::endl;
using namespace dibigd;
int my_rand(unsigned char *bytes, size_t nbytes, const unsigned char *seed, size_t seedlen)
/* Our own (insecure) random generator call-back function using good old rand.
This demonstrates a function with the required format for BD_RANDFUNC
(unsigned char*, size_t, const unsigned char*, size_t)
-- replace this in practice with your own cryptographically-secure function.
*/
{
unsigned int myseed;
size_t i;
int offset;
/* Use time and clock - then blend in seed, if any */
myseed = (unsigned)time(NULL) << 8 | (unsigned)clock();
if (seed) {
for (offset = 0, i = 0; i < seedlen; i++, offset = (offset + 1) % sizeof(unsigned))
myseed ^= ((unsigned int)seed[i] << (offset * 8));
}
srand(myseed);
while (nbytes--) {
*bytes++ = rand() & 0xFF;
}
return 0;
}
void do_test() {
cout << endl << "Testing random numbers..." << endl;
BigDigit r;
BigDigit n(100);
int i;
cout << "Up to 100 random bits:" << endl;
for (i = 0; i < 5; i++) {
r.set_rand_bits(100);
r.printhex();
}
cout << "Up to 64 random bits:" << endl;
for (i = 0; i < 5; i++) {
r.set_rand_bits(64);
r.printbits();
}
// Check that set_rand_* funcs return the new value
BigDigit t;
t = r.set_rand_bits(128);
assert(t == r);
t = r.set_rand_number(n);
// Check the distribution we get from calling set_rand_number N times for a certain range n.
const double CHI_CRITICAL = 16.92;
/* We sort the results into k=10 buckets and use the chi-square goodness of fit test with the
null hypothesis being the numbers are drwan from a uniform distribution.
The expected result E-i = N/k.
We have k-1=9 degrees of freedom and want a significance level of 0.05.
The critical value for 9 degrees of freedom at a significance level of 0.05 is 16.92
If the calculated chi-square statistic is *greater* than CHI_CRITICAL, then the result
is statisically significant and we can reject the null hypothesis. Otherwise we can say that
the result is consistent with a uniform distribution. */
int N, k;
double Ei, Oi, chisq;
k = 10; // Number of categories
int distrib[10] = { 0 };
N = 200; // Number of tests
cout << "set_rand_number(n) for n=" << n.to_str() << endl;
cout << "N tests = " << N << endl;
for (i = 0; i < N; i++) {
// Generate random number in range [0,n-1]
r.set_rand_number(n);
cout << r.to_str() << " ";
// Keep a tally, use integers
int band = r.to_short() / k;
distrib[band]++;
}
cout << endl;
// Display distribution and do a Chi-square goodness of fit test
// Expected count is N / k
Ei = (double)N / (double)k;
chisq = 0;
for (i = 0; i < k; i++) {
cout << std::setfill('0') << std::setw(2) << k * i << "-" << std::setw(2) << k * i + 9 << ": " << distrib[i] << endl;
Oi = (double)distrib[i]; // Observed count
chisq += (Oi - Ei)*(Oi - Ei) / Ei;
}
cout << "Chi-square=" << chisq <<
(chisq < CHI_CRITICAL ? " OK, consistent, within limit " : " Probably not uniform! Outside limit! ") << CHI_CRITICAL << endl;
// Again
N = 100;
n = 10;
for (i = 0; i < k; i++) {
distrib[i] = 0;
}
cout << "set_rand_number(n) for n=" << n.to_str() << endl;
cout << "N tests = " << N << endl;
Ei = (double)N / (double)k;
chisq = 0;
for (i = 0; i < N; i++) {
r.set_rand_number(n);
cout << r.to_str() << " ";
int band = r.to_short();
distrib[band]++;
}
cout << endl;
// Display distribution
for (i = 0; i < k; i++) {
cout << i << ": " << distrib[i] << endl;
Oi = (double)distrib[i]; // Observed count
chisq += (Oi - Ei)*(Oi - Ei) / Ei;
}
cout << "Chi-square=" << chisq <<
(chisq < CHI_CRITICAL ? " OK, consistent, within limit " : " Probably not uniform! Outside limit! ") << CHI_CRITICAL << endl;
// TEST PRIME GENERATION
BigDigit p;
cout << "Generating a prime..." << endl;
p.set_prime(100);
p.printhex("p=");
p.printbits("p=");
cout << "p is " << (p.is_prime() ? "prime" : "not prime") << " (" << p.bitlen() << " bits)" << endl;
// Set a bit at n=101, two bits in front -- unlikely to be a prime
p.setbit(101, 1);
cout << "After setbit(101) " << p.bitlen() << " bits" << endl;
p.printbits("p'=");
cout << "p' is " << (p.is_prime() ? "prime" : "not prime") << " (" << p.bitlen() << " bits)" << endl;
cout << "p.getbit(101) is " << p.getbit(101) << " (expected 1)" << endl; // expected 1
cout << "p.getbit(100) is " << p.getbit(100) << " (expected 0)" << endl; // expected 0
// Use an external RNG function. NB my_rand is not secure!
p.set_prime(200, my_rand);
p.printhex("p(200)=");
cout << "p is " << (p.is_prime() ? "prime" : "not prime") << " (" << p.bitlen() << " bits)" << endl;
// Again with a user-supplied seed
p.set_prime(200, my_rand, (unsigned char*)"NaCl", 4);
p.printhex("p(200)=");
cout << "p is " << (p.is_prime() ? "prime" : "not prime") << " (" << p.bitlen() << " bits)" << endl;
// Use default constructor to set a prime
BigDigit x = BigDigit().set_prime(64);
x.printhex("BigDigit().set_prime(64)=");
// Check that set_prime returns the value it set
t = p.set_prime(64);
assert(t == p);
t = p.set_prime(64, my_rand);
assert(t == p);
t = p.set_prime(64, my_rand, (unsigned char*)"NaCl", 4);
assert(t == p);
}
int main() {
/* MSVC memory leak checking stuff */
#if _MSC_VER >= 1100
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
_CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE);
_CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDOUT);
_CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE);
_CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDOUT);
#endif
/* Catch any exceptions */
try {
do_test();
}
catch (const std::exception& e) {
// Handle standard exceptions with a specific message
std::cerr << "Standard exception: " << e.what() << std::endl;
}
catch (...) {
std::cerr << "Caught unknown exception." << std::endl;
}
cout << endl << "ALL DONE." << endl << endl;
return 0;
}