4362a433c356 — Michael Johnson 2 years ago
Prototype using Genumerics library for Uniform distributions

Genumerics looks like it could be a very useful library, but it's not in a
usable state for us right now. Primarily, it doesn't have a way of specifying
whether to use checked or unchecked operations. Right now, we often want
unchecked, but for some operations (for example, Number.Convert<,>), it isn't
possible to use without throwing an exception. Another issue is that the
performance vs direct UniformInt32 leaves a lot to be desired.
M src/Benchmarks/UniformDists.cs +30 -8
@@ 13,14 13,16 @@ namespace RandN.Benchmarks
 
         private readonly StepRng _rng;
 
-        private readonly UniformSByte _uniformSByte;
-        private readonly UniformInt16 _uniformInt16;
-        private readonly UniformInt32 _uniformInt32;
-        private readonly UniformInt64 _uniformInt64;
-        private readonly UniformByte _uniformByte;
-        private readonly UniformUInt16 _uniformUInt16;
-        private readonly UniformUInt32 _uniformUInt32;
-        private readonly UniformUInt64 _uniformUInt64;
+        private readonly UniformInt<SByte> _uniformSByte;
+        private readonly UniformInt<Int16> _uniformInt16;
+        private readonly UniformInt<Int32> _uniformInt32;
+        private readonly UniformInt32 _uniformInt32Original;
+        private readonly UniformInt<Int64> _uniformInt64;
+        private readonly UniformInt64 _uniformInt64Original;
+        private readonly UniformInt<Byte> _uniformByte;
+        private readonly UniformInt<UInt16> _uniformUInt16;
+        private readonly UniformInt<UInt32> _uniformUInt32;
+        private readonly UniformInt<UInt64> _uniformUInt64;
 
         private readonly UniformFloat<Single> _uniformSingle;
         private readonly UniformFloat<Double> _uniformDouble;

          
@@ 38,7 40,9 @@ namespace RandN.Benchmarks
             _uniformByte = Uniform.New((Byte)LowerBound, (Byte)UpperBound);
             _uniformUInt16 = Uniform.New((UInt16)LowerBound, (UInt16)UpperBound);
             _uniformUInt32 = Uniform.New((UInt32)LowerBound, (UInt32)UpperBound);
+            _uniformInt32Original = UniformInt32.Create(LowerBound, UpperBound);
             _uniformUInt64 = Uniform.New((UInt64)LowerBound, (UInt64)UpperBound);
+            _uniformInt64Original = UniformInt64.Create(LowerBound, UpperBound);
 
             _uniformSingle = Uniform.New((Single)LowerBound, (Single)UpperBound);
             _uniformDouble = Uniform.New((Double)LowerBound, (Double)UpperBound);

          
@@ 74,6 78,15 @@ namespace RandN.Benchmarks
         }
 
         [Benchmark]
+        public Int32 SampleInt32Original()
+        {
+            Int32 sum = 0;
+            for (Int32 i = 0; i < Iterations; i++)
+                sum = unchecked(sum + _uniformInt32Original.Sample(_rng));
+            return sum;
+        }
+
+        [Benchmark]
         public Int64 SampleInt64()
         {
             Int64 sum = 0;

          
@@ 83,6 96,15 @@ namespace RandN.Benchmarks
         }
 
         [Benchmark]
+        public Int64 SampleInt64Original()
+        {
+            Int64 sum = 0;
+            for (Int32 i = 0; i < Iterations; i++)
+                sum = unchecked(sum + _uniformInt64Original.Sample(_rng));
+            return sum;
+        }
+
+        [Benchmark]
         public UInt32 SampleByte()
         {
             UInt32 sum = 0;

          
M src/RandN/Distributions/Uniform.cs +161 -168
@@ 21,174 21,6 @@ namespace RandN.Distributions
         /// <exception cref="ArgumentOutOfRangeException">
         /// Thrown when <paramref name="low"/> is greater than or equal to <paramref name="high"/>.
         /// </exception>
-        public static UniformSByte New(SByte low, SByte high) => UniformSByte.Create(low, high);
-
-        /// <summary>
-        /// Creates uniform distribution in the interval [low, high], inclusive of low and high.
-        /// </summary>
-        /// <param name="low">The inclusive lower bound.</param>
-        /// <param name="high">The inclusive upper bound.</param>
-        /// <exception cref="ArgumentOutOfRangeException">
-        /// Thrown when <paramref name="low"/> is greater than <paramref name="high"/>.
-        /// </exception>
-        public static UniformSByte NewInclusive(SByte low, SByte high) => UniformSByte.CreateInclusive(low, high);
-
-
-        /// <summary>
-        /// Creates uniform distribution in the interval [low, high), inclusive of low and exclusive of high.
-        /// </summary>
-        /// <param name="low">The inclusive lower bound.</param>
-        /// <param name="high">The exclusive upper bound.</param>
-        /// <exception cref="ArgumentOutOfRangeException">
-        /// Thrown when <paramref name="low"/> is greater than or equal to <paramref name="high"/>.
-        /// </exception>
-        public static UniformInt16 New(Int16 low, Int16 high) => UniformInt16.Create(low, high);
-
-        /// <summary>
-        /// Creates uniform distribution in the interval [low, high], inclusive of low and high.
-        /// </summary>
-        /// <param name="low">The inclusive lower bound.</param>
-        /// <param name="high">The inclusive upper bound.</param>
-        /// <exception cref="ArgumentOutOfRangeException">
-        /// Thrown when <paramref name="low"/> is greater than <paramref name="high"/>.
-        /// </exception>
-        public static UniformInt16 NewInclusive(Int16 low, Int16 high) => UniformInt16.CreateInclusive(low, high);
-
-
-        /// <summary>
-        /// Creates uniform distribution in the interval [low, high), inclusive of low and exclusive of high.
-        /// </summary>
-        /// <param name="low">The inclusive lower bound.</param>
-        /// <param name="high">The exclusive upper bound.</param>
-        /// <exception cref="ArgumentOutOfRangeException">
-        /// Thrown when <paramref name="low"/> is greater than or equal to <paramref name="high"/>.
-        /// </exception>
-        public static UniformInt32 New(Int32 low, Int32 high) => UniformInt32.Create(low, high);
-
-        /// <summary>
-        /// Creates uniform distribution in the interval [low, high], inclusive of low and high.
-        /// </summary>
-        /// <param name="low">The inclusive lower bound.</param>
-        /// <param name="high">The inclusive upper bound.</param>
-        /// <exception cref="ArgumentOutOfRangeException">
-        /// Thrown when <paramref name="low"/> is greater than <paramref name="high"/>.
-        /// </exception>
-        public static UniformInt32 NewInclusive(Int32 low, Int32 high) => UniformInt32.CreateInclusive(low, high);
-
-
-        /// <summary>
-        /// Creates uniform distribution in the interval [low, high), inclusive of low and exclusive of high.
-        /// </summary>
-        /// <param name="low">The inclusive lower bound.</param>
-        /// <param name="high">The exclusive upper bound.</param>
-        /// <exception cref="ArgumentOutOfRangeException">
-        /// Thrown when <paramref name="low"/> is greater than or equal to <paramref name="high"/>.
-        /// </exception>
-        public static UniformInt64 New(Int64 low, Int64 high) => UniformInt64.Create(low, high);
-
-        /// <summary>
-        /// Creates uniform distribution in the interval [low, high], inclusive of low and high.
-        /// </summary>
-        /// <param name="low">The inclusive lower bound.</param>
-        /// <param name="high">The inclusive upper bound.</param>
-        /// <exception cref="ArgumentOutOfRangeException">
-        /// Thrown when <paramref name="low"/> is greater than <paramref name="high"/>.
-        /// </exception>
-        public static UniformInt64 NewInclusive(Int64 low, Int64 high) => UniformInt64.CreateInclusive(low, high);
-
-
-        /// <summary>
-        /// Creates uniform distribution in the interval [low, high), inclusive of low and exclusive of high.
-        /// </summary>
-        /// <param name="low">The inclusive lower bound.</param>
-        /// <param name="high">The exclusive upper bound.</param>
-        /// <exception cref="ArgumentOutOfRangeException">
-        /// Thrown when <paramref name="low"/> is greater than or equal to <paramref name="high"/>.
-        /// </exception>
-        public static UniformByte New(Byte low, Byte high) => UniformByte.Create(low, high);
-
-        /// <summary>
-        /// Creates uniform distribution in the interval [low, high], inclusive of low and high.
-        /// </summary>
-        /// <param name="low">The inclusive lower bound.</param>
-        /// <param name="high">The inclusive upper bound.</param>
-        /// <exception cref="ArgumentOutOfRangeException">
-        /// Thrown when <paramref name="low"/> is greater than <paramref name="high"/>.
-        /// </exception>
-        public static UniformByte NewInclusive(Byte low, Byte high) => UniformByte.CreateInclusive(low, high);
-
-
-        /// <summary>
-        /// Creates uniform distribution in the interval [low, high), inclusive of low and exclusive of high.
-        /// </summary>
-        /// <param name="low">The inclusive lower bound.</param>
-        /// <param name="high">The exclusive upper bound.</param>
-        /// <exception cref="ArgumentOutOfRangeException">
-        /// Thrown when <paramref name="low"/> is greater than or equal to <paramref name="high"/>.
-        /// </exception>
-        public static UniformUInt16 New(UInt16 low, UInt16 high) => UniformUInt16.Create(low, high);
-
-        /// <summary>
-        /// Creates uniform distribution in the interval [low, high], inclusive of low and high.
-        /// </summary>
-        /// <param name="low">The inclusive lower bound.</param>
-        /// <param name="high">The inclusive upper bound.</param>
-        /// <exception cref="ArgumentOutOfRangeException">
-        /// Thrown when <paramref name="low"/> is greater than <paramref name="high"/>.
-        /// </exception>
-        public static UniformUInt16 NewInclusive(UInt16 low, UInt16 high) => UniformUInt16.CreateInclusive(low, high);
-
-
-        /// <summary>
-        /// Creates uniform distribution in the interval [low, high), inclusive of low and exclusive of high.
-        /// </summary>
-        /// <param name="low">The inclusive lower bound.</param>
-        /// <param name="high">The exclusive upper bound.</param>
-        /// <exception cref="ArgumentOutOfRangeException">
-        /// Thrown when <paramref name="low"/> is greater than or equal to <paramref name="high"/>.
-        /// </exception>
-        public static UniformUInt32 New(UInt32 low, UInt32 high) => UniformUInt32.Create(low, high);
-
-        /// <summary>
-        /// Creates uniform distribution in the interval [low, high], inclusive of low and high.
-        /// </summary>
-        /// <param name="low">The inclusive lower bound.</param>
-        /// <param name="high">The inclusive upper bound.</param>
-        /// <exception cref="ArgumentOutOfRangeException">
-        /// Thrown when <paramref name="low"/> is greater than <paramref name="high"/>.
-        /// </exception>
-        public static UniformUInt32 NewInclusive(UInt32 low, UInt32 high) => UniformUInt32.CreateInclusive(low, high);
-
-
-        /// <summary>
-        /// Creates uniform distribution in the interval [low, high), inclusive of low and exclusive of high.
-        /// </summary>
-        /// <param name="low">The inclusive lower bound.</param>
-        /// <param name="high">The exclusive upper bound.</param>
-        /// <exception cref="ArgumentOutOfRangeException">
-        /// Thrown when <paramref name="low"/> is greater than or equal to <paramref name="high"/>.
-        /// </exception>
-        public static UniformUInt64 New(UInt64 low, UInt64 high) => UniformUInt64.Create(low, high);
-
-        /// <summary>
-        /// Creates uniform distribution in the interval [low, high], inclusive of low and high.
-        /// </summary>
-        /// <param name="low">The inclusive lower bound.</param>
-        /// <param name="high">The inclusive upper bound.</param>
-        /// <exception cref="ArgumentOutOfRangeException">
-        /// Thrown when <paramref name="low"/> is greater than <paramref name="high"/>.
-        /// </exception>
-        public static UniformUInt64 NewInclusive(UInt64 low, UInt64 high) => UniformUInt64.CreateInclusive(low, high);
-
-
-        /// <summary>
-        /// Creates uniform distribution in the interval [low, high), inclusive of low and exclusive of high.
-        /// </summary>
-        /// <param name="low">The inclusive lower bound.</param>
-        /// <param name="high">The exclusive upper bound.</param>
-        /// <exception cref="ArgumentOutOfRangeException">
-        /// Thrown when <paramref name="low"/> is greater than or equal to <paramref name="high"/>.
-        /// </exception>
         public static UniformTimeSpan New(TimeSpan low, TimeSpan high) => UniformTimeSpan.Create(low, high);
 
         /// <summary>

          
@@ 223,6 55,167 @@ namespace RandN.Distributions
         public static UniformDecimal NewInclusive(Decimal low, Decimal high) => UniformDecimal.CreateInclusive(low, high);
 
 
+
+        /// <summary>
+        /// Creates uniform distribution in the interval [low, high), inclusive of low and exclusive of high.
+        /// </summary>
+        /// <param name="low">The inclusive lower bound.</param>
+        /// <param name="high">The exclusive upper bound.</param>
+        /// <exception cref="ArgumentOutOfRangeException">
+        /// Thrown when <paramref name="low"/> is greater than or equal to <paramref name="high"/>.
+        /// </exception>
+        public static UniformInt<SByte> New(SByte low, SByte high) => UniformInt.Create(low, high);
+
+        /// <summary>
+        /// Creates uniform distribution in the interval [low, high], inclusive of low and high.
+        /// </summary>
+        /// <param name="low">The inclusive lower bound.</param>
+        /// <param name="high">The inclusive upper bound.</param>
+        /// <exception cref="ArgumentOutOfRangeException">
+        /// Thrown when <paramref name="low"/> is greater than <paramref name="high"/>.
+        /// </exception>
+        public static UniformInt<SByte> NewInclusive(SByte low, SByte high) => UniformInt.CreateInclusive(low, high);
+
+        /// <summary>
+        /// Creates uniform distribution in the interval [low, high), inclusive of low and exclusive of high.
+        /// </summary>
+        /// <param name="low">The inclusive lower bound.</param>
+        /// <param name="high">The exclusive upper bound.</param>
+        /// <exception cref="ArgumentOutOfRangeException">
+        /// Thrown when <paramref name="low"/> is greater than or equal to <paramref name="high"/>.
+        /// </exception>
+        public static UniformInt<Int16> New(Int16 low, Int16 high) => UniformInt.Create(low, high);
+
+        /// <summary>
+        /// Creates uniform distribution in the interval [low, high], inclusive of low and high.
+        /// </summary>
+        /// <param name="low">The inclusive lower bound.</param>
+        /// <param name="high">The inclusive upper bound.</param>
+        /// <exception cref="ArgumentOutOfRangeException">
+        /// Thrown when <paramref name="low"/> is greater than <paramref name="high"/>.
+        /// </exception>
+        public static UniformInt<Int16> NewInclusive(Int16 low, Int16 high) => UniformInt.CreateInclusive(low, high);
+
+        /// <summary>
+        /// Creates uniform distribution in the interval [low, high), inclusive of low and exclusive of high.
+        /// </summary>
+        /// <param name="low">The inclusive lower bound.</param>
+        /// <param name="high">The exclusive upper bound.</param>
+        /// <exception cref="ArgumentOutOfRangeException">
+        /// Thrown when <paramref name="low"/> is greater than or equal to <paramref name="high"/>.
+        /// </exception>
+        public static UniformInt<Int32> New(Int32 low, Int32 high) => UniformInt.Create(low, high);
+
+        /// <summary>
+        /// Creates uniform distribution in the interval [low, high], inclusive of low and high.
+        /// </summary>
+        /// <param name="low">The inclusive lower bound.</param>
+        /// <param name="high">The inclusive upper bound.</param>
+        /// <exception cref="ArgumentOutOfRangeException">
+        /// Thrown when <paramref name="low"/> is greater than <paramref name="high"/>.
+        /// </exception>
+        public static UniformInt<Int32> NewInclusive(Int32 low, Int32 high) => UniformInt.CreateInclusive(low, high);
+
+        /// <summary>
+        /// Creates uniform distribution in the interval [low, high), inclusive of low and exclusive of high.
+        /// </summary>
+        /// <param name="low">The inclusive lower bound.</param>
+        /// <param name="high">The exclusive upper bound.</param>
+        /// <exception cref="ArgumentOutOfRangeException">
+        /// Thrown when <paramref name="low"/> is greater than or equal to <paramref name="high"/>.
+        /// </exception>
+        public static UniformInt<Int64> New(Int64 low, Int64 high) => UniformInt.Create(low, high);
+
+        /// <summary>
+        /// Creates uniform distribution in the interval [low, high], inclusive of low and high.
+        /// </summary>
+        /// <param name="low">The inclusive lower bound.</param>
+        /// <param name="high">The inclusive upper bound.</param>
+        /// <exception cref="ArgumentOutOfRangeException">
+        /// Thrown when <paramref name="low"/> is greater than <paramref name="high"/>.
+        /// </exception>
+        public static UniformInt<Int64> NewInclusive(Int64 low, Int64 high) => UniformInt.CreateInclusive(low, high);
+
+        /// <summary>
+        /// Creates uniform distribution in the interval [low, high), inclusive of low and exclusive of high.
+        /// </summary>
+        /// <param name="low">The inclusive lower bound.</param>
+        /// <param name="high">The exclusive upper bound.</param>
+        /// <exception cref="ArgumentOutOfRangeException">
+        /// Thrown when <paramref name="low"/> is greater than or equal to <paramref name="high"/>.
+        /// </exception>
+        public static UniformInt<Byte> New(Byte low, Byte high) => UniformInt.Create(low, high);
+
+        /// <summary>
+        /// Creates uniform distribution in the interval [low, high], inclusive of low and high.
+        /// </summary>
+        /// <param name="low">The inclusive lower bound.</param>
+        /// <param name="high">The inclusive upper bound.</param>
+        /// <exception cref="ArgumentOutOfRangeException">
+        /// Thrown when <paramref name="low"/> is greater than <paramref name="high"/>.
+        /// </exception>
+        public static UniformInt<Byte> NewInclusive(Byte low, Byte high) => UniformInt.CreateInclusive(low, high);
+
+        /// <summary>
+        /// Creates uniform distribution in the interval [low, high), inclusive of low and exclusive of high.
+        /// </summary>
+        /// <param name="low">The inclusive lower bound.</param>
+        /// <param name="high">The exclusive upper bound.</param>
+        /// <exception cref="ArgumentOutOfRangeException">
+        /// Thrown when <paramref name="low"/> is greater than or equal to <paramref name="high"/>.
+        /// </exception>
+        public static UniformInt<UInt16> New(UInt16 low, UInt16 high) => UniformInt.Create(low, high);
+
+        /// <summary>
+        /// Creates uniform distribution in the interval [low, high], inclusive of low and high.
+        /// </summary>
+        /// <param name="low">The inclusive lower bound.</param>
+        /// <param name="high">The inclusive upper bound.</param>
+        /// <exception cref="ArgumentOutOfRangeException">
+        /// Thrown when <paramref name="low"/> is greater than <paramref name="high"/>.
+        /// </exception>
+        public static UniformInt<UInt16> NewInclusive(UInt16 low, UInt16 high) => UniformInt.CreateInclusive(low, high);
+
+        /// <summary>
+        /// Creates uniform distribution in the interval [low, high), inclusive of low and exclusive of high.
+        /// </summary>
+        /// <param name="low">The inclusive lower bound.</param>
+        /// <param name="high">The exclusive upper bound.</param>
+        /// <exception cref="ArgumentOutOfRangeException">
+        /// Thrown when <paramref name="low"/> is greater than or equal to <paramref name="high"/>.
+        /// </exception>
+        public static UniformInt<UInt32> New(UInt32 low, UInt32 high) => UniformInt.Create(low, high);
+
+        /// <summary>
+        /// Creates uniform distribution in the interval [low, high], inclusive of low and high.
+        /// </summary>
+        /// <param name="low">The inclusive lower bound.</param>
+        /// <param name="high">The inclusive upper bound.</param>
+        /// <exception cref="ArgumentOutOfRangeException">
+        /// Thrown when <paramref name="low"/> is greater than <paramref name="high"/>.
+        /// </exception>
+        public static UniformInt<UInt32> NewInclusive(UInt32 low, UInt32 high) => UniformInt.CreateInclusive(low, high);
+
+        /// <summary>
+        /// Creates uniform distribution in the interval [low, high), inclusive of low and exclusive of high.
+        /// </summary>
+        /// <param name="low">The inclusive lower bound.</param>
+        /// <param name="high">The exclusive upper bound.</param>
+        /// <exception cref="ArgumentOutOfRangeException">
+        /// Thrown when <paramref name="low"/> is greater than or equal to <paramref name="high"/>.
+        /// </exception>
+        public static UniformInt<UInt64> New(UInt64 low, UInt64 high) => UniformInt.Create(low, high);
+
+        /// <summary>
+        /// Creates uniform distribution in the interval [low, high], inclusive of low and high.
+        /// </summary>
+        /// <param name="low">The inclusive lower bound.</param>
+        /// <param name="high">The inclusive upper bound.</param>
+        /// <exception cref="ArgumentOutOfRangeException">
+        /// Thrown when <paramref name="low"/> is greater than <paramref name="high"/>.
+        /// </exception>
+        public static UniformInt<UInt64> NewInclusive(UInt64 low, UInt64 high) => UniformInt.CreateInclusive(low, high);
+
         /// <summary>
         /// Creates uniform distribution in the interval [low, high), inclusive of low and exclusive of high.
         /// </summary>

          
M src/RandN/Distributions/Uniform.tt +32 -2
@@ 4,6 4,11 @@ 
 <#
 var types = new Type[]
 {
+    typeof(TimeSpan),
+    typeof(Decimal),
+};
+var genericTypes = new Type[]
+{
     typeof(SByte),
     typeof(Int16),
     typeof(Int32),

          
@@ 12,8 17,6 @@ var types = new Type[]
     typeof(UInt16),
     typeof(UInt32),
     typeof(UInt64),
-    typeof(TimeSpan),
-    typeof(Decimal),
 };
 #>
 using System;

          
@@ 54,6 57,33 @@ foreach (var type in types)
 <#
 }
 #>
+<#
+foreach (var type in genericTypes)
+{
+    String typeName = type.Name;
+#>
+        /// <summary>
+        /// Creates uniform distribution in the interval [low, high), inclusive of low and exclusive of high.
+        /// </summary>
+        /// <param name="low">The inclusive lower bound.</param>
+        /// <param name="high">The exclusive upper bound.</param>
+        /// <exception cref="ArgumentOutOfRangeException">
+        /// Thrown when <paramref name="low"/> is greater than or equal to <paramref name="high"/>.
+        /// </exception>
+        public static UniformInt<<#= typeName #>> New(<#= typeName #> low, <#= typeName #> high) => UniformInt.Create(low, high);
+
+        /// <summary>
+        /// Creates uniform distribution in the interval [low, high], inclusive of low and high.
+        /// </summary>
+        /// <param name="low">The inclusive lower bound.</param>
+        /// <param name="high">The inclusive upper bound.</param>
+        /// <exception cref="ArgumentOutOfRangeException">
+        /// Thrown when <paramref name="low"/> is greater than <paramref name="high"/>.
+        /// </exception>
+        public static UniformInt<<#= typeName #>> NewInclusive(<#= typeName #> low, <#= typeName #> high) => UniformInt.CreateInclusive(low, high);
+<#
+}
+#>
         /// <summary>
         /// Creates uniform distribution in the interval [low, high), inclusive of low and exclusive of high.
         /// </summary>

          
M src/RandN/Distributions/UniformFloat.cs +1 -1
@@ 178,7 178,7 @@ namespace RandN.Distributions
     /// Use of any other type results in a runtime exception.
     /// </summary>
     public readonly struct UniformFloat<T> : IDistribution<T>
-        // We're extremely restrictive here to discourage people from trying to use non-supported type for T
+        // We're extremely restrictive here to discourage people from trying to use unsupported types for T
         where T : struct, IComparable, IComparable<T>, IConvertible, IEquatable<T>, IFormattable
     {
         private readonly T _low;

          
M src/RandN/Distributions/UniformFloat.tt +1 -1
@@ 111,7 111,7 @@ namespace RandN.Distributions
     /// Use of any other type results in a runtime exception.
     /// </summary>
     public readonly struct UniformFloat<T> : IDistribution<T>
-        // We're extremely restrictive here to discourage people from trying to use non-supported type for T
+        // We're extremely restrictive here to discourage people from trying to use unsupported types for T
         where T : struct, IComparable, IComparable<T>, IConvertible, IEquatable<T>, IFormattable
     {
         private readonly T _low;

          
M src/RandN/Distributions/UniformInt.cs +370 -0
@@ 4,10 4,380 @@ 
 
 
 using System;
+using Genumerics;
 
 /*** This file is auto generated - any changes made here will be lost. ***/
 namespace RandN.Distributions
 {
+    internal static class UniformInt
+    {
+
+
+        /// <summary>
+        /// Creates a <see cref="UniformSByte" /> with an exclusive upper bound. Should not
+        /// be used directly; instead, use <see cref="Uniform.New(SByte, SByte)" />.
+        /// </summary>
+        /// <exception cref="ArgumentOutOfRangeException">
+        /// Thrown when <paramref name="low"/> is greater than or equal to <paramref name="high"/>.
+        /// </exception>
+        public static UniformInt<SByte> Create(SByte low, SByte high)
+        {
+            if (low >= high)
+                throw new ArgumentOutOfRangeException(nameof(high), $"{nameof(high)} ({high}) must be higher than {nameof(low)} ({low}).");
+
+            return CreateInclusive(low, (SByte)(high - 1));
+        }
+        
+
+        /// <summary>
+        /// Creates a <see cref="UniformSByte" /> with an exclusive lower bound. Should not
+        /// be used directly; instead, use <see cref="Uniform.NewInclusive(SByte, SByte)" />.
+        /// </summary>
+        /// <exception cref="ArgumentOutOfRangeException">
+        /// Thrown when <paramref name="low"/> is greater than <paramref name="high"/>.
+        /// </exception>
+        public static UniformInt<SByte> CreateInclusive(SByte low, SByte high)
+        {
+            if (low > high)
+                throw new ArgumentOutOfRangeException(nameof(high), $"{nameof(high)} ({high}) must be higher than or equal to {nameof(low)} ({low}).");
+
+            var unsignedMax = UInt32.MaxValue;
+            var range = unchecked((UInt32)(high - low + 1));
+            var intsToReject = range == 0 ? 0 : (unsignedMax - range + 1) % range;
+
+            return new UniformInt<SByte>(low, range, (Byte)intsToReject);
+        }
+
+
+        /// <summary>
+        /// Creates a <see cref="UniformInt16" /> with an exclusive upper bound. Should not
+        /// be used directly; instead, use <see cref="Uniform.New(Int16, Int16)" />.
+        /// </summary>
+        /// <exception cref="ArgumentOutOfRangeException">
+        /// Thrown when <paramref name="low"/> is greater than or equal to <paramref name="high"/>.
+        /// </exception>
+        public static UniformInt<Int16> Create(Int16 low, Int16 high)
+        {
+            if (low >= high)
+                throw new ArgumentOutOfRangeException(nameof(high), $"{nameof(high)} ({high}) must be higher than {nameof(low)} ({low}).");
+
+            return CreateInclusive(low, (Int16)(high - 1));
+        }
+        
+
+        /// <summary>
+        /// Creates a <see cref="UniformInt16" /> with an exclusive lower bound. Should not
+        /// be used directly; instead, use <see cref="Uniform.NewInclusive(Int16, Int16)" />.
+        /// </summary>
+        /// <exception cref="ArgumentOutOfRangeException">
+        /// Thrown when <paramref name="low"/> is greater than <paramref name="high"/>.
+        /// </exception>
+        public static UniformInt<Int16> CreateInclusive(Int16 low, Int16 high)
+        {
+            if (low > high)
+                throw new ArgumentOutOfRangeException(nameof(high), $"{nameof(high)} ({high}) must be higher than or equal to {nameof(low)} ({low}).");
+
+            var unsignedMax = UInt32.MaxValue;
+            var range = unchecked((UInt32)(high - low + 1));
+            var intsToReject = range == 0 ? 0 : (unsignedMax - range + 1) % range;
+
+            return new UniformInt<Int16>(low, range, (UInt16)intsToReject);
+        }
+
+
+        /// <summary>
+        /// Creates a <see cref="UniformInt32" /> with an exclusive upper bound. Should not
+        /// be used directly; instead, use <see cref="Uniform.New(Int32, Int32)" />.
+        /// </summary>
+        /// <exception cref="ArgumentOutOfRangeException">
+        /// Thrown when <paramref name="low"/> is greater than or equal to <paramref name="high"/>.
+        /// </exception>
+        public static UniformInt<Int32> Create(Int32 low, Int32 high)
+        {
+            if (low >= high)
+                throw new ArgumentOutOfRangeException(nameof(high), $"{nameof(high)} ({high}) must be higher than {nameof(low)} ({low}).");
+
+            return CreateInclusive(low, (Int32)(high - 1));
+        }
+        
+
+        /// <summary>
+        /// Creates a <see cref="UniformInt32" /> with an exclusive lower bound. Should not
+        /// be used directly; instead, use <see cref="Uniform.NewInclusive(Int32, Int32)" />.
+        /// </summary>
+        /// <exception cref="ArgumentOutOfRangeException">
+        /// Thrown when <paramref name="low"/> is greater than <paramref name="high"/>.
+        /// </exception>
+        public static UniformInt<Int32> CreateInclusive(Int32 low, Int32 high)
+        {
+            if (low > high)
+                throw new ArgumentOutOfRangeException(nameof(high), $"{nameof(high)} ({high}) must be higher than or equal to {nameof(low)} ({low}).");
+
+            var unsignedMax = UInt32.MaxValue;
+            var range = unchecked((UInt32)(high - low + 1));
+            var intsToReject = range == 0 ? 0 : (unsignedMax - range + 1) % range;
+
+            return new UniformInt<Int32>(low, range, (UInt32)intsToReject);
+        }
+
+
+        /// <summary>
+        /// Creates a <see cref="UniformInt64" /> with an exclusive upper bound. Should not
+        /// be used directly; instead, use <see cref="Uniform.New(Int64, Int64)" />.
+        /// </summary>
+        /// <exception cref="ArgumentOutOfRangeException">
+        /// Thrown when <paramref name="low"/> is greater than or equal to <paramref name="high"/>.
+        /// </exception>
+        public static UniformInt<Int64> Create(Int64 low, Int64 high)
+        {
+            if (low >= high)
+                throw new ArgumentOutOfRangeException(nameof(high), $"{nameof(high)} ({high}) must be higher than {nameof(low)} ({low}).");
+
+            return CreateInclusive(low, (Int64)(high - 1));
+        }
+        
+
+        /// <summary>
+        /// Creates a <see cref="UniformInt64" /> with an exclusive lower bound. Should not
+        /// be used directly; instead, use <see cref="Uniform.NewInclusive(Int64, Int64)" />.
+        /// </summary>
+        /// <exception cref="ArgumentOutOfRangeException">
+        /// Thrown when <paramref name="low"/> is greater than <paramref name="high"/>.
+        /// </exception>
+        public static UniformInt<Int64> CreateInclusive(Int64 low, Int64 high)
+        {
+            if (low > high)
+                throw new ArgumentOutOfRangeException(nameof(high), $"{nameof(high)} ({high}) must be higher than or equal to {nameof(low)} ({low}).");
+
+            var unsignedMax = UInt64.MaxValue;
+            var range = unchecked((UInt64)(high - low + 1));
+            var intsToReject = range == 0 ? 0 : (unsignedMax - range + 1) % range;
+
+            return new UniformInt<Int64>(low, range, (UInt64)intsToReject);
+        }
+
+
+        /// <summary>
+        /// Creates a <see cref="UniformByte" /> with an exclusive upper bound. Should not
+        /// be used directly; instead, use <see cref="Uniform.New(Byte, Byte)" />.
+        /// </summary>
+        /// <exception cref="ArgumentOutOfRangeException">
+        /// Thrown when <paramref name="low"/> is greater than or equal to <paramref name="high"/>.
+        /// </exception>
+        public static UniformInt<Byte> Create(Byte low, Byte high)
+        {
+            if (low >= high)
+                throw new ArgumentOutOfRangeException(nameof(high), $"{nameof(high)} ({high}) must be higher than {nameof(low)} ({low}).");
+
+            return CreateInclusive(low, (Byte)(high - 1));
+        }
+        
+
+        /// <summary>
+        /// Creates a <see cref="UniformByte" /> with an exclusive lower bound. Should not
+        /// be used directly; instead, use <see cref="Uniform.NewInclusive(Byte, Byte)" />.
+        /// </summary>
+        /// <exception cref="ArgumentOutOfRangeException">
+        /// Thrown when <paramref name="low"/> is greater than <paramref name="high"/>.
+        /// </exception>
+        public static UniformInt<Byte> CreateInclusive(Byte low, Byte high)
+        {
+            if (low > high)
+                throw new ArgumentOutOfRangeException(nameof(high), $"{nameof(high)} ({high}) must be higher than or equal to {nameof(low)} ({low}).");
+
+            var unsignedMax = UInt32.MaxValue;
+            var range = unchecked((UInt32)(high - low + 1));
+            var intsToReject = range == 0 ? 0 : (unsignedMax - range + 1) % range;
+
+            return new UniformInt<Byte>(low, range, (Byte)intsToReject);
+        }
+
+
+        /// <summary>
+        /// Creates a <see cref="UniformUInt16" /> with an exclusive upper bound. Should not
+        /// be used directly; instead, use <see cref="Uniform.New(UInt16, UInt16)" />.
+        /// </summary>
+        /// <exception cref="ArgumentOutOfRangeException">
+        /// Thrown when <paramref name="low"/> is greater than or equal to <paramref name="high"/>.
+        /// </exception>
+        public static UniformInt<UInt16> Create(UInt16 low, UInt16 high)
+        {
+            if (low >= high)
+                throw new ArgumentOutOfRangeException(nameof(high), $"{nameof(high)} ({high}) must be higher than {nameof(low)} ({low}).");
+
+            return CreateInclusive(low, (UInt16)(high - 1));
+        }
+        
+
+        /// <summary>
+        /// Creates a <see cref="UniformUInt16" /> with an exclusive lower bound. Should not
+        /// be used directly; instead, use <see cref="Uniform.NewInclusive(UInt16, UInt16)" />.
+        /// </summary>
+        /// <exception cref="ArgumentOutOfRangeException">
+        /// Thrown when <paramref name="low"/> is greater than <paramref name="high"/>.
+        /// </exception>
+        public static UniformInt<UInt16> CreateInclusive(UInt16 low, UInt16 high)
+        {
+            if (low > high)
+                throw new ArgumentOutOfRangeException(nameof(high), $"{nameof(high)} ({high}) must be higher than or equal to {nameof(low)} ({low}).");
+
+            var unsignedMax = UInt32.MaxValue;
+            var range = unchecked((UInt32)(high - low + 1));
+            var intsToReject = range == 0 ? 0 : (unsignedMax - range + 1) % range;
+
+            return new UniformInt<UInt16>(low, range, (UInt16)intsToReject);
+        }
+
+
+        /// <summary>
+        /// Creates a <see cref="UniformUInt32" /> with an exclusive upper bound. Should not
+        /// be used directly; instead, use <see cref="Uniform.New(UInt32, UInt32)" />.
+        /// </summary>
+        /// <exception cref="ArgumentOutOfRangeException">
+        /// Thrown when <paramref name="low"/> is greater than or equal to <paramref name="high"/>.
+        /// </exception>
+        public static UniformInt<UInt32> Create(UInt32 low, UInt32 high)
+        {
+            if (low >= high)
+                throw new ArgumentOutOfRangeException(nameof(high), $"{nameof(high)} ({high}) must be higher than {nameof(low)} ({low}).");
+
+            return CreateInclusive(low, (UInt32)(high - 1));
+        }
+        
+
+        /// <summary>
+        /// Creates a <see cref="UniformUInt32" /> with an exclusive lower bound. Should not
+        /// be used directly; instead, use <see cref="Uniform.NewInclusive(UInt32, UInt32)" />.
+        /// </summary>
+        /// <exception cref="ArgumentOutOfRangeException">
+        /// Thrown when <paramref name="low"/> is greater than <paramref name="high"/>.
+        /// </exception>
+        public static UniformInt<UInt32> CreateInclusive(UInt32 low, UInt32 high)
+        {
+            if (low > high)
+                throw new ArgumentOutOfRangeException(nameof(high), $"{nameof(high)} ({high}) must be higher than or equal to {nameof(low)} ({low}).");
+
+            var unsignedMax = UInt32.MaxValue;
+            var range = unchecked((UInt32)(high - low + 1));
+            var intsToReject = range == 0 ? 0 : (unsignedMax - range + 1) % range;
+
+            return new UniformInt<UInt32>(low, range, (UInt32)intsToReject);
+        }
+
+
+        /// <summary>
+        /// Creates a <see cref="UniformUInt64" /> with an exclusive upper bound. Should not
+        /// be used directly; instead, use <see cref="Uniform.New(UInt64, UInt64)" />.
+        /// </summary>
+        /// <exception cref="ArgumentOutOfRangeException">
+        /// Thrown when <paramref name="low"/> is greater than or equal to <paramref name="high"/>.
+        /// </exception>
+        public static UniformInt<UInt64> Create(UInt64 low, UInt64 high)
+        {
+            if (low >= high)
+                throw new ArgumentOutOfRangeException(nameof(high), $"{nameof(high)} ({high}) must be higher than {nameof(low)} ({low}).");
+
+            return CreateInclusive(low, (UInt64)(high - 1));
+        }
+        
+
+        /// <summary>
+        /// Creates a <see cref="UniformUInt64" /> with an exclusive lower bound. Should not
+        /// be used directly; instead, use <see cref="Uniform.NewInclusive(UInt64, UInt64)" />.
+        /// </summary>
+        /// <exception cref="ArgumentOutOfRangeException">
+        /// Thrown when <paramref name="low"/> is greater than <paramref name="high"/>.
+        /// </exception>
+        public static UniformInt<UInt64> CreateInclusive(UInt64 low, UInt64 high)
+        {
+            if (low > high)
+                throw new ArgumentOutOfRangeException(nameof(high), $"{nameof(high)} ({high}) must be higher than or equal to {nameof(low)} ({low}).");
+
+            var unsignedMax = UInt64.MaxValue;
+            var range = unchecked((UInt64)(high - low + 1));
+            var intsToReject = range == 0 ? 0 : (unsignedMax - range + 1) % range;
+
+            return new UniformInt<UInt64>(low, range, (UInt64)intsToReject);
+        }
+
+    }
+
+    /// <summary>
+    /// Implements a Uniform <see cref="IDistribution{TResult}"/> for integral types such as <see cref="Int32" /> and <see cref="UInt64" />.
+    /// Use of any other type results in a runtime exception.
+    /// </summary>
+    public readonly struct UniformInt<T> : IPortableDistribution<T>
+        // We're extremely restrictive here to discourage people from trying to use unsupported types for T
+        where T : struct, IComparable, IComparable<T>, IConvertible, IEquatable<T>, IFormattable
+    {
+        private readonly Number<T> _low;
+        private readonly UInt64 _range;
+        private readonly UInt64 _zone;
+        
+        internal UniformInt(Number<T> low, UInt64 range, UInt64 zone)
+        {
+            _low = low;
+            _range = range;
+            _zone = zone;
+        }
+
+        /// <inheritdoc />
+        public T Sample<TRng>(TRng rng) where TRng : notnull, IRng
+        {
+            if (typeof(T) == typeof(Int32))
+            {
+                var unsigned = rng.NextUInt32();
+                if (_range == 0) // 0 is a special case where we sample the entire range.
+                    return unchecked(Number.Convert<UInt32, T>(unsigned));
+
+                var zone = UInt32.MaxValue - _zone;
+
+                while (unsigned > zone)
+                {
+                    unsigned = rng.NextUInt32();
+                }
+
+                return unchecked(Number.Convert<UInt32, T>(unsigned % (UInt32)_range) + _low);
+            }
+            else
+            {
+                var unsigned = rng.NextUInt64();
+                if (_range == 0) // 0 is a special case where we sample the entire range.
+                    return Number.Convert<UInt64, T>(unsigned);
+
+                var zone = UInt64.MaxValue - _zone;
+
+                while (unsigned > zone)
+                {
+                    unsigned = rng.NextUInt64();
+                }
+
+                return unchecked(Number.Convert<UInt64, T>(unsigned % _range) + _low);
+            }
+        }
+
+        /// <inheritdoc />
+        public Boolean TrySample<TRng>(TRng rng, out T result) where TRng : notnull, IRng
+        {
+            var unsigned = rng.NextUInt64();
+            if (_range == 0) // 0 is a special case where we sample the entire range.
+            {
+                result = Number.Convert<UInt64, T>(unsigned);
+                return true;
+            }
+
+            var zone = UInt64.MaxValue - _zone;
+
+            if (unsigned > zone)
+            {
+                result = default;
+                return false;
+            }
+
+            result = unchecked(Number.Convert<UInt64, T>(unsigned % _range) + _low);
+            return true;
+        }
+    }
 
     /// <summary>
     /// A uniform distribution of type <see cref="SByte" />.

          
M src/RandN/Distributions/UniformInt.tt +125 -0
@@ 16,10 16,135 @@ var types = new (Type type, Type unsigne
 };
 #>
 using System;
+using Genumerics;
 
 /*** This file is auto generated - any changes made here will be lost. ***/
 namespace RandN.Distributions
 {
+    internal static class UniformInt
+    {
+<# foreach (var param in types)
+{
+    String type = param.type.Name;
+    String unsigned = param.unsigned.Name;
+    String ularge = param.ularge.Name;
+#>
+
+        /// <summary>
+        /// Creates a <see cref="Uniform<#= type #>" /> with an exclusive upper bound. Should not
+        /// be used directly; instead, use <see cref="Uniform.New(<#= type #>, <#= type #>)" />.
+        /// </summary>
+        /// <exception cref="ArgumentOutOfRangeException">
+        /// Thrown when <paramref name="low"/> is greater than or equal to <paramref name="high"/>.
+        /// </exception>
+        public static UniformInt<<#= type #>> Create(<#= type #> low, <#= type #> high)
+        {
+            if (low >= high)
+                throw new ArgumentOutOfRangeException(nameof(high), $"{nameof(high)} ({high}) must be higher than {nameof(low)} ({low}).");
+
+            return CreateInclusive(low, (<#= type #>)(high - 1));
+        }
+        
+
+        /// <summary>
+        /// Creates a <see cref="Uniform<#= type #>" /> with an exclusive lower bound. Should not
+        /// be used directly; instead, use <see cref="Uniform.NewInclusive(<#= type #>, <#= type #>)" />.
+        /// </summary>
+        /// <exception cref="ArgumentOutOfRangeException">
+        /// Thrown when <paramref name="low"/> is greater than <paramref name="high"/>.
+        /// </exception>
+        public static UniformInt<<#= type #>> CreateInclusive(<#= type #> low, <#= type #> high)
+        {
+            if (low > high)
+                throw new ArgumentOutOfRangeException(nameof(high), $"{nameof(high)} ({high}) must be higher than or equal to {nameof(low)} ({low}).");
+
+            var unsignedMax = <#= ularge #>.MaxValue;
+            var range = unchecked((<#= ularge #>)(high - low + 1));
+            var intsToReject = range == 0 ? 0 : (unsignedMax - range + 1) % range;
+
+            return new UniformInt<<#= type #>>(low, range, (<#= unsigned #>)intsToReject);
+        }
+<#
+}
+#>
+    }
+
+    /// <summary>
+    /// Implements a Uniform <see cref="IDistribution{TResult}"/> for integral types such as <see cref="Int32" /> and <see cref="UInt64" />.
+    /// Use of any other type results in a runtime exception.
+    /// </summary>
+    public readonly struct UniformInt<T> : IPortableDistribution<T>
+        // We're extremely restrictive here to discourage people from trying to use unsupported types for T
+        where T : struct, IComparable, IComparable<T>, IConvertible, IEquatable<T>, IFormattable
+    {
+        private readonly Number<T> _low;
+        private readonly UInt64 _range;
+        private readonly UInt64 _zone;
+        
+        internal UniformInt(Number<T> low, UInt64 range, UInt64 zone)
+        {
+            _low = low;
+            _range = range;
+            _zone = zone;
+        }
+
+        /// <inheritdoc />
+        public T Sample<TRng>(TRng rng) where TRng : notnull, IRng
+        {
+            if (typeof(T) == typeof(Int32))
+            {
+                var unsigned = rng.NextUInt32();
+                if (_range == 0) // 0 is a special case where we sample the entire range.
+                    return Number.Convert<UInt32, T>(unsigned);
+
+                var zone = UInt32.MaxValue - _zone;
+
+                while (unsigned > zone)
+                {
+                    unsigned = rng.NextUInt32();
+                }
+
+                return unchecked(Number.Convert<UInt32, T>(unsigned % (UInt32)_range) + _low);
+            }
+            else
+            {
+                var unsigned = rng.NextUInt64();
+                if (_range == 0) // 0 is a special case where we sample the entire range.
+                    return Number.Convert<UInt64, T>(unsigned);
+
+                var zone = UInt64.MaxValue - _zone;
+
+                while (unsigned > zone)
+                {
+                    unsigned = rng.NextUInt64();
+                }
+
+                return unchecked(Number.Convert<UInt64, T>(unsigned % _range) + _low);
+            }
+        }
+
+        /// <inheritdoc />
+        public Boolean TrySample<TRng>(TRng rng, out T result) where TRng : notnull, IRng
+        {
+            var unsigned = rng.NextUInt64();
+            if (_range == 0) // 0 is a special case where we sample the entire range.
+            {
+                result = Number.Convert<UInt64, T>(unsigned);
+                return true;
+            }
+
+            var zone = UInt64.MaxValue - _zone;
+
+            if (unsigned > zone)
+            {
+                result = default;
+                return false;
+            }
+
+            result = unchecked(Number.Convert<UInt64, T>(unsigned % _range) + _low);
+            return true;
+        }
+    }
 <#
 foreach (var tuple in types)
 {

          
M src/RandN/RandN.csproj +1 -0
@@ 57,6 57,7 @@ 
     <PackageReference Include="NullGuard.Fody" Version="3.0.0" />
     <PackageReference Include="RandN.Core" Version="0.1.0" />
     <ProjectReference Include="..\Core\Core.csproj" />
+    <PackageReference Include="Genumerics" Version="1.0.2" />
   </ItemGroup>
 
   <ItemGroup>