Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces performance optimizations and new features to the ILibRandom class, including thread-static byte buffers, .NET 6+ specific optimizations for random integer and long generation, stricter character range validation, new random decimal generation methods, and random enum generation. The review feedback highlights several critical issues: potential integer overflows in IRandomInt and IRandomLong when dealing with maximum bounds, a minor bias in the rejection sampling logic of IRandomLong, potential overflow in IRandomDecimal with high precision, and performance overhead and infinite loop risks in the new IRandomEnum methods.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| #if NET6_0_OR_GREATER | ||
| return System.Random.Shared.Next(min, max + 1); | ||
| #else |
There was a problem hiding this comment.
If max is int.MaxValue, max + 1 will overflow to int.MinValue. This causes System.Random.Shared.Next(min, max + 1) to throw an ArgumentOutOfRangeException because the upper bound is less than the lower bound.
By casting to long, we can safely calculate the range (long)max - min + 1 without any overflow, and then use NextInt64 to get a uniform random value.
#if NET6_0_OR_GREATER
return (int)(min + System.Random.Shared.NextInt64((long)max - min + 1));
#else| #if NET6_0_OR_GREATER | ||
| // .NET 6+ has built-in method for long range | ||
| return System.Random.Shared.NextInt64(min, max + 1); | ||
| #else | ||
| // For older frameworks, use rejection sampling to avoid bias | ||
| ulong range = (ulong)(max - min); | ||
|
|
||
| // Handle full range (ulong.MaxValue) | ||
| if (range == ulong.MaxValue) | ||
| { | ||
| byte[] buf = new byte[8]; | ||
| _random.NextBytes(buf); | ||
| return BitConverter.ToInt64(buf, 0); | ||
| byte[] buffer = GetByteBuffer(); | ||
| _random.NextBytes(buffer); | ||
| return BitConverter.ToInt64(buffer, 0); | ||
| } | ||
|
|
||
| byte[] bytes = new byte[8]; | ||
| _random.NextBytes(bytes); | ||
| ulong uval = BitConverter.ToUInt64(bytes, 0); | ||
|
|
||
| // Rejection sampling to eliminate bias | ||
| ulong limit = ulong.MaxValue - ulong.MaxValue % (range + 1); | ||
| byte[] bytes = GetByteBuffer(); | ||
| ulong uval; | ||
|
|
||
| do | ||
| { | ||
| _random.NextBytes(bytes); | ||
| uval = BitConverter.ToUInt64(bytes, 0); | ||
| } while (uval > limit); | ||
|
|
||
| return min + (long)(uval % (range + 1)); | ||
| #endif |
There was a problem hiding this comment.
This method has two issues:
- If
maxislong.MaxValue,max + 1will overflow tolong.MinValue, causingSystem.Random.Shared.NextInt64to throw anArgumentOutOfRangeException. - The rejection sampling loop condition
uval > limitacceptslimit + 1values, which is not a multiple ofrange + 1, introducing a tiny bias. Changing it touval >= limitensures perfect uniformity.
For .NET 6+, we can handle the max == long.MaxValue case using Span<byte> and stackalloc to avoid any thread-static buffer access or array overhead.
#if NET6_0_OR_GREATER
if (max < long.MaxValue)
{
return System.Random.Shared.NextInt64(min, max + 1);
}
ulong range = (ulong)(max - min);
if (range == ulong.MaxValue)
{
Span<byte> buffer = stackalloc byte[8];
System.Random.Shared.NextBytes(buffer);
return BitConverter.ToInt64(buffer);
}
ulong limit = ulong.MaxValue - ulong.MaxValue % (range + 1);
Span<byte> bytes = stackalloc byte[8];
ulong uval;
do
{
System.Random.Shared.NextBytes(bytes);
uval = BitConverter.ToUInt64(bytes);
} while (uval >= limit);
return min + (long)(uval % (range + 1));
#else
ulong range = (ulong)(max - min);
if (range == ulong.MaxValue)
{
byte[] buffer = GetByteBuffer();
_random.NextBytes(buffer);
return BitConverter.ToInt64(buffer, 0);
}
ulong limit = ulong.MaxValue - ulong.MaxValue % (range + 1);
byte[] bytes = GetByteBuffer();
ulong uval;
do
{
_random.NextBytes(bytes);
uval = BitConverter.ToUInt64(bytes, 0);
} while (uval >= limit);
return min + (long)(uval % (range + 1));
#endif| // Generate random integer with specified precision | ||
| long multiplier = (long)Math.Pow(10, precision); | ||
| long minScaled = (long)(min * multiplier); | ||
| long maxScaled = (long)(max * multiplier); | ||
|
|
||
| long randomScaled = IRandomLong(minScaled, maxScaled); | ||
| return randomScaled / (decimal)multiplier; |
There was a problem hiding this comment.
Using Math.Pow(10, precision) and casting to long will overflow when precision >= 19 (since 10^19 exceeds long.MaxValue). Additionally, min * multiplier or max * multiplier can easily overflow long even for smaller precisions if the values are large.
A much safer and simpler approach is to generate the random decimal using IRandomDecimal(min, max) and then round it to the desired precision using Math.Round.
decimal randomValue = IRandomDecimal(min, max);
return Math.Round(randomValue, precision, MidpointRounding.AwayFromZero);| /// <summary> | ||
| /// Returns a random element from an enumeration | ||
| /// </summary> | ||
| public static T IRandomEnum<T>() where T : Enum | ||
| { | ||
| var values = Enum.GetValues(typeof(T)); | ||
| return (T)values.GetValue(_random.Next(values.Length))!; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Returns a random element from an enumeration with exclusion | ||
| /// </summary> | ||
| public static T IRandomEnum<T>(T exclude) where T : Enum | ||
| { | ||
| var values = Enum.GetValues(typeof(T)); | ||
| T result; | ||
| do | ||
| { | ||
| result = (T)values.GetValue(_random.Next(values.Length))!; | ||
| } while (result.Equals(exclude)); | ||
| return result; | ||
| } |
There was a problem hiding this comment.
This implementation has two main issues:
- Calling
Enum.GetValues(typeof(T))on every invocation is very slow because it uses reflection and allocates a new array each time. We can cache the values in a generic static classEnumCache<T>to avoid allocations and reflection overhead. - In
IRandomEnum<T>(T exclude), if the enum has only one value and it is excluded (or if all values are excluded), thedo-whileloop will run infinitely, causing a denial of service. We should validate that there is at least one non-excluded value before looping.
private static class EnumCache<T> where T : Enum
{
public static readonly T[] Values = (T[])Enum.GetValues(typeof(T));
}
/// <summary>
/// Returns a random element from an enumeration
/// </summary>
public static T IRandomEnum<T>() where T : Enum
{
var values = EnumCache<T>.Values;
if (values.Length == 0)
throw new ArgumentException("Enum has no values");
return values[_random.Next(values.Length)];
}
/// <summary>
/// Returns a random element from an enumeration with exclusion
/// </summary>
public static T IRandomEnum<T>(T exclude) where T : Enum
{
var values = EnumCache<T>.Values;
if (values.Length == 0)
throw new ArgumentException("Enum has no values");
bool hasOther = false;
foreach (var val in values)
{
if (!val.Equals(exclude))
{
hasOther = true;
break;
}
}
if (!hasOther)
throw new ArgumentException("All values in the enum are excluded");
T result;
do
{
result = values[_random.Next(values.Length)];
} while (result.Equals(exclude));
return result;
}
No description provided.