Skip to content

Instantly share code, notes, and snippets.

@jnm2
Last active October 23, 2019 13:10
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 jnm2/cd2b3455a208a0eda9a778d462394d6d to your computer and use it in GitHub Desktop.
Save jnm2/cd2b3455a208a0eda9a778d462394d6d to your computer and use it in GitHub Desktop.
using System;
using System.Runtime.Intrinsics.X86;
using System.Security.Cryptography;
public sealed class CryptographicRandomSource : IRandomSource, IDisposable
{
private readonly RandomNumberGenerator generator;
public CryptographicRandomSource()
{
generator = RandomNumberGenerator.Create();
}
public int ChooseIndex(int optionCount)
{
if (optionCount < 1) throw new ArgumentOutOfRangeException(nameof(optionCount), optionCount, "There must be at least one option.");
var maxValue = (uint)optionCount - 1;
var maxValueLeadingZeroCount = (int)Lzcnt.LeadingZeroCount(maxValue);
var result = default(uint);
var minimumNumberOfBytesToFill = sizeof(uint) - (maxValueLeadingZeroCount / 8);
Span<byte> resultBytesToFill;
unsafe
{
resultBytesToFill = CreateSpanForLeastSignificantBytes(&result, minimumNumberOfBytesToFill);
}
var maskToRemoveBitsGuaranteedToExceedMaximum = uint.MaxValue >> maxValueLeadingZeroCount;
while (true)
{
generator.GetBytes(resultBytesToFill);
result &= maskToRemoveBitsGuaranteedToExceedMaximum;
if (result <= maxValue) return (int)result;
}
}
private unsafe static Span<byte> CreateSpanForLeastSignificantBytes(uint* memory, int byteCount)
{
return new Span<byte>(
(byte*)memory + (BitConverter.IsLittleEndian ? 0 : sizeof(uint) - byteCount),
byteCount);
}
public void Dispose()
{
generator.Dispose();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment