我有32个字节的随机数据。
我想生成介于0-9和0-100之间的随机数。
如果我使用任意精度算术(bignum)库,并将32个字节视为大数,我可以简单地做到:
random = random_source % range;
random_source = random_source / range;
按照我喜欢的频率(具有不同的范围)进行操作,直到范围的乘积接近2 ^ 256。
有没有一种方法可以仅使用(固定大小)整数算法?
答案 0 :(得分:2)
/* 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;
}
答案 1 :(得分:2)
当然,您可以通过以256为基数的长除法(或上推乘法)来完成此操作。就像您在小学学习过的长除法一样,只是字节而不是数字。它涉及依次对每个字节进行除法和余数级联。请注意,您还需要知道如何使用大数,并且当您使用大数且它变小时,对范围中较大值的偏见就会增加。例如,如果您只剩下110,而您要求rnd(100),则值0-9比每个10-99的可能性高10%。
但是,您实际上并不需要bignum技术,您可以使用算术编码压缩中的思想,在此过程中您可以构建单个数字而无需实际处理整个事情。
如果从一个无符号的uint_32缓冲区中读取4个字节开始,则它的范围为0..4294967295,最大值为4294967296(不包括在内)。我将此合成值称为“结转”,并且将此独占最大值对于记录也很重要。
[为简单起见,您可能先读取3个字节到缓冲区,最大产生16M。这避免了必须处理无法保存在32位整数中的4G值。]
有2种方法可以使用此方法,均涉及准确性:
向下流:
做你的模数范围。模数是您的随机答案。除法结果是您的新结转额,并且射程较小。
假设您想要0..99,所以您对100取模,则您的上半部分的最大范围是42949672(4294967296/100),您可以继续下一个随机请求
我们还不能输入另一个字节...
假设您现在想要0..9,所以您对10取模,现在您的上半部分的范围是0..4294967(42949672/100)
由于max小于16M,我们现在可以输入下一个字节。将其乘以当前的最大值4294967,并将其加到结转中。最大值也乘以256-> 1099511552
此方法对较小的值略有偏差,因为在“下一个最大”时间中为1,所以值的可用范围将不是完整范围,因为最后一个值被截断了,而是选择保持3-4最好的最大字节数,可以最大程度地减少偏差。它只会在1600万次中最多出现1次。
此算法的计算成本为div除以结转和最大值的随机范围,然后乘以每次输入新字节的乘积。我认为编译器将优化模数
流式传输:
说要0..99
将最大值除以范围,得到nextmax,然后将结转除以nextmax。现在,您的随机数出现在除法结果中,余数形成您继续获取下一个随机数的值。
当nextmax小于16M时,只需将nextmax和您的结转数乘以256,然后添加下一个字节。
如果此方法的不利之处在于,取决于用于生成nextmax的除法,则最大值结果(即99或9)会严重偏离,或者有时会生成超值(100)-这取决于是否舍入或向下进行第一次分组。
这里的计算成本还是2分,假设编译器优化程序将div和mod操作混合在一起。乘以256很快。
在两种情况下,您都可以选择说,如果输入结转值在此“高偏置范围”内,那么您将执行另一种方法。您甚至可以在两种技术之间进行振荡-优先使用第二种技术,但是如果第二种技术产生了超值,则可以使用第一种技术,尽管就其自身而言,当结转时,两种技术都可能会偏向相似的输入随机流值接近最大值。可以通过使第二种方法生成-1作为超出范围的值来减小此偏差,但是这些修复方法中的每一个都增加了一个额外的乘法步骤。
请注意,在算术编码中,当提取每个符号时,将有效地丢弃此溢出区。确保在解码期间不会出现这些边缘值,这会导致轻微的次优压缩。