Last active
April 5, 2024 13:24
-
-
Save emafriedrich/b4dcdf1268b0118f5bbc35435b6980e3 to your computer and use it in GitHub Desktop.
Random Number Generator with Chi Squared test
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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