From 0d1d48db3bedddb8cf95cdd19c13d050bdb71c42 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Wed, 29 May 2024 11:32:25 +0200 Subject: [PATCH 1/8] Spelling --- README.md | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index a9b8e04..be0bae8 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,12 @@ # FastRng -FastRng is a multi-threaded pseudo-random number generator. Besides the generation of uniformly distributed random numbers, there are several other distributions to choose from. For performance reasons the parameters of the distributions are not user-definable. For some distributions, therefore, different parameter variations are available. If a different combination is desired, a separate class can be created. +FastRng is a multithreaded pseudo-random number generator. Besides the generation of uniformly distributed random numbers, there are several other distributions to choose from. For performance reasons, the parameters of the distributions are not user-definable. For some distributions, therefore, different parameter variations are available. If a different combination is desired, a separate class can be created. -Please note, that Math.NET's (https://www.mathdotnet.com/) random number generator is in some situations faster. Unlike Math.NET, MultiThreadedRng is multi-threaded. Consumers can use a token to cancel e.g. timeout an operation. +Please note, that Math.NET's (https://www.mathdotnet.com/) random number generator is in some situations faster. Unlike Math.NET, MultiThreadedRng is multithreaded. Consumers can use a token to cancel an operation. -FastRng (class `MultiThreadedRng`) using a shape fitter (a rejection sampler) to enforce arbitrary shapes of probabilities for desired distributions. By using the shape fitter, it is even easy to define discontinuous, arbitrary functions as shapes. Any consumer can define and use own distributions. +FastRng (class `MultiThreadedRng`) using a shape fitter (a rejection sampler) to enforce arbitrary shapes of probabilities for desired distributions. By using the shape fitter, it is even easier to define discontinuous, arbitrary functions as shapes. Any consumer can define and use own distributions. -The class `MultiThreadedRng` uses the George Marsaglia's MWC algorithm. The algorithm's implementation based loosely on John D. Cook's (johndcook.com) [implementation](https://www.codeproject.com/Articles/25172/Simple-Random-Number-Generation). Thanks John for the inspiration. - -Please notice: When using the debug environment, MultiThreadedRng might uses a smaller buffer size. Please ensure, that the production environment uses a release build, though. +The class `MultiThreadedRng` uses the George Marsaglia's MWC algorithm. The algorithm's implementation based loosely on John D. Cook's (johndcook.com) [implementation](https://www.codeproject.com/Articles/25172/Simple-Random-Number-Generation). Thanks, John, for the inspiration. ## Usage Example code: @@ -37,7 +35,7 @@ Notes: - `MultiThreadedRng` fills some buffers after creation. Thus, create and reuse it as long as needed. Avoid useless re-creation. -- Distributions need some time at creation to calculate probabilities. Thus, just create a distribution once and use reuse it. Avoid useless re-creation. +- Distributions need some time in creation to calculate probabilities. Thus, create a distribution once and use reuse it. Avoid useless re-creation. ## Available Distributions From 4602bb14afd4303974c8fe2157763282a6e65b05 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Wed, 29 May 2024 11:33:12 +0200 Subject: [PATCH 2/8] Added a frequency analysis tool for integers --- FastRngTests/IntFrequencyAnalysis.cs | 82 ++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 FastRngTests/IntFrequencyAnalysis.cs diff --git a/FastRngTests/IntFrequencyAnalysis.cs b/FastRngTests/IntFrequencyAnalysis.cs new file mode 100644 index 0000000..1a000c5 --- /dev/null +++ b/FastRngTests/IntFrequencyAnalysis.cs @@ -0,0 +1,82 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Text; + +namespace FastRngTests; + +[ExcludeFromCodeCoverage] +public sealed class IntFrequencyAnalysis +{ + private readonly uint[] data; + + public IntFrequencyAnalysis(int maxValue = 100) + { + this.data = new uint[maxValue]; + } + + public void CountThis(int value) + { + this.data[value]++; + } + + public float[] GetNormalizedEvents() + { + var max = (float)this.data.Max(); + var result = new float[this.data.Length]; + for (var n = 0; n < this.data.Length; n++) + { + result[n] = this.data[n] / max; + } + + return result; + } + + private float[] Normalize() + { + var max = (float)this.data.Max(); + var result = new float[this.data.Length]; + for (var n = 0; n < this.data.Length; n++) + result[n] = this.data[n] / max; + + return result; + } + + public float[] NormalizeAndPlotEvents(Action writer) + { + var result = this.Normalize(); + Plot(result, writer, "Event Distribution"); + + return result; + } + + public void PlotOccurence(Action writer) + { + var data = this.data.Select(n => n > 0f ? 1.0f : 0.0f).ToArray(); + Plot(data, writer, "Occurrence Distribution"); + } + + private static void Plot(float[] data, Action writer, string name) + { + const int HEIGHT = 16; + + var values = new float[data.Length]; + for (var n = 0; n < data.Length; n++) + { + values[n] = data[n] * HEIGHT; + } + + var sb = new StringBuilder(); + for (var line = HEIGHT; line > 0; line--) + { + for (var column = 0; column < data.Length; column++) + sb.Append(values[column] >= line ? '█' : '░'); + + writer.Invoke(sb.ToString()); + sb.Clear(); + } + + writer.Invoke(name); + writer.Invoke(string.Empty); + } +} \ No newline at end of file From a8121a306ec998e74d32a125b8684bd75cb62e4c Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Wed, 29 May 2024 11:34:37 +0200 Subject: [PATCH 3/8] .NET 8 migration; closes #10 --- FastRng/FastRng.csproj | 2 +- FastRng/MultiThreadedRng.cs | 2 +- FastRngTests/FastRngTests.csproj | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/FastRng/FastRng.csproj b/FastRng/FastRng.csproj index 74eaaf3..06142fa 100644 --- a/FastRng/FastRng.csproj +++ b/FastRng/FastRng.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 true 1.1.2 1.1.2 diff --git a/FastRng/MultiThreadedRng.cs b/FastRng/MultiThreadedRng.cs index 68c2177..3bbd21a 100644 --- a/FastRng/MultiThreadedRng.cs +++ b/FastRng/MultiThreadedRng.cs @@ -66,7 +66,7 @@ public sealed class MultiThreadedRng : IRandom, IDisposable where TN private uint mZ; // This is the current buffer for the consumer side i.e. the public interfaces: - private TNum[] currentBuffer = Array.Empty(); + private TNum[] currentBuffer = []; // The current pointer to the next current buffer's address to read from: private int currentBufferPointer = BUFFER_SIZE; diff --git a/FastRngTests/FastRngTests.csproj b/FastRngTests/FastRngTests.csproj index 07d3755..2cc4d1d 100644 --- a/FastRngTests/FastRngTests.csproj +++ b/FastRngTests/FastRngTests.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 false From 817895015fa7213a180ed3694a29226152d3726b Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Wed, 29 May 2024 11:36:02 +0200 Subject: [PATCH 4/8] Spelling --- FastRng/MultiChannelRng.cs | 9 ++++----- FastRng/MultiThreadedRng.cs | 31 +++++++++++++------------------ 2 files changed, 17 insertions(+), 23 deletions(-) diff --git a/FastRng/MultiChannelRng.cs b/FastRng/MultiChannelRng.cs index aff1fbc..f4e0b9b 100644 --- a/FastRng/MultiChannelRng.cs +++ b/FastRng/MultiChannelRng.cs @@ -36,13 +36,12 @@ public sealed class MultiChannelRng : IRandom, IDisposable where TNu #region Constructors /// - /// Creates a multi-threaded random number generator. + /// Creates a multithreaded random number generator. /// /// /// This constructor uses the user's current local time - /// to derive necessary parameters for the generator. - /// Thus, the results are depending on the time, where - /// the generator was created. + /// to derive the necessary parameters for the generator. + /// Thus, the results depend on the time when the generator was created. /// public MultiChannelRng() { @@ -58,7 +57,7 @@ public sealed class MultiChannelRng : IRandom, IDisposable where TNu } /// - /// Creates a multi-threaded random number generator. + /// Creates a multithreaded random number generator. /// /// /// A multi-threaded random number generator created by this constructor is diff --git a/FastRng/MultiThreadedRng.cs b/FastRng/MultiThreadedRng.cs index 3bbd21a..77e94e2 100644 --- a/FastRng/MultiThreadedRng.cs +++ b/FastRng/MultiThreadedRng.cs @@ -12,18 +12,15 @@ namespace FastRng; /// /// /// Please note, that Math.NET's (https://www.mathdotnet.com/) random number generator is in some situations faster. -/// Unlike Math.NET, MultiThreadedRng is multi-threaded. Consumers can use a token to cancel e.g. timeout an operation.

+/// Unlike Math.NET, MultiThreadedRng is multithreaded. Consumers can use a token to cancel an operation.

/// /// MultiThreadedRng using a shape fitter (a rejection sampler) to enforce arbitrary shapes of probabilities for -/// desired distributions. By using the shape fitter, it is even easy to define discontinuous, arbitrary functions +/// desired distributions. By using the shape fitter, it is even easier to define discontinuous, arbitrary functions /// as shapes. Any consumer can define and use own distributions.

/// -/// This class uses the George Marsaglia's MWC algorithm. The algorithm's implementation based loosely on John D. +/// This class uses the George Marsaglia's MWC algorithm. The algorithm's implementation is based loosely on John D. /// Cook's (johndcook.com) implementation (https://www.codeproject.com/Articles/25172/Simple-Random-Number-Generation). -/// Thanks John for the inspiration.

-/// -/// Please notice: When using the debug environment, MultiThreadedRng might uses a smaller buffer size. Please ensure, -/// that the production environment uses a release build, though. +/// Thanks, John, for the inspiration.

///
public sealed class MultiThreadedRng : IRandom, IDisposable where TNum : IFloatingPointIeee754, IAdditionOperators { @@ -33,10 +30,10 @@ public sealed class MultiThreadedRng : IRandom, IDisposable where TN private const int BUFFER_SIZE = 1_000_000; #endif - // The queue size means, how many buffer we store in a queue at the same time: + // The queue size means, how many buffers we store in a queue at the same time: private const int QUEUE_SIZE_FLOAT = 2; - // The queue size means, how many buffer we store in a queue at the same time: + // The queue size means, how many buffers we store in a queue at the same time: private const int QUEUE_SIZE_INT = QUEUE_SIZE_FLOAT * 2; private static readonly TNum CONST_FLOAT_CONVERSION = TNum.CreateChecked(2.328306435454494e-10f); @@ -65,7 +62,7 @@ public sealed class MultiThreadedRng : IRandom, IDisposable where TN private uint mW; private uint mZ; - // This is the current buffer for the consumer side i.e. the public interfaces: + // This is the current buffer for the consumer side, i.e., the public interfaces: private TNum[] currentBuffer = []; // The current pointer to the next current buffer's address to read from: @@ -77,10 +74,8 @@ public sealed class MultiThreadedRng : IRandom, IDisposable where TN /// Creates a multi-threaded random number generator. /// /// - /// This constructor uses the user's current local time - /// to derive necessary parameters for the generator. - /// Thus, the results are depending on the time, where - /// the generator was created. + /// This constructor uses the user's current local time to derive the necessary parameters for the generator. + /// Thus, the results depend on the time when the generator was created. /// public MultiThreadedRng() { @@ -171,7 +166,7 @@ public sealed class MultiThreadedRng : IRandom, IDisposable where TN { try { - // Ensure, that we do not produce more buffers, as configured: + // Ensure that we do not produce more buffers, as configured: if (this.queueIntegers.Count < QUEUE_SIZE_INT) { this.queueIntegers.Enqueue(nextBuffer); @@ -224,7 +219,7 @@ public sealed class MultiThreadedRng : IRandom, IDisposable where TN { try { - // Ensure, that the queue contains only the configured number of buffers: + // Ensure that the queue contains only the configured number of buffers: if (this.queueFloats.Count < QUEUE_SIZE_FLOAT) { this.queueFloats.Enqueue(nextBuffer); @@ -273,7 +268,7 @@ public sealed class MultiThreadedRng : IRandom, IDisposable where TN // can get the next buffer: lock (LOCKER) { - // We are might not the first thread, which has to get the next buffer. + // We might not the first thread, which has to get the next buffer. // When some other thread has already got the next buffer, the pointer // was already reset to zero. In this case, we start over again: if(this.currentBufferPointer < BUFFER_SIZE) @@ -295,7 +290,7 @@ public sealed class MultiThreadedRng : IRandom, IDisposable where TN // Made a local copy of the current pointer: var myPointer = this.currentBufferPointer; - // Issue #8: This might happens when the current thread was interrupted by another thread, and + // Issue #8: This might happen when another thread interrupted the current thread, and // the other thread has already updated the pointer. In this case, we start over again. if (myPointer >= BUFFER_SIZE) goto Start; From 8ade2f5a5d7bfdf682c2325832274561f697fff2 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Wed, 29 May 2024 11:37:29 +0200 Subject: [PATCH 5/8] Addressed some warnings --- FastRng/MultiChannelRng.cs | 2 ++ FastRng/MultiThreadedRng.cs | 7 ++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/FastRng/MultiChannelRng.cs b/FastRng/MultiChannelRng.cs index f4e0b9b..2eaa0b5 100644 --- a/FastRng/MultiChannelRng.cs +++ b/FastRng/MultiChannelRng.cs @@ -4,6 +4,8 @@ using System.Numerics; using System.Threading; using System.Threading.Channels; +// ReSharper disable RedundantExtendsListEntry + namespace FastRng; public sealed class MultiChannelRng : IRandom, IDisposable where TNum : IFloatingPointIeee754, IAdditionOperators diff --git a/FastRng/MultiThreadedRng.cs b/FastRng/MultiThreadedRng.cs index 77e94e2..e7fbfba 100644 --- a/FastRng/MultiThreadedRng.cs +++ b/FastRng/MultiThreadedRng.cs @@ -5,6 +5,8 @@ using System.Numerics; using System.Threading; using System.Threading.Tasks; +// ReSharper disable RedundantExtendsListEntry + namespace FastRng; /// @@ -37,7 +39,10 @@ public sealed class MultiThreadedRng : IRandom, IDisposable where TN private const int QUEUE_SIZE_INT = QUEUE_SIZE_FLOAT * 2; private static readonly TNum CONST_FLOAT_CONVERSION = TNum.CreateChecked(2.328306435454494e-10f); + + // ReSharper disable StaticMemberInGenericType private static readonly object LOCKER = new(); + // ReSharper restore StaticMemberInGenericType // Gets used to stop the producer threads: private readonly CancellationTokenSource producerTokenSource = new(); @@ -197,7 +202,7 @@ public sealed class MultiThreadedRng : IRandom, IDisposable where TN while (!cancellationToken.IsCancellationRequested) { // A local source buffer of uints: - uint[] bufferSource = null; + uint[] bufferSource; // Try to get the next source buffer: while (!this.queueIntegers.TryDequeue(out bufferSource) && !cancellationToken.IsCancellationRequested) From f9ea837f687feb85bddcad982cefac3dd14ff9f8 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Wed, 29 May 2024 11:37:59 +0200 Subject: [PATCH 6/8] Added documentation --- FastRng/MultiChannelRng.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/FastRng/MultiChannelRng.cs b/FastRng/MultiChannelRng.cs index 2eaa0b5..d3d9abd 100644 --- a/FastRng/MultiChannelRng.cs +++ b/FastRng/MultiChannelRng.cs @@ -150,13 +150,15 @@ public sealed class MultiChannelRng : IRandom, IDisposable where TNu #region Implementation of IDisposable private void StopProducer() => this.producerTokenSource.Cancel(); - + + /// public void Dispose() => this.StopProducer(); #endregion #region Implementation of IRandom + /// public TNum GetUniform(CancellationToken cancel = default) { var valueTask = this.channelFloats.Reader.ReadAsync(cancel); From b2d8e2b5ed020ecc5a17d1438b7643fdd5b902db Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Wed, 29 May 2024 11:39:08 +0200 Subject: [PATCH 7/8] Added possibility to get integers out of the RNG --- FastRng/IRandom.cs | 25 +++ FastRng/MultiChannelRng.cs | 14 ++ FastRng/MultiThreadedRng.cs | 22 ++- FastRng/UIntChannelProducer.cs | 121 ++++++++++++ FastRngTests/GetIntTests.cs | 327 +++++++++++++++++++++++++++++++++ FastRngTests/TestCategories.cs | 1 + 6 files changed, 509 insertions(+), 1 deletion(-) create mode 100644 FastRng/UIntChannelProducer.cs create mode 100644 FastRngTests/GetIntTests.cs diff --git a/FastRng/IRandom.cs b/FastRng/IRandom.cs index a455859..6a250ba 100644 --- a/FastRng/IRandom.cs +++ b/FastRng/IRandom.cs @@ -18,4 +18,29 @@ public interface IRandom : IDisposable where TNum : IFloatingPointIeee754< /// by using multiple threads at the same time. /// public TNum GetUniform(CancellationToken cancel = default); + + /// + /// Get a uniform distributed pseudo-random number from the interval [0, max). + /// + /// + /// This method is thread-safe. You can consume numbers from the same generator + /// by using multiple threads at the same time. + /// + /// The maximum value (exclusive). The max value returned will be max - 1. + /// The cancellation token. + /// A pseudo-random number from the interval [0, max). + public int GetUniformInt(int max, CancellationToken cancel = default); + + /// + /// Get a uniform distributed pseudo-random number from the interval [min, max). + /// + /// + /// This method is thread-safe. You can consume numbers from the same generator + /// by using multiple threads at the same time. + /// + /// The minimum value (inclusive). + /// The maximum value (exclusive). The max value returned will be max - 1. + /// The cancellation token. + /// A pseudo-random number from the interval [min, max). + public int GetUniformInt(int min, int max, CancellationToken cancel = default); } \ No newline at end of file diff --git a/FastRng/MultiChannelRng.cs b/FastRng/MultiChannelRng.cs index d3d9abd..ff11f7f 100644 --- a/FastRng/MultiChannelRng.cs +++ b/FastRng/MultiChannelRng.cs @@ -165,6 +165,20 @@ public sealed class MultiChannelRng : IRandom, IDisposable where TNu return valueTask.AsTask().Result; } + /// + public int GetUniformInt(int max, CancellationToken cancel = default) + { + var valueTask = this.channelIntegers.Reader.ReadAsync(cancel); + return (int) (valueTask.AsTask().Result % (uint) max); + } + + /// + public int GetUniformInt(int min, int max, CancellationToken cancel = default) + { + var valueTask = this.channelIntegers.Reader.ReadAsync(cancel); + return (int) (valueTask.AsTask().Result % (uint) (max - min) + (uint) min); + } + #endregion #endregion diff --git a/FastRng/MultiThreadedRng.cs b/FastRng/MultiThreadedRng.cs index e7fbfba..bfeb0b2 100644 --- a/FastRng/MultiThreadedRng.cs +++ b/FastRng/MultiThreadedRng.cs @@ -61,6 +61,8 @@ public sealed class MultiThreadedRng : IRandom, IDisposable where TN // The uniform float producer thread: private Thread producerRandomUniformDistributedFloat; + + private readonly UIntChannelProducer independentUIntProducer = new(); // Variable w and z for the uint generator. Both get used // as seeding variable as well (cf. constructors) @@ -312,6 +314,20 @@ public sealed class MultiThreadedRng : IRandom, IDisposable where TN // return this.currentBuffer[myPointer]; } + + /// + public int GetUniformInt(int max, CancellationToken cancel = default) + { + var valueTask = this.independentUIntProducer.GetNextAsync(cancel); + return (int) (valueTask.AsTask().Result % (uint) max); + } + + /// + public int GetUniformInt(int min, int max, CancellationToken cancel = default) + { + var valueTask = this.independentUIntProducer.GetNextAsync(cancel); + return (int) (valueTask.AsTask().Result % (uint) (max - min) + (uint) min); + } private void StopProducer() => this.producerTokenSource.Cancel(); @@ -320,7 +336,11 @@ public sealed class MultiThreadedRng : IRandom, IDisposable where TN /// when it is no longer needed. Otherwise, the background threads /// are still running. /// - public void Dispose() => this.StopProducer(); + public void Dispose() + { + this.independentUIntProducer.Dispose(); + this.StopProducer(); + } #endregion } \ No newline at end of file diff --git a/FastRng/UIntChannelProducer.cs b/FastRng/UIntChannelProducer.cs new file mode 100644 index 0000000..f4121d2 --- /dev/null +++ b/FastRng/UIntChannelProducer.cs @@ -0,0 +1,121 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Threading; +using System.Threading.Channels; +using System.Threading.Tasks; + +namespace FastRng; + +internal sealed class UIntChannelProducer : IDisposable +{ +#if DEBUG + private const int BUFFER_SIZE = 1_000_000; +#else + private const int BUFFER_SIZE = 1_000_000; +#endif + + private readonly Channel channelIntegers = Channel.CreateBounded(new BoundedChannelOptions(capacity: BUFFER_SIZE * 2) { FullMode = BoundedChannelFullMode.Wait, SingleWriter = true, SingleReader = true }); + + // Gets used to stop the producer thread: + private readonly CancellationTokenSource producerTokenSource = new(); + + // The uint producer thread: + private Thread producerRandomUint; + + // Variable w and z for the uint generator. Both get used + // as seeding variable as well (cf. constructors) + private uint mW; + private uint mZ; + + #region Constructors + + public UIntChannelProducer() + { + // + // Initialize the mW and mZ by using + // the system's time. + // + var now = DateTime.Now; + var ticks = now.Ticks; + this.mW = (uint) (ticks >> 16); + this.mZ = (uint) (ticks % 4_294_967_296); + this.StartProducerThread(); + } + + /// + /// Creates a multithreaded random number generator. + /// + /// + /// A multi-threaded random number generator created by this constructor is + /// deterministic. It's behaviour is not depending on the time of its creation.

+ /// + /// Please note: Although the number generator and all distributions are deterministic, + /// the behavior of the consuming application might be non-deterministic. This is possible if + /// the application with multiple threads consumes the numbers. The scheduling of the threads + /// is up to the operating system and might not be predictable. + ///
+ /// A seed value to generate a deterministic generator. + public UIntChannelProducer(uint seedU) + { + this.mW = seedU; + this.mZ = 362_436_069; + this.StartProducerThread(); + } + + /// + /// Creates a multi-threaded random number generator. + /// + /// + /// A multi-threaded random number generator created by this constructor is + /// deterministic. It's behaviour is not depending on the time of its creation.

+ /// + /// Please note: Although the number generator and all distributions are deterministic, + /// the behavior of the consuming application might be non-deterministic. This is possible if + /// the application with multiple threads consumes the numbers. The scheduling of the threads + /// is up to the operating system and might not be predictable. + ///
+ /// The first seed value. + /// The second seed value. + public UIntChannelProducer(uint seedU, uint seedV) + { + this.mW = seedU; + this.mZ = seedV; + this.StartProducerThread(); + } + + private void StartProducerThread() + { + this.producerRandomUint = new Thread(() => this.RandomProducerUint(this.producerTokenSource.Token)) {IsBackground = true}; + this.producerRandomUint.Start(); + } + + #endregion + + [ExcludeFromCodeCoverage] + private async void RandomProducerUint(CancellationToken cancellationToken) + { + try + { + while (!cancellationToken.IsCancellationRequested) + { + this.mZ = 36_969 * (this.mZ & 65_535) + (this.mZ >> 16); + this.mW = 18_000 * (this.mW & 65_535) + (this.mW >> 16); + await this.channelIntegers.Writer.WriteAsync((this.mZ << 16) + this.mW, cancellationToken); + } + } + catch (OperationCanceledException) + { + } + } + + public async ValueTask GetNextAsync(CancellationToken cancellationToken = default) => await this.channelIntegers.Reader.ReadAsync(cancellationToken); + + #region Implementation of IDisposable + + private void StopProducer() => this.producerTokenSource.Cancel(); + + /// + public void Dispose() => this.StopProducer(); + + #endregion +} \ No newline at end of file diff --git a/FastRngTests/GetIntTests.cs b/FastRngTests/GetIntTests.cs new file mode 100644 index 0000000..8864fa4 --- /dev/null +++ b/FastRngTests/GetIntTests.cs @@ -0,0 +1,327 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Threading.Tasks; + +using FastRng; +using FastRng.Distributions; + +using NUnit.Framework; + +namespace FastRngTests; + +[ExcludeFromCodeCoverage] +public class GetIntTests +{ + #region Channel-Based + + [Test] + [Category(TestCategories.COVER)] + [Category(TestCategories.NORMAL)] + [Category(TestCategories.INT)] + public void ChannelGetMax() + { + var random = new Random(); + var randomMax = random.Next(); + + using var rng = new MultiChannelRng(); + for(var n = 0; n < 10_000; n++) + { + var value = rng.GetUniformInt(randomMax); + Assert.That(value, Is.GreaterThanOrEqualTo(0)); + Assert.That(value, Is.LessThanOrEqualTo(randomMax)); + } + } + + [Test] + [Category(TestCategories.COVER)] + [Category(TestCategories.NORMAL)] + [Category(TestCategories.INT)] + public void ChannelGetMaxDistributionCheck() + { + var random = new Random(); + var randomMax = random.Next(3, 33); + var freqCheck = new IntFrequencyAnalysis(randomMax); + using var rng = new MultiChannelRng(); + + for(var n = 0; n < 10_000; n++) + { + var value = rng.GetUniformInt(randomMax); + freqCheck.CountThis(value); + + Assert.That(value, Is.GreaterThanOrEqualTo(0)); + Assert.That(value, Is.LessThanOrEqualTo(randomMax)); + } + + var distribution = freqCheck.NormalizeAndPlotEvents(TestContext.WriteLine); + var max = distribution.Max(); + var min = distribution.Min(); + Assert.That(max - min, Is.LessThanOrEqualTo(0.27f)); + } + + [Test] + [Category(TestCategories.COVER)] + [Category(TestCategories.NORMAL)] + [Category(TestCategories.INT)] + public void ChannelGetMinMax() + { + var random = new Random(); + var randomMin = random.Next(); + int randomMax; + do + { + randomMax = random.Next(); + } while (randomMax < randomMin); + + using var rng = new MultiChannelRng(); + for(var n = 0; n < 10_000; n++) + { + var value = rng.GetUniformInt(randomMin, randomMax); + Assert.That(value, Is.GreaterThanOrEqualTo(randomMin)); + Assert.That(value, Is.LessThanOrEqualTo(randomMax)); + } + } + + [Test] + [Category(TestCategories.COVER)] + [Category(TestCategories.NORMAL)] + [Category(TestCategories.INT)] + public void ChannelGetMinMaxDistributionCheck() + { + var random = new Random(); + var randomMin = random.Next(0, 16); + var randomMax = random.Next(randomMin + 10, 33); + + var freqCheck = new IntFrequencyAnalysis(randomMax - randomMin); + using var rng = new MultiChannelRng(); + + for(var n = 0; n < 10_000; n++) + { + var value = rng.GetUniformInt(randomMin, randomMax); + freqCheck.CountThis(value - randomMin); + + Assert.That(value, Is.GreaterThanOrEqualTo(randomMin)); + Assert.That(value, Is.LessThanOrEqualTo(randomMax)); + } + + var distribution = freqCheck.NormalizeAndPlotEvents(TestContext.WriteLine); + var max = distribution.Max(); + var min = distribution.Min(); + Assert.That(max - min, Is.LessThanOrEqualTo(0.27f)); + } + + [Test] + [Category(TestCategories.COVER)] + [Category(TestCategories.NORMAL)] + [Category(TestCategories.INT)] + public void ChannelCheckMinMax01() + { + var expectedMax = 3; + + var max = int.MinValue; + var min = int.MaxValue; + using var rng = new MultiChannelRng(); + for(var n = 0; n < 10_000; n++) + { + var value = rng.GetUniformInt(expectedMax); + if (value < min) + min = value; + + if (value > max) + max = value; + + Assert.That(value, Is.GreaterThanOrEqualTo(0)); + Assert.That(value, Is.LessThanOrEqualTo(expectedMax)); + } + + Assert.That(min, Is.EqualTo(0)); + Assert.That(max, Is.EqualTo(expectedMax - 1)); + } + + [Test] + [Category(TestCategories.COVER)] + [Category(TestCategories.NORMAL)] + [Category(TestCategories.INT)] + public void ChannelCheckMinMax02() + { + var expectedMin = 2; + var expectedMax = 6; + + var max = int.MinValue; + var min = int.MaxValue; + using var rng = new MultiChannelRng(); + for(var n = 0; n < 10_000; n++) + { + var value = rng.GetUniformInt(expectedMin, expectedMax); + if (value < min) + min = value; + + if (value > max) + max = value; + + Assert.That(value, Is.GreaterThanOrEqualTo(expectedMin)); + Assert.That(value, Is.LessThanOrEqualTo(expectedMax)); + } + + Assert.That(min, Is.EqualTo(expectedMin)); + Assert.That(max, Is.EqualTo(expectedMax - 1)); + } + + #endregion + + #region Multithreaded + + [Test] + [Category(TestCategories.COVER)] + [Category(TestCategories.NORMAL)] + [Category(TestCategories.INT)] + public void MultithreadedGetMax() + { + var random = new Random(); + var randomMax = random.Next(); + + using var rng = new MultiThreadedRng(); + for(var n = 0; n < 10_000; n++) + { + var value = rng.GetUniformInt(randomMax); + Assert.That(value, Is.GreaterThanOrEqualTo(0)); + Assert.That(value, Is.LessThanOrEqualTo(randomMax)); + } + } + + [Test] + [Category(TestCategories.COVER)] + [Category(TestCategories.NORMAL)] + [Category(TestCategories.INT)] + public void MultithreadedGetMaxDistributionCheck() + { + var random = new Random(); + var randomMax = random.Next(3, 33); + var freqCheck = new IntFrequencyAnalysis(randomMax); + using var rng = new MultiThreadedRng(); + + for(var n = 0; n < 10_000; n++) + { + var value = rng.GetUniformInt(randomMax); + freqCheck.CountThis(value); + + Assert.That(value, Is.GreaterThanOrEqualTo(0)); + Assert.That(value, Is.LessThanOrEqualTo(randomMax)); + } + + var distribution = freqCheck.NormalizeAndPlotEvents(TestContext.WriteLine); + var max = distribution.Max(); + var min = distribution.Min(); + Assert.That(max - min, Is.LessThanOrEqualTo(0.27f)); + } + + [Test] + [Category(TestCategories.COVER)] + [Category(TestCategories.NORMAL)] + [Category(TestCategories.INT)] + public void MultithreadedGetMinMax() + { + var random = new Random(); + var randomMin = random.Next(); + int randomMax; + do + { + randomMax = random.Next(); + } while (randomMax < randomMin); + + using var rng = new MultiThreadedRng(); + for(var n = 0; n < 10_000; n++) + { + var value = rng.GetUniformInt(randomMin, randomMax); + Assert.That(value, Is.GreaterThanOrEqualTo(randomMin)); + Assert.That(value, Is.LessThanOrEqualTo(randomMax)); + } + } + + [Test] + [Category(TestCategories.COVER)] + [Category(TestCategories.NORMAL)] + [Category(TestCategories.INT)] + public void MultithreadedGetMinMaxDistributionCheck() + { + var random = new Random(); + var randomMin = random.Next(0, 16); + var randomMax = random.Next(randomMin + 10, 33); + + var freqCheck = new IntFrequencyAnalysis(randomMax - randomMin); + using var rng = new MultiThreadedRng(); + + for(var n = 0; n < 10_000; n++) + { + var value = rng.GetUniformInt(randomMin, randomMax); + freqCheck.CountThis(value - randomMin); + + Assert.That(value, Is.GreaterThanOrEqualTo(randomMin)); + Assert.That(value, Is.LessThanOrEqualTo(randomMax)); + } + + var distribution = freqCheck.NormalizeAndPlotEvents(TestContext.WriteLine); + var max = distribution.Max(); + var min = distribution.Min(); + Assert.That(max - min, Is.LessThanOrEqualTo(0.27f)); + } + + [Test] + [Category(TestCategories.COVER)] + [Category(TestCategories.NORMAL)] + [Category(TestCategories.INT)] + public void MultithreadedCheckMinMax01() + { + var expectedMax = 3; + + var max = int.MinValue; + var min = int.MaxValue; + using var rng = new MultiThreadedRng(); + for(var n = 0; n < 10_000; n++) + { + var value = rng.GetUniformInt(expectedMax); + if (value < min) + min = value; + + if (value > max) + max = value; + + Assert.That(value, Is.GreaterThanOrEqualTo(0)); + Assert.That(value, Is.LessThanOrEqualTo(expectedMax)); + } + + Assert.That(min, Is.EqualTo(0)); + Assert.That(max, Is.EqualTo(expectedMax - 1)); + } + + [Test] + [Category(TestCategories.COVER)] + [Category(TestCategories.NORMAL)] + [Category(TestCategories.INT)] + public void MultithreadedCheckMinMax02() + { + var expectedMin = 2; + var expectedMax = 6; + + var max = int.MinValue; + var min = int.MaxValue; + using var rng = new MultiThreadedRng(); + for(var n = 0; n < 10_000; n++) + { + var value = rng.GetUniformInt(expectedMin, expectedMax); + if (value < min) + min = value; + + if (value > max) + max = value; + + Assert.That(value, Is.GreaterThanOrEqualTo(expectedMin)); + Assert.That(value, Is.LessThanOrEqualTo(expectedMax)); + } + + Assert.That(min, Is.EqualTo(expectedMin)); + Assert.That(max, Is.EqualTo(expectedMax - 1)); + } + + #endregion +} \ No newline at end of file diff --git a/FastRngTests/TestCategories.cs b/FastRngTests/TestCategories.cs index a523612..b416978 100644 --- a/FastRngTests/TestCategories.cs +++ b/FastRngTests/TestCategories.cs @@ -5,6 +5,7 @@ public static class TestCategories public const string COVER = "cover"; public const string PERFORMANCE = "performance"; public const string NORMAL = "normal"; + public const string INT = "int"; public const string EXAMPLE = "example"; public const string LONG_RUNNING = "long running"; } \ No newline at end of file From 262dd6cf1fac7acc830afc955c34f4ea967d0710 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Wed, 29 May 2024 11:39:18 +0200 Subject: [PATCH 8/8] Prepared next release --- FastRng/FastRng.csproj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/FastRng/FastRng.csproj b/FastRng/FastRng.csproj index 06142fa..2e5cc06 100644 --- a/FastRng/FastRng.csproj +++ b/FastRng/FastRng.csproj @@ -3,9 +3,9 @@ net8.0 true - 1.1.2 - 1.1.2 - 1.1.2 + 1.2.0 + 1.2.0 + 1.2.0 Thorsten Sommer https://devops.tsommer.org/open-source/dotnet/FastRng https://devops.tsommer.org/open-source/dotnet/FastRng