From d9dd0029d921bc1785124ffa14103dd24c471b23 Mon Sep 17 00:00:00 2001 From: Dry Fish Date: Sun, 21 Jun 2026 06:49:14 +0700 Subject: [PATCH] fix: add validation for alphabet random, fix performance issue and many issue --- src/main/ILibRandom.cs | 150 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 140 insertions(+), 10 deletions(-) diff --git a/src/main/ILibRandom.cs b/src/main/ILibRandom.cs index 87f32f1..9917209 100644 --- a/src/main/ILibRandom.cs +++ b/src/main/ILibRandom.cs @@ -26,6 +26,19 @@ public static class ILibRandom "yellow", "white" }; + // Cache byte array for performance + [ThreadStatic] + private static byte[]? _byteBuffer; + + private static byte[] GetByteBuffer() + { + if (_byteBuffer == null) + { + _byteBuffer = new byte[8]; + } + return _byteBuffer; + } + /// /// Returns a random element from the specified array /// @@ -56,9 +69,13 @@ public static int IRandomInt(int min, int max) if (min > max) throw new ArgumentException("min must be <= max"); +#if NET6_0_OR_GREATER + return System.Random.Shared.Next(min, max + 1); +#else double range = (double)max - (double)min; int offset = (int)(_random.NextDouble() * (range + 1.0)); return min + offset; +#endif } /// @@ -87,9 +104,36 @@ public static char IRandomAlphabet(char min, char max) { if (min > max) throw new ArgumentException("min must be <= max"); - if (!char.IsLetter(min) || !char.IsLetter(max)) - throw new ArgumentException("Only alphabet characters allowed"); + // Validate both characters are letters + if (!char.IsLetter(min) || !char.IsLetter(max)) + throw new ArgumentException("Both min and max must be alphabet characters"); + + // Validate the range only contains letters + // Check if min and max are both uppercase or both lowercase + bool minIsUpper = char.IsUpper(min); + bool maxIsUpper = char.IsUpper(max); + + if (minIsUpper != maxIsUpper) + throw new ArgumentException("min and max must be both uppercase or both lowercase"); + + // Validate range doesn't include non-letter characters + // For uppercase: A-Z only (65-90) + // For lowercase: a-z only (97-122) + int minValue = (int)min; + int maxValue = (int)max; + + if (minIsUpper) + { + if (minValue < 'A' || maxValue > 'Z') + throw new ArgumentException("Range must be within A-Z for uppercase letters"); + } + else + { + if (minValue < 'a' || maxValue > 'z') + throw new ArgumentException("Range must be within a-z for lowercase letters"); + } + char result; do { @@ -124,25 +168,44 @@ public static bool IRandomBool() } /// - /// Returns a random long between min and max + /// Returns a random long between min and max (inclusive) /// public static long IRandomLong(long min, long max) { if (min > max) throw new ArgumentException("min must be <= max"); + + if (min == max) + return min; +#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 } /// @@ -156,6 +219,50 @@ public static double IRandomDouble(double min = 0.0, double max = 1.0) return min + (_random.NextDouble() * (max - min)); } + /// + /// Returns a random decimal between min and max + /// + public static decimal IRandomDecimal(decimal min, decimal max) + { + if (min > max) + throw new ArgumentException("min must be <= max"); + + if (min == max) + return min; + + // Get a random double and convert to decimal + // Using 28-29 digits of precision (maximum for decimal) + double randomDouble = _random.NextDouble(); + decimal randomDecimal = (decimal)randomDouble; + + // Scale to the range + decimal range = max - min; + return min + (randomDecimal * range); + } + + /// + /// Returns a random decimal between min and max with specified precision + /// + public static decimal IRandomDecimal(decimal min, decimal max, int precision) + { + if (min > max) + throw new ArgumentException("min must be <= max"); + + if (precision < 0 || precision > 28) + throw new ArgumentException("precision must be between 0 and 28"); + + if (min == max) + return min; + + // 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; + } + /// /// Returns a random item from a list /// @@ -190,5 +297,28 @@ public static string IRandomConsoleColor() { return IRandomFromArray(ConsoleColors); } + + /// + /// Returns a random element from an enumeration + /// + public static T IRandomEnum() where T : Enum + { + var values = Enum.GetValues(typeof(T)); + return (T)values.GetValue(_random.Next(values.Length))!; + } + + /// + /// Returns a random element from an enumeration with exclusion + /// + public static T IRandomEnum(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; + } } }