Skip to content

Instantly share code, notes, and snippets.

@emafriedrich
Last active April 5, 2024 13:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save emafriedrich/b4dcdf1268b0118f5bbc35435b6980e3 to your computer and use it in GitHub Desktop.
Save emafriedrich/b4dcdf1268b0118f5bbc35435b6980e3 to your computer and use it in GitHub Desktop.
Random Number Generator with Chi Squared test
const crypto = require('crypto');
// Can set the game param to fix any bias you find
const randomDecimalFromHash = ({ hash, game, charsToTake = 5, startsFromPosition = 0 }) => {
hash = hash + game;
const cycle = 3;
const cycleOffset = (cycle * 2) % hash.length;
const hashSegmentForInterval = hash.substring(cycleOffset, cycleOffset + 2);
const randomInterval = parseInt(hashSegmentForInterval, 16) % charsToTake + 1;
const hashToChars = hash.split('');
let charsTaken = '';
for (let index = startsFromPosition; index < hashToChars.length; index++) {
if (index % randomInterval === 0) {
const element = hashToChars[index];
charsTaken = charsTaken.concat(element);
if (charsToTake === charsTaken.length) break;
}
if (index >= hashToChars.length && charsToTake > charsTaken.length) {
index = 0;
}
}
const maxNumber = parseInt(
charsTaken.split('').map(() => 'F').join(''),
16
);
// adding 1 because we can have charsTaken with only `f`, so the next division would be resolved as 1,
// breaking the expected value of the function => mathematically [0,1)
const numberFromSlicedHash = parseInt(charsTaken, 16) - 1;
const randomDecimal = numberFromSlicedHash / maxNumber;
return randomDecimal;
};
module.exports = { randomDecimalFromHash };
const chiSquaredTest = require('chi-squared-test');
const { randomDecimalFromHash } = require('./shared-utils');
const crypto = require('crypto');
const { assert } = require('console');
function generateHash() {
const rand = () => crypto.randomBytes(32).toString('hex');
const hash = (str) => crypto.createHash('sha256').update(str).digest('hex');
const gameHash = hash(rand());
return gameHash;
}
describe('Random Number Generator Tests', () => {
const doTest = (chooseBetween) => {
const intervalCount = chooseBetween;
let observedFrequencies = new Array(intervalCount).fill(0);
let totalObservations = 10000;
for (let i = 0; i < totalObservations; i++) {
const randomNumber = randomDecimalFromHash({ hash: generateHash() });
const scaledNumber = Math.floor(randomNumber * chooseBetween);
observedFrequencies[scaledNumber]++;
}
let expectedFrequency = totalObservations / intervalCount;
let expectedFrequencies = new Array(intervalCount).fill(expectedFrequency);
let chiSquared = chiSquaredTest(observedFrequencies, expectedFrequencies, intervalCount - 1);
return chiSquared.probability > 0.05;
};
it('Should distribute numbers evenly across 3 intervals', () => {
let failures = 0;
const numberOfTests = 10;
for (let index = 0; index < numberOfTests; index++) {
if (doTest(3) === false) {
failures++;
}
}
assert(failures <= 1);
});
it('Should distribute numbers evenly across 2 intervals', () => {
let failures = 0;
const numberOfTests = 10;
for (let index = 0; index < numberOfTests; index++) {
if (doTest(2) === false) {
failures++;
}
}
assert(failures <= 1);
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment