在没有 bignum 库的情况下生成 32 字节随机数据范围内的随机数
Generating random numbers in ranges from 32 bytes of random data, without bignum library
我有 32 个字节的随机数据。
我想在 0-9 和 0-100 之间的可变范围内生成随机数。
如果我使用任意精度算术 (bignum) 库,并将 32 个字节视为一个大数字,我可以简单地这样做:
random = random_source % range;
random_source = random_source / range;
随心所欲(使用不同的范围),直到范围的乘积接近 2^256。
有没有办法只使用(固定大小的)整数运算来做到这一点?
/* The 32 bytes in data are treated as a base-256 numeral following a "." (a
radix point marking where fractional digits start). This routine
multiplies that numeral by range, updates data to contain the fractional
portion of the product, and returns the integer portion.
8-bit bytes are assumed, or "t /= 256" could be changed to
"t >>= CHAR_BIT". But then you have to check the sizes of int
and unsigned char to consider overflow.
*/
int r(int range, unsigned char *data)
{
// Start with 0 carried from a lower position.
int t = 0;
// Iterate through each byte.
for (int i = 32; 0 < i;)
{
--i;
// Multiply next byte by our multiplier and add the carried data.
t = data[i] * range + t;
// Store the low bits of the result.
data[i] = t;
// Carry the high bits of the result to the next position.
t /= 256;
}
// Return the bits that carried out of the multiplication.
return t;
}
当然你可以通过 base 256 long division(或上推乘法)来做到这一点。它就像你在小学学习的长 division,但用字节而不是数字。它涉及对每个字节依次进行 divide 和余数的级联。请注意,您还需要了解您是如何使用大数字的,并且当您使用它并且它变小时,对范围内较大值的偏差会增加。例如,如果您只剩下 110,并且您要求 rnd(100),则值 0-9 的可能性比 10-99 高 10%。
但是,您实际上并不需要 bignum 技术,您可以使用 ideas 来自算术编码压缩,您可以在其中构建单个数字,而无需实际处理整个事情。
如果您首先将 4 个字节读取到无符号 uint_32 缓冲区,则它的范围为 0..4294967295,不包括最大值 4294967296。我将此合成值称为 "carry forward",这个唯一的最大值也很重要,需要记录。
[为简单起见,您可以从读取 3 个字节到缓冲区开始,最多生成 16M。这避免了必须处理不能保存在 32 位整数中的 4G 值。]
有 2 种使用方法,都具有准确性影响:
向下流:
做你的 modulo 范围。 modulo 是您的随机答案。 division 结果是您的新结转结果,范围更小。
假设你想要 0..99,所以你 modulo 100,你的上半部分的范围最大为 42949672 (4294967296/100),你可以为下一个随机请求结转
我们还不能再输入一个字节...
假设你现在想要 0..9,所以你 modulo 乘以 10,现在你的上半部分的范围是 0..4294967 (42949672/100)
由于 max 小于 16M,我们现在可以引入下一个字节。将它乘以当前最大值 4294967 并将其添加到结转中。最大值也乘以 256 -> 1099511552
这个方法对小值有轻微的偏向,因为1在"next max"次,可用值范围不会是全范围,因为最后一个值被截断了,但是通过选择保持最多 3-4 个好字节,偏差最小化。它只会在 1600 万次中最多出现 1 次。
该算法的计算成本是 div 乘以 carried forward 和 max 的随机范围,然后每次输入新字节时乘以。我假设编译器会优化 modulo
向上流:
假设你想要 0..99
Divide 你的最大范围,得到下一个最大值,然后 divide 由下一个最大值结转。现在,您的随机数在 division 结果中,余数构成您结转以获得下一个随机数的值。
当 nextmax 小于 16M 时,只需将 nextmax 和您的结转乘以 256 并添加下一个字节。
downside 如果这个方法是依赖于 division 用于生成 nextmax,最高值结果(即 99 或 9)严重偏向,或者有时你会生成超值(100) - 这取决于你是向上还是向下进行第一个 division。
此处的计算成本再次为 2 divides,假设编译器优化器混合了 div 和 mod 操作。乘以 256 很快。
在这两种情况下,您都可以选择说如果输入结转值在此 "high bias range" 中,那么您将执行不同的技术。您甚至可以在这两种技术之间摇摆不定——优先使用第二种,但如果它产生超值,则使用第一种技术,尽管就其本身而言,这两种技术在结转时可能会偏向于相似的输入随机流值接近最大值。可以通过使第二种方法生成 -1 作为超出范围来减少这种偏差,但是这些修复中的每一个都增加了一个额外的乘法步骤。
请注意,在算术编码中,当每个符号被提取时,这个溢出区域被有效地丢弃了。在解码期间保证不会出现这些边缘值,这会导致轻微的次优压缩。
我有 32 个字节的随机数据。
我想在 0-9 和 0-100 之间的可变范围内生成随机数。
如果我使用任意精度算术 (bignum) 库,并将 32 个字节视为一个大数字,我可以简单地这样做:
random = random_source % range;
random_source = random_source / range;
随心所欲(使用不同的范围),直到范围的乘积接近 2^256。
有没有办法只使用(固定大小的)整数运算来做到这一点?
/* The 32 bytes in data are treated as a base-256 numeral following a "." (a
radix point marking where fractional digits start). This routine
multiplies that numeral by range, updates data to contain the fractional
portion of the product, and returns the integer portion.
8-bit bytes are assumed, or "t /= 256" could be changed to
"t >>= CHAR_BIT". But then you have to check the sizes of int
and unsigned char to consider overflow.
*/
int r(int range, unsigned char *data)
{
// Start with 0 carried from a lower position.
int t = 0;
// Iterate through each byte.
for (int i = 32; 0 < i;)
{
--i;
// Multiply next byte by our multiplier and add the carried data.
t = data[i] * range + t;
// Store the low bits of the result.
data[i] = t;
// Carry the high bits of the result to the next position.
t /= 256;
}
// Return the bits that carried out of the multiplication.
return t;
}
当然你可以通过 base 256 long division(或上推乘法)来做到这一点。它就像你在小学学习的长 division,但用字节而不是数字。它涉及对每个字节依次进行 divide 和余数的级联。请注意,您还需要了解您是如何使用大数字的,并且当您使用它并且它变小时,对范围内较大值的偏差会增加。例如,如果您只剩下 110,并且您要求 rnd(100),则值 0-9 的可能性比 10-99 高 10%。
但是,您实际上并不需要 bignum 技术,您可以使用 ideas 来自算术编码压缩,您可以在其中构建单个数字,而无需实际处理整个事情。
如果您首先将 4 个字节读取到无符号 uint_32 缓冲区,则它的范围为 0..4294967295,不包括最大值 4294967296。我将此合成值称为 "carry forward",这个唯一的最大值也很重要,需要记录。
[为简单起见,您可以从读取 3 个字节到缓冲区开始,最多生成 16M。这避免了必须处理不能保存在 32 位整数中的 4G 值。]
有 2 种使用方法,都具有准确性影响:
向下流:
做你的 modulo 范围。 modulo 是您的随机答案。 division 结果是您的新结转结果,范围更小。
假设你想要 0..99,所以你 modulo 100,你的上半部分的范围最大为 42949672 (4294967296/100),你可以为下一个随机请求结转
我们还不能再输入一个字节...
假设你现在想要 0..9,所以你 modulo 乘以 10,现在你的上半部分的范围是 0..4294967 (42949672/100)
由于 max 小于 16M,我们现在可以引入下一个字节。将它乘以当前最大值 4294967 并将其添加到结转中。最大值也乘以 256 -> 1099511552
这个方法对小值有轻微的偏向,因为1在"next max"次,可用值范围不会是全范围,因为最后一个值被截断了,但是通过选择保持最多 3-4 个好字节,偏差最小化。它只会在 1600 万次中最多出现 1 次。
该算法的计算成本是 div 乘以 carried forward 和 max 的随机范围,然后每次输入新字节时乘以。我假设编译器会优化 modulo
向上流:
假设你想要 0..99
Divide 你的最大范围,得到下一个最大值,然后 divide 由下一个最大值结转。现在,您的随机数在 division 结果中,余数构成您结转以获得下一个随机数的值。
当 nextmax 小于 16M 时,只需将 nextmax 和您的结转乘以 256 并添加下一个字节。
downside 如果这个方法是依赖于 division 用于生成 nextmax,最高值结果(即 99 或 9)严重偏向,或者有时你会生成超值(100) - 这取决于你是向上还是向下进行第一个 division。
此处的计算成本再次为 2 divides,假设编译器优化器混合了 div 和 mod 操作。乘以 256 很快。
在这两种情况下,您都可以选择说如果输入结转值在此 "high bias range" 中,那么您将执行不同的技术。您甚至可以在这两种技术之间摇摆不定——优先使用第二种,但如果它产生超值,则使用第一种技术,尽管就其本身而言,这两种技术在结转时可能会偏向于相似的输入随机流值接近最大值。可以通过使第二种方法生成 -1 作为超出范围来减少这种偏差,但是这些修复中的每一个都增加了一个额外的乘法步骤。
请注意,在算术编码中,当每个符号被提取时,这个溢出区域被有效地丢弃了。在解码期间保证不会出现这些边缘值,这会导致轻微的次优压缩。