【问题标题】:Deterministic random noise generator from multiple 64-bit inputs?来自多个 64 位输入的确定性随机噪声发生器?
【发布时间】:2015-02-20 21:46:13
【问题描述】:

我正在尝试在 C# 中创建一个伪随机数生成器,它在给定相同的输入集的情况下产生相同的输出。它需要速度快,接受 64 位数字,并且特别重要的是允许任意一组输入。输出应该是 [0 和 1} 之间的双精度值。

输入大部分是在应用程序生命周期内保持不变的种子值,以及一个或多个其他数值,例如 x、y、z 和 w 坐标,它们本身可能是输入 int32、int64 或 double。

我尝试过调整我发现的其他一些随机数生成器,但我一直遇到大量的聚集问题,主要是因为我不了解围绕该主题的理论或素数的数学属性,这似乎在大多数情况下都起作用。

我想使用此函数生成随机噪声,这些随机噪声将在许多其他算法(包括我自己的算法和标准算法(例如 Perlin 噪声)等)内部使用。

我该怎么办?

【问题讨论】:

  • “特别重要的是允许任意一组输入。” - 您可以散列任何值并将该散列用作种子。
  • 我刚刚得出了同样的结论,即构建我的输入的字节数组并生成它的哈希。
  • 如果您要从字节数组生成哈希,您可能需要注意负零和浮点精度。
  • 看起来 4D 或 5D Perlin 或 Simplex 噪声可以完成这项工作。但是 x、y、z 和 w 的目的是什么?您是否期望噪声是沿这四个维度的连续函数?听起来你真的不知道如何描述你正在寻找的噪音的属性,所以也许你应该提供更多关于你到底需要什么的细节。
  • @stslavik 实际上 PRNG 正是我想要的,但在谷歌搜索失败后,我找不到任何输入任意数量的东西,所以我想我必须做我自己的。另一个区别当然是它需要保持不变 - 即我发现了许多生成器,但它们都被设计为在连续运行时改变它们的输出。在给定相同的输入集的情况下,我希望它每次都生成相同的输出。我并不是说我下面的解决方案是完美的,但有时你只需要运行一些东西,直到有人为你指出更好的方向。

标签: c# random noise


【解决方案1】:

我找到了一个解决方案,我会一直使用它,直到有人说服我为什么不应该这样做。

下面的代码是我的课程的可用摘录。第一个Generate 函数可以接受任意数量的重载,具体取决于您的需要。您将输入转换为字节数组并将它们传递给私有的 Generate 方法,由该方法完成其余的工作。请注意,在内部有一个对 _seed 的引用,它本身只是一个字节数组,由您通过构造函数提供的种子值生成。

另外,代码依赖于this MurMurHash3 algorithm,这非常快。

我已经运行了大量的迭代来检查分布,它似乎分布非常均匀,在给定值周围没有明显的人类明显的聚集。我让它在英特尔酷睿 i7 上在大约 720 毫秒内生成一百万个值,这对于我的目的来说已经足够快了。我还测试了它在纹理上生成 2D 白噪声,噪声看起来非常随机。

public double Generate(double x, double y, double z, double w)
{
    return Generate(
        _seed,
        BitConverter.GetBytes(x),
        BitConverter.GetBytes(y),
        BitConverter.GetBytes(z),
        BitConverter.GetBytes(w)
    );
}

private double Generate(params byte[][] inputs)
{
    var len = 0;
    int i;
    for(i = 0; i < inputs.Length; i++)
        len += inputs[i].Length;
    var buffer = new byte[len];
    var offset = 0;
    for(i = 0; i < inputs.Length; i++)
    {
        var bytes = inputs[i];
        Buffer.BlockCopy(bytes, 0, buffer, offset, bytes.Length);
        offset += bytes.Length;
    }
    return Hash(buffer);
}

private double Hash(byte[] bytes)
{
    var hash = new Murmur3().ComputeHash(bytes);
    var buffer = new byte[8];
    for(var i = 0; i < hash.Length; i++)
        buffer[i%8] ^= hash[i];
    var n = BitConverter.ToInt64(buffer, 0);
    if(n < 0) n = -n;
    if(n == long.MaxValue) n--;
    return n / (double)long.MaxValue;
}

【讨论】:

  • 每秒超过一百万个值实际上非常慢。还要记住-long.MinValue 溢出到long.MinValue,如果你想将结果限制为正符号整数,你可以做n = n &amp; long.MaxValue。
  • @eBusiness 感谢您的提示,我将相应地修改模式代码。鉴于您对速度的评论,您建议我应该怎么做?
  • 摆脱Murmur,对于大块数据可能会很快,但你仍然为初始化付出了沉重的代价。另外,我不确定您认为这将如何使用浮点数作为输入,如果您想要确定性,则必须远离浮点数,除非您真的知道自己在做什么。我想我可以做一些不依赖任何昂贵东西的东西。
【解决方案2】:

这仍然是一个相当慢的 rng,但比基于 Murmur3 的速度快大约 10 倍。为每个生成的数字重新播种是有成本的,因此需要大量对结果都有非系统性影响的种子也是如此。

更新:确实没有任何理由允许弱位,这个版本应该没有明显的模式。

class Prng
{
    const double shift3 = .125;
    const double shift9 = shift3 * shift3 * shift3;
    const double shift27 = shift9 * shift9 * shift9;
    const double shift53 = shift27 * shift27 * 2.0;
    public ulong rndlong(ulong a, ulong b, ulong c, ulong d){
        ulong e = ((a ^ (b >> 14 | b << 50)) + ((c >> 31 | c << 33) ^ (d >> 18 | d << 46)))*1911413418482053185;
        ulong f = (((a >> 30 | a << 34) ^ c) + ((b >> 32 | b << 32) ^ (d >> 50 | d << 14)))*1139072524405308145;
        ulong g = (((a >> 49 | a << 15) ^ (d >> 33 | d << 31)) + (b ^ (c >> 48 | c << 16)))*8792993707439626365;
        ulong h = (((a >> 17 | a << 47) ^ (b >> 47 | b << 17)) + ((c >> 15 | c << 49) ^ d))*1089642907432013597;
        return (e ^ f ^ (g >> 21 | g << 43) ^ (h >> 44 | h << 20)) * 2550117894111961111 +
            ((e >> 20 | e << 44) ^ (f >> 41 | f << 23) ^ (g >> 42 | g << 22) ^ h) * 8786584852613159497 +
            ((e >> 43 | e << 21) ^ (f >> 22 | f << 42) ^ g ^ (h >> 23 | h << 41)) * 3971056679291618767;
    }
    public double rnddouble(ulong a, ulong b, ulong c, ulong d)
    {
        return (double)(rndlong(a, b, c, d) >> 11) * shift53;
    }
}

【讨论】:

  • 我假设你会使用种子作为第一个参数,而你不需要的任何参数都为零?你是如何决定使用什么位移的?我问是因为如果我想要第五个参数,则需要对其进行修改。在这种情况下你有什么建议?
  • 我试图确保每一位输入都可能影响每一位输出。乘以大数将位向上扩展,因此在我的第一次位移时,我确保输入的每一位都出现在位移值的合理低位中,然后在第一次乘法之后,每个输入位的影响被传播在至少一个中间变量的大部分上,然后我基本上再次做同样的事情以“填补”剩余的空白。
  • 获得更长版本的最简单方法是菊花链 4 输入版本,例如:rnddouble(rndlong(in1,in2,in3,in4),in5,in6,in7)
猜你喜欢
  • 2010-11-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-09-22
  • 2011-03-20
  • 1970-01-01
  • 2020-06-18
相关资源
最近更新 更多