XorShift128

  1. トップページ
  2. プログラミング
  3. C#
  4. XorShift128

ビット演算(排他的論理和とシフト)のみを使用しているため非常に高速であるといわれている疑似乱数生成アルゴリズム「XorShift」のコード例です。 内部で128ビットの値を保持しており,周期は2128-1になります。


using System;

namespace ikuzak.Crypto
{
  /// <summary>
  /// Represents a pseudo-random number generator, using XorShift 128.
  /// </summary>
  public class XorShift128
  {
    private const uint zero = 0x00000000;

    private uint t = 0x00000000; // 0000 0000 0000 0000 0000 0000 0000 0000
    private uint x = 0x0141FDAF; // 0000 0001 0100 0001 1111 1101 1010 1111
    private uint y = 0x21101999; // 0010 0001 0001 0000 0001 1001 1001 1001
    private uint z = 0x54655307; // 0101 0100 0110 0101 0101 0011 0000 0111
    private uint w = 0x2194BB93; // 0010 0001 1001 0100 1011 1011 1001 0011

    /// <summary>
    /// Initializes a new instance of the <see cref="XorShift128"/> class.
    /// </summary>
    public XorShift128()
    {
      SkipOver();
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="XorShift128"/> class with a byte array.
    /// </summary>
    /// <param name="seed">A byte array to use as a seed value.</param>
    public XorShift128(byte[] seed)
    {
      if (seed.Length < 4) throw new ArgumentException();
      this.x = BitConverter.ToUInt32(seed, 0);
      if (!SkipOver()) throw new Exception("The seed value must not be equal to zero.");
    }

    /// <summary>
    /// Initializes a new instance of the , <see cref="XorShift128"/> class with a seed value.
    /// </summary>
    /// <param name="seed">A seed value.</param>
    public XorShift128(int seed)
    {
      var bs = BitConverter.GetBytes(seed);
      this.x = BitConverter.ToUInt32(bs, 0);
      if (!SkipOver()) throw new Exception("The seed value must not be equal to zero.");
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="XorShift128"/> class with an object.
    /// </summary>
    /// <param name="o">An object to used as a seed.</param>
    public XorShift128(object o)
    {
      var bs = BitConverter.GetBytes(o.GetHashCode());
      this.x = BitConverter.ToUInt32(bs, 0);
      if (!SkipOver()) throw new Exception("The seed value must not be equal to zero.");
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="XorShift128"/> class with objects.
    /// </summary>
    /// <param name="o">Objects to  used as a seed.</param>
    public XorShift128(params object[] o)
    {
      var tmp = o[0].GetHashCode();
      for (var i = 1; i < o.Length; i++) tmp ^= o[i].GetHashCode();
      var bs = BitConverter.GetBytes(tmp);
      this.x = BitConverter.ToUInt32(bs, 0);
      if (!SkipOver()) throw new Exception("The seed value must not be equal to zero.");
    }

    private bool SkipOver()
    {
      if ((this.x | zero) == zero) return false;

      for (var i = 0; i < 40; i++) Next(0, 1);

      return true;
    }

    /// <summary>
    /// Returns a random integer that is within a specified range.
    /// </summary>
    /// <param name="minValue">The inclusive lower bound of the random number returned.</param>
    /// <param name="maxValue">
    /// The exclusive upper bound of the random number returned.
    /// <paramref name="maxValue"/> must be greater than or equal to <paramref name="minValue"/>.</param>
    /// <returns>
    /// A 32-bit signed integer greater than or equal to <paramref name="minValue"/>
    /// and less than <paramref name="maxValue"/>;
    /// that is, the range of return values includes <paramref name="minValue"/>
    /// but not <paramref name="maxValue"/>.
    /// If <paramref name="minValue"/> equals <paramref name="maxValue"/>,
    /// <paramref name="minValue"/> is returned.
    /// </returns>
    /// <exception cref="ArgumentOutOfRangeException">
    /// <paramref name="minValue"/> is greater than <paramref name="maxValue"/>.
    /// </exception>
    public int Next(int minValue, int maxValue)
    {
      if (minValue > maxValue)
        throw new ArgumentOutOfRangeException($"{minValue} is greater than {maxValue}.");
      if (minValue == maxValue) return minValue;
      this.t = this.x ^ (this.x << 11);
      this.x = this.y; this.y = this.z; this.z = this.w;
      this.w = (this.w ^ (this.w >> 19)) ^ (this.t ^ (this.t >> 8));
      return (int)(this.w % (maxValue - minValue)) + minValue;
    }
  }
}