Resolve "Migrate to INumber" #11
168
FastRng/MultiChannelRng.cs
Normal file
168
FastRng/MultiChannelRng.cs
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
using System;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Channels;
|
||||||
|
|
||||||
|
namespace FastRng;
|
||||||
|
|
||||||
|
public sealed class MultiChannelRng<TNum> : IRandom<TNum>, IDisposable where TNum : IFloatingPointIeee754<TNum>, IAdditionOperators<TNum, TNum, TNum>
|
||||||
|
{
|
||||||
|
#if DEBUG
|
||||||
|
private const int BUFFER_SIZE = 1_000_000;
|
||||||
|
#else
|
||||||
|
private const int BUFFER_SIZE = 1_000_000;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
private readonly Channel<uint> channelIntegers = Channel.CreateBounded<uint>(new BoundedChannelOptions(capacity: BUFFER_SIZE * 2) { FullMode = BoundedChannelFullMode.Wait, SingleWriter = true, SingleReader = true });
|
||||||
|
private readonly Channel<TNum> channelFloats = Channel.CreateBounded<TNum>(new BoundedChannelOptions(capacity: BUFFER_SIZE) { FullMode = BoundedChannelFullMode.Wait, SingleWriter = true, SingleReader = false });
|
||||||
|
|
||||||
|
private static readonly TNum CONST_FLOAT_CONVERSION = TNum.CreateChecked(2.328306435454494e-10f);
|
||||||
|
|
||||||
|
// Gets used to stop the producer threads:
|
||||||
|
private readonly CancellationTokenSource producerTokenSource = new();
|
||||||
|
|
||||||
|
// The uint producer thread:
|
||||||
|
private Thread producerRandomUint;
|
||||||
|
|
||||||
|
// The uniform float producer thread:
|
||||||
|
private Thread producerRandomUniformDistributedFloat;
|
||||||
|
|
||||||
|
// 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
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a multi-threaded random number generator.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 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.
|
||||||
|
/// </remarks>
|
||||||
|
public MultiChannelRng()
|
||||||
|
{
|
||||||
|
//
|
||||||
|
// 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.StartProducerThreads();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a multi-threaded random number generator.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// A multi-threaded random number generator created by this constructor is
|
||||||
|
/// deterministic. It's behaviour is not depending on the time of its creation.<br/><br/>
|
||||||
|
///
|
||||||
|
/// <b>Please note:</b> 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.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="seedU">A seed value to generate a deterministic generator.</param>
|
||||||
|
public MultiChannelRng(uint seedU)
|
||||||
|
{
|
||||||
|
this.mW = seedU;
|
||||||
|
this.mZ = 362_436_069;
|
||||||
|
this.StartProducerThreads();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a multi-threaded random number generator.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// A multi-threaded random number generator created by this constructor is
|
||||||
|
/// deterministic. It's behaviour is not depending on the time of its creation.<br/><br/>
|
||||||
|
///
|
||||||
|
/// <b>Please note:</b> 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.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="seedU">The first seed value.</param>
|
||||||
|
/// <param name="seedV">The second seed value.</param>
|
||||||
|
public MultiChannelRng(uint seedU, uint seedV)
|
||||||
|
{
|
||||||
|
this.mW = seedU;
|
||||||
|
this.mZ = seedV;
|
||||||
|
this.StartProducerThreads();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StartProducerThreads()
|
||||||
|
{
|
||||||
|
this.producerRandomUint = new Thread(() => this.RandomProducerUint(this.producerTokenSource.Token)) {IsBackground = true};
|
||||||
|
this.producerRandomUint.Start();
|
||||||
|
this.producerRandomUniformDistributedFloat = new Thread(() => this.RandomProducerUniformDistributedFloat(this.producerTokenSource.Token)) {IsBackground = true};
|
||||||
|
this.producerRandomUniformDistributedFloat.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Producers
|
||||||
|
|
||||||
|
[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)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[ExcludeFromCodeCoverage]
|
||||||
|
private async void RandomProducerUniformDistributedFloat(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
while (!cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
await this.channelFloats.Writer.WriteAsync((TNum.CreateChecked(await this.channelIntegers.Reader.ReadAsync(cancellationToken)) + TNum.One) * CONST_FLOAT_CONVERSION, cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Implementing interfaces
|
||||||
|
|
||||||
|
#region Implementation of IDisposable
|
||||||
|
|
||||||
|
private void StopProducer() => this.producerTokenSource.Cancel();
|
||||||
|
|
||||||
|
public void Dispose() => this.StopProducer();
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Implementation of IRandom<TNum>
|
||||||
|
|
||||||
|
public TNum GetUniform(CancellationToken cancel = default)
|
||||||
|
{
|
||||||
|
var valueTask = this.channelFloats.Reader.ReadAsync(cancel);
|
||||||
|
return valueTask.AsTask().Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user