使用 x,y 坐标作为种子的随机数生成器

6

我正在寻找一种高效、均匀分布的伪随机数生成器,它可以为平面上任意整数点(以坐标 x 和 y 作为函数输入)生成一个随机整数。

int rand(int x, int y)

它必须在每次输入相同坐标时提供相同的随机数。

您是否了解可以用于此类问题以及更高维度中的算法?

我已经尝试使用普通的伪随机数生成器,例如LFSR,并将x、y坐标合并在一起作为种子值。就像这样:

int seed = x << 16 | (y & 0xFFFF)

这种方法的明显问题是种子不会被多次迭代,而是为每个x、y点重新初始化。如果您可视化结果,这将导致非常丑陋的非随机模式。
我已经知道了一种方法,它使用某些大小为256的洗牌置换表,并从中获取一个随机整数。
int r = P[x + P[y & 255] & 255];

但我不想使用这种方法,因为它的范围非常有限,受限制的时间长度和高内存消耗。

感谢任何有用的建议!


我使用C++,但问题与语言无关。 - bakkaa
你需要多大的整数? - Mark Ransom
“x”和“y”的最小值和最大值是多少? - user3386109
@Mark Ransom 整数大小并不重要。我使用32位整数。 - bakkaa
你需要的范围越小,制作看上去随机的东西就越容易 - 这就是我问的原因。32位将是一个挑战。 - Mark Ransom
显示剩余3条评论
3个回答

8

我发现了一种基于xxhash算法的非常简单、快速和足够的哈希函数。

// cash stands for chaos hash :D
int cash(int x, int y){   
    int h = seed + x*374761393 + y*668265263; //all constants are prime
    h = (h^(h >> 13))*1274126177;
    return h^(h >> 16);
}

现在,它比我上面描述的查找表方法快得多,看起来同样随机。我不知道与xxhash相比,随机属性是否好,但只要对于肉眼来说看起来是随机的,它就是我的目的的一个公平解决方案。
下面是输入像素坐标的效果: enter image description here

构建良好的哈希函数是一个艰难的过程,而你的看起来非常糟糕。首先,我怀疑哈希函数存在溢出问题;其次,相同输入的不同种子会导致相同的结果:12980285595313403384 / 12980285592294613320;类似但不同的输入也会导致相似但不同的结果:12709661718304170483 / 13558132204872960782。这根本不是一个哈希函数(如果想要雪崩效应和协同作用的话)。如果它对你有用,那很好。但这甚至比线性同余发生器还要糟糕(而且可能不会更快)。 - sascha
4
我已经添加了函数的绘图。肉眼看起来相当随机。我的算法与xxhash类似,只是减少了一些扭曲以提高性能。xxhash内部也使用溢出进行计算。例如从第370行到375行。这不是算法的缺陷。我不能使用像LCG这样的生成器,因为它们被设计用于计算序列中的一个随机数,而在GPU上我必须同时计算它们。 - bakkaa
我很感激这个,对我正在进行的抗锯齿技巧添加一点噪音非常有帮助。我找了一些在GPU上快速运行的非加密哈希函数,并且即使它无法通过测试,但这对此非常不错。 - lahwran
@lahwran 谢谢您。我很感激,这很有帮助。 - bakkaa

3

我的方法

通常,我认为你需要一些哈希函数(大多数都设计用于输出随机性;RNG的avalanche效应,CryptoPRNG所需的显式随机性)。请参考线程。

以下代码使用此方法:

  • 1)从您的输入构建可哈希的内容
  • 2)哈希 -> 随机字节(非加密)
  • 3)以某种方式将这些随机字节转换为您的整数范围(很难正确/均匀地执行!)

最后一步是通过方法完成的,该方法似乎不太快,但具有强大的理论保证(使用了选择的答案)。

我使用的哈希函数支持种子,在第3步中将使用它们!

import xxhash
import math
import numpy as np
import matplotlib.pyplot as plt
import time

def rng(a, b, maxExclN=100):
    # preprocessing
    bytes_needed = int(math.ceil(maxExclN / 256.0))
    smallest_power_larger = 2
    while smallest_power_larger < maxExclN:
        smallest_power_larger *= 2

    counter = 0
    while True:
        random_hash = xxhash.xxh32(str((a, b)).encode('utf-8'), seed=counter).digest()
        random_integer = int.from_bytes(random_hash[:bytes_needed], byteorder='little')
        if random_integer < 0:
            counter += 1
            continue # inefficient but safe; could be improved
        random_integer = random_integer % smallest_power_larger
        if random_integer < maxExclN:
            return random_integer
        else:
            counter += 1

test_a = rng(3, 6)
test_b = rng(3, 9)
test_c = rng(3, 6)
print(test_a, test_b, test_c) # OUTPUT: 90 22 90

random_as = np.random.randint(100, size=1000000)
random_bs = np.random.randint(100, size=1000000)

start = time.time()
rands = [rng(*x) for x in zip(random_as, random_bs)]
end = time.time()

plt.hist(rands, bins=100)
plt.show()
print('needed secs: ', end-start)
# OUTPUT: needed secs:  15.056888341903687 -> 0,015056 per sample
# -> possibly heavy-dependence on range of output

可能的改进:

  • 从某些来源(urandom;可以放入str)添加额外的熵
  • 创建一个类并进行初始化以记忆预处理(如果对每个采样都进行预处理,则代价高昂)
  • 处理负整数;也许只需使用abs(x)

假设:

  • 输出范围为[0,N)->只需针对其他内容进行转移!
  • 输出范围比哈希输出(可能使用xxh64)小(位)

评估:

检查随机性/均匀性

1D-Histogram of output -> looks good 2D-Representation -> looks good

检查是否关于输入是确定性的

2D-Representation with equal input-vectors


谢谢你的回答。你认为我可以使用遗传算法来找到一个合适的哈希函数吗?我想要一个真正快速的哈希函数,比GPU中的查找表更快,所以我只能使用简单的位运算。随机数不需要具有密码学安全性,它们只需要在视觉上看起来是随机的。因此,我想可以测量编码在DNA中的不同算法的分布特性和周期长度,并交叉繁殖和/或突变好的算法,在几代之后找到一个好的解决方案。 - bakkaa
1
绝对不是!我使用的xxhash是一种非加密哈希函数,速度与内存一样快。如果没有大量研究,你是无法超越它的。使用GAs生成代码也非常困难,即使是结构化输出,想要混乱的输出更加困难。只需使用快速哈希函数即可。现在还有两个问题:是否有更快的GPU哈希函数(看起来你的目标就是这个)?以及更重要的是:如何在GPU上实现第三步。我的方法不快,甚至在GPU上会更慢。您必须决定需要多少质量。类似取模的简单方案可能已经足够了。 - sascha
我已经在GPU上实现了一个简化版的xxhash。只需一个整数输入而不是字节数组,许多复杂性就消失了。我只是使用了这段代码将我的整数输入x和y转换为一个整数。随机性非常好,但比使用查找表稍微慢一些。因此,我将尝试找到一个更简单的哈希函数,使用更少的乘法。如果我找到了,我会在这里发布。感谢您的有益答案。 - bakkaa
修改之前的评论:所以我对我的输入执行了这个操作 '(x << 16) | y',因为这是我能想到的最快的方法。缺点是,如果 x 和 y 值超过了 16 位,就会发生许多冲突,因此它们受到了限制。在三维或四维中,我认为这将太大程度地限制我的输入范围。 - bakkaa
我找到了一个哈希函数,你觉得怎么样?对我来说结果很好。 - bakkaa

1
你可以使用各种随机性提取器来实现你的目标。至少有两个来源可以寻找解决方案。

总之,你可以优先使用:

  1. AES-CBC-MAC使用一个随机密钥(可以是固定的并重复使用)
  2. HMAC,最好使用SHA2-512
  3. SHA系列哈希函数(如SHA1、SHA256等);使用一个随机的最终块(例如,在结尾处使用一个大的随机盐)
因此,您可以连接您的坐标,获取它们的字节,添加一个随机密钥(用于AES和HMAC)或SHA的盐,并且您的输出具有足够的熵。根据NIST的说法,输出熵取决于输入熵:
假设您使用SHA1;因此n = 160位。假设m = input_entropy(您的坐标的熵)
- 如果m >= 2n,则output_entropy = n = 160位 - 如果2n < m <= n,则最大output_entropy = m(但不能保证完全熵)。 - 如果m < n,则最大output_entropy = m(这是您的情况)
请参见NIST sp800-90c(第11页)

根据NIST的说法,输出熵取决于输入熵 - 这是一个强有力的观点!这意味着,如果输入具有低熵(可能具有高输出范围;至少在间隔方面更容易观察),那么我的方法(以及一般所有没有外部熵源的方法)可能会遇到困难。根据情况,考虑添加额外的熵是值得考虑的。 - sascha

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接