2 整数区间非幂的混合函数
Mixing function for non power of 2 integer intervals
我正在寻找一个混合函数,它给定区间 <0, n) 中的整数 returns 相同区间中的随机整数。区间大小 n 通常是 2 的复合非幂数。我需要一对一的功能。它只能使用 O(1) 内存,强烈推荐 O(1) 时间。我不太关心输出的随机性,但在视觉上它应该看起来足够随机(见下一段)。
我想将此函数用作实时渲染器中的像素改组步骤,以 select 渲染像素的顺序(输出将在固定时间后显示,如果未完成但这给了我一个嘈杂但快速的部分预览)。间隔大小 n 将是渲染中的像素数(n = 1920*1080 = 2073600 是一个典型值)。该函数必须是一对一的,这样我才能确定每个像素在完成时都只渲染一次。
我查看了 hash prospector 使用的可逆构建块,但这些大多特定于 2 次幂范围。
我能想到的唯一其他方法是乘以大质数,但它并没有给出特别好的随机输出。
这里还有哪些其他选项?
这是一个基于原根思想的解决方案modulo a prime:
如果 a
是原根 mod p
则函数 g(i) = a^i % p
是小于 p
的非零元素的排列。这对应于Lehmer prng。如果n < p
,则可以得到0, ..., n-1
的排列如下:给定i
在那个范围内,先加1,然后重复乘以a
,得到结果mod p
,直到你得到一个元素是 <= n
,此时你 return 结果 - 1.
为了补充细节,this paper包含一个table,它给出了一系列素数(所有素数都接近2
的各种幂)和相应的原根被选择以便它们产生具有良好统计特性的生成器。这是 table 的一部分,编码为 Python 字典,其中键是素数,原始根是值:
d = {32749: 30805,
65521: 32236,
131071: 66284,
262139: 166972,
524287: 358899,
1048573: 444362,
2097143: 1372180,
4194301: 1406151,
8388593: 5169235,
16777213: 9726917,
33554393: 32544832,
67108859: 11526618,
134217689: 70391260,
268435399: 150873839,
536870909: 219118189,
1073741789: 599290962}
给定 n
(在一定范围内——如果您需要扩大该范围,请参阅论文),您可以找到最小的 p
有效:
def find_p_a(n):
for p in sorted(d.keys()):
if n < p:
return p, d[p]
一旦您知道 n
和匹配的 p,a
,以下函数是 0 ... n-1
的排列:
def f(i,n,p,a):
x = a*(i+1) % p
while x > n:
x = a*x % p
return x-1
快速测试:
n = 2073600
p,a = find_p_a(n) # p = 2097143, a = 1372180
nums = [f(i,n,p,a) for i in range(n)]
print(len(set(nums)) == n) #prints True
f()
中的平均乘法次数为 p/n
,在本例中为 1.011
,并且永远不会超过 2
(或稍大一些,因为p
不是 2
的精确幂)。在实践中,这种方法与您的 "multiply by a large prime" 方法没有根本区别,但在这种情况下,因子的选择更加谨慎,而且有时需要多次乘法会增加明显的随机性。
我正在寻找一个混合函数,它给定区间 <0, n) 中的整数 returns 相同区间中的随机整数。区间大小 n 通常是 2 的复合非幂数。我需要一对一的功能。它只能使用 O(1) 内存,强烈推荐 O(1) 时间。我不太关心输出的随机性,但在视觉上它应该看起来足够随机(见下一段)。
我想将此函数用作实时渲染器中的像素改组步骤,以 select 渲染像素的顺序(输出将在固定时间后显示,如果未完成但这给了我一个嘈杂但快速的部分预览)。间隔大小 n 将是渲染中的像素数(n = 1920*1080 = 2073600 是一个典型值)。该函数必须是一对一的,这样我才能确定每个像素在完成时都只渲染一次。
我查看了 hash prospector 使用的可逆构建块,但这些大多特定于 2 次幂范围。
我能想到的唯一其他方法是乘以大质数,但它并没有给出特别好的随机输出。
这里还有哪些其他选项?
这是一个基于原根思想的解决方案modulo a prime:
如果 a
是原根 mod p
则函数 g(i) = a^i % p
是小于 p
的非零元素的排列。这对应于Lehmer prng。如果n < p
,则可以得到0, ..., n-1
的排列如下:给定i
在那个范围内,先加1,然后重复乘以a
,得到结果mod p
,直到你得到一个元素是 <= n
,此时你 return 结果 - 1.
为了补充细节,this paper包含一个table,它给出了一系列素数(所有素数都接近2
的各种幂)和相应的原根被选择以便它们产生具有良好统计特性的生成器。这是 table 的一部分,编码为 Python 字典,其中键是素数,原始根是值:
d = {32749: 30805,
65521: 32236,
131071: 66284,
262139: 166972,
524287: 358899,
1048573: 444362,
2097143: 1372180,
4194301: 1406151,
8388593: 5169235,
16777213: 9726917,
33554393: 32544832,
67108859: 11526618,
134217689: 70391260,
268435399: 150873839,
536870909: 219118189,
1073741789: 599290962}
给定 n
(在一定范围内——如果您需要扩大该范围,请参阅论文),您可以找到最小的 p
有效:
def find_p_a(n):
for p in sorted(d.keys()):
if n < p:
return p, d[p]
一旦您知道 n
和匹配的 p,a
,以下函数是 0 ... n-1
的排列:
def f(i,n,p,a):
x = a*(i+1) % p
while x > n:
x = a*x % p
return x-1
快速测试:
n = 2073600
p,a = find_p_a(n) # p = 2097143, a = 1372180
nums = [f(i,n,p,a) for i in range(n)]
print(len(set(nums)) == n) #prints True
f()
中的平均乘法次数为 p/n
,在本例中为 1.011
,并且永远不会超过 2
(或稍大一些,因为p
不是 2
的精确幂)。在实践中,这种方法与您的 "multiply by a large prime" 方法没有根本区别,但在这种情况下,因子的选择更加谨慎,而且有时需要多次乘法会增加明显的随机性。