Fix Microbit::random() to be random

The LFSR used only provides 1 bit of random data each time it is cycled.
This implementation generates the minimum number of bits needed.  Further
it discards numbers that are bigger than required and re-calculates -
this keeps the distribution flat.
This commit is contained in:
Robert May 2015-11-17 18:19:46 +00:00
parent 2532d2f0b5
commit 892689c54a
1 changed files with 38 additions and 6 deletions

View File

@ -301,17 +301,49 @@ int MicroBit::sleep(int milliseconds)
*/
int MicroBit::random(int max)
{
uint32_t m;
uint8_t b, bits = 0;
//return MICROBIT_INVALID_VALUE if max is <= 0...
if(max <= 0)
return MICROBIT_INVALID_PARAMETER;
// Calculate the number of bits we need
m = max;
while (m >>= 1) {
bits++;
}
// Cycle the LFSR (Linear Feedback Shift Register).
// We use an optimal sequence with a period of 2^32-1, as defined by Bruce Schneider here (a true legend in the field!),
// For those interested, it's documented in his paper:
// "Pseudo-Random Sequence Generator for 32-Bit CPUs: A fast, machine-independent generator for 32-bit Microprocessors"
m = 0;
do {
for(b=0; b<bits; b++) {
// Cycle the LFSR (Linear Feedback Shift Register).
// We use an optimal sequence with a period of 2^32-1, as defined by Bruce Schneider here (a true legend in the field!),
// For those interested, it's documented in his paper:
// "Pseudo-Random Sequence Generator for 32-Bit CPUs: A fast, machine-independent generator for 32-bit Microprocessors"
// https://www.schneier.com/paper-pseudorandom-sequence.html
// Avoid interupts (and hence fibre context switch) while we are doing this
randomValue = ((((randomValue >> 31) ^ (randomValue >> 6) ^ (randomValue >> 4) ^ (randomValue >> 2) ^ (randomValue >> 1) ^ randomValue) & 0x0000001) << 31 ) | (randomValue >> 1);
return randomValue % max;
__disable_irq();
randomValue = ((((randomValue >> 31)
^ (randomValue >> 6)
^ (randomValue >> 4)
^ (randomValue >> 2)
^ (randomValue >> 1)
^ randomValue)
& 0x0000001)
<< 31 )
| (randomValue >> 1);
__enable_irq();
m = ((m << 1) | (randomValue & 0x00000001));
}
} while (m > (uint32_t)max);
return m;
}