Introduction
I tried to write code that would return an integer of value between 0 and 9 without losing the randomness strength provided in the class RandomNumberGenerator (RNGCryptoServiceProvider)
in the System.Security.Cryptography
namespace. The hard part was that the byte array is filled with random bytes valued between 0 and 255.
Basic idea and method
If we use only integers 0, 1, 2, 3 and we randomly pick one three times from this collection, the sum of the three draws wil always have a value between 0 and 9.
If a byte has a value between 0 and 255 we also can say that this byte is an even or odd number. The even and odd numbers are equally represented in a set of (0-255).
Therefore if we take two bytes the possibilities of the outcome for a byte pair (b1 and b2) will always be:
- b(1)odd b(0)even = 0
- b(1)odd b(1)odd = 1
- b(0)even b(0)even = 2
- b(1)even b(2)odd = 3
If we populate a byte array with random 6 bytes (3 pairs) and compare 3x two bytes, we will have 3x3 possible values between 0 and 3.
For example: Array X= {a,b,c,d,e,f}
- Bytes: [b1,b2] - [b3,b4] - [b5,b6]
- Valued: [11][34] -[55][47] - [55][66]
- Are: odd/even -odd/odd -odd/even
- Resulting in: 3 + 3 + 2 = 8
(After each round I reverse the odd/even values for better distribution. This was also done in the Hot Bits Radiation project. After testing the Bell curve was better distributed.)
Using the code
There is a class file RandomDigits
and a Console application.
The class file:
using System;
using System.Security.Cryptography;
using System.Text;
namespace ps.security{
public class RandomDigits
{
public static int getDigit(){
int[] Result = { 0, 0, 0 };
int R = 0;
int b1 = 0;
int b2 = 0;
int offset = 0;
RandomNumberGenerator rng = new RNGCryptoServiceProvider();
byte[] b = new byte[6];
rng.GetNonZeroBytes(b);
for (int i =0; i <=2; i++)
{
if (i == 2) {
offset = 4;
}else if(i == 1) {
offset = 2;
}else if (i == 0){
offset = 0;
};
b1 = ((int)b[offset] % 2);
b2 = ((int)b[offset+1] % 2);
if ((i == 0))
{
if ((b1 == 1) && (b2 == 1))
{
Result[i] = 0;
}
else if ((b1 == 0) && (b2 == 1))
{
Result[i] = 1;
}
else if ((b1 == 0) && (b2 == 0))
{
Result[i] = 2;
}
else if ((b1 == 1) && (b2 == 0))
{
Result[i] = 3;
};
}
else if (i==1) {
if ((b1 == 1) && (b2 == 1))
{
Result[i] = 3;
}
else if ((b1 == 0) && (b2 == 1))
{
Result[i] = 2;
}
else if ((b1 == 0) && (b2 == 0))
{
Result[i] = 1;
}
else if ((b1 == 1) && (b2 == 0))
{
Result[i] = 0;
};
}
else if (i == 2) {
if ((b1 == 1) && (b2 == 1))
{
Result[i] = 1;
}
else if ((b1 == 0) && (b2 == 1))
{
Result[i] = 3;
}
else if ((b1 == 0) && (b2 == 0))
{
Result[i] = 0;
}
else if ((b1 == 1) && (b2 == 0))
{
Result[i] = 2;
};
}
b1 = 0;
b2 = 0;
};
R =(int)(Result[0] + Result[1] + Result[2]);
return R;
}
}
}
Here is the console testing code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ps.security;
using System.Diagnostics;
class Program
{
static void Main(string[] args){
Program.DrawNumbersPerDigitTest();
Console.ReadKey();
Console.WriteLine("");
Console.WriteLine("Test 2");
Program.DrawDigitTest(1000000);
}
public static int[] DrawNumbersPerDigitTest()
{
Console.WriteLine("Random digits 0-9 generator test.");
double frequency = System.Diagnostics.Stopwatch.Frequency;
double nanosecPerTick = (1000 * 1000 * 1000) / frequency;
int totalItter = 0;
int[] Totaldistri = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
for (int AllDigits = 9; AllDigits >= 0; AllDigits--)
{
Stopwatch timer = new Stopwatch();
int avg = 0;
int itters = 0;
int[] distri = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
timer.Start();
for (int i = 1000000; i >= 1; i--)
{
int n = ps.security.RandomDigits.getDigit();
avg = (avg + n);
distri[n]++;
itters++;
if (n == AllDigits)
{
break;
}
};
timer.Stop();
double durNanosec = timer.ElapsedTicks * nanosecPerTick;
double durSecs = timer.ElapsedTicks * 1000 / frequency;
Console.WriteLine("");
Console.WriteLine("|----------------------------------|");
Console.WriteLine("Digit nr:\t" + AllDigits);
Console.WriteLine("");
Console.WriteLine("| int\t| freq\t percentage\t |");
Console.WriteLine("|----------------------------------|");
for (int i = 9; i >= 0; i--)
{
double p =Math.Round( ((double)distri[i])/((double)itters / 100),3);
Console.WriteLine("| " + i + "\t| " +
+distri[i] + "\t " + p + "%\t");
}
Console.WriteLine("|----------------------------------|");
Console.WriteLine("");
Console.WriteLine(" Sum : >\t" + avg.ToString());
Console.WriteLine(" Average : >\t" +
((double)avg / (double)itters).ToString());
Console.WriteLine(" Count : >\t" + (itters).ToString());
Console.WriteLine("");
Console.WriteLine(" nSecs :\t" + Math.Round(durNanosec, 3));
Console.WriteLine(" mSecs :\t" + Math.Round(durSecs, 9));
totalItter += (itters);
Totaldistri[AllDigits] = itters;
};
Console.WriteLine("");
Console.WriteLine("|----------------------------------|");
Console.WriteLine("Average + total draws per Digit:");
Console.WriteLine("|----------------------------------|");
Console.WriteLine("");
Console.WriteLine(" total draws\t: >\t" + totalItter.ToString());
Console.WriteLine(" avg\t\t: >\t" +
Math.Round((double)totalItter / 10, 3));
Console.WriteLine("| int\t| freq\t ");
Console.WriteLine("|----------------------------------|");
for (int i = 9; i >= 0; i--)
{
Console.WriteLine("| " + i + "\t| " +
+Totaldistri[i] + "\t \t");
}
return Totaldistri;
}
public static void DrawDigitTest(int nrOfDigits)
{
Console.WriteLine("Random digits 0-9 generator test.");
double frequency = System.Diagnostics.Stopwatch.Frequency;
double nanosecPerTick = (1000 * 1000 * 1000) / frequency;
Stopwatch timer = new Stopwatch();
int avg = 0;
int itters = 0;
int[] distri = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
timer.Start();
for (int i = nrOfDigits; i >= 1; i--)
{
int n = ps.security.RandomDigits.getDigit();
avg = (avg + n);
distri[n]++;
itters++;
};
timer.Stop();
double durNanosec = timer.ElapsedTicks * nanosecPerTick;
double durSecs = timer.ElapsedTicks * 1000 / frequency;
Console.WriteLine("");
Console.WriteLine("| int\t| freq\t percentage\t |");
Console.WriteLine("|----------------------------------|");
for (int i = 9; i >= 0; i--)
{
double p = Math.Round(((double)distri[i]) / ((double)itters / 100), 1);
Console.WriteLine("| " + i + "\t| " +
+distri[i] + "\t\t" + p + "%.");
}
Console.WriteLine("|----------------------------------|");
Console.WriteLine("");
Console.WriteLine(" #Digits : >\t\t" + (itters).ToString());
Console.WriteLine(" Sum : >\t\t" + avg.ToString());
Console.WriteLine(" Average : >\t\t" +
((double)avg / (double)itters).ToString());
Console.WriteLine("");
Console.WriteLine(" nSecs : >\t" + Math.Round(durNanosec, 3));
Console.WriteLine(" mSecs : >\t" + Math.Round(durSecs, 9));
}
}