假设我有一个真正随机的字节数组(例如从熵源捕获)。
byte[] myTrulyRandomBytes = MyEntropyHardwareEngine.GetBytes(8);
现在,我希望获得一个随机的双精度浮点值,但是在0和正1之间的 (就像执行Random.NextDouble()
函数一样)。
简单地将8个随机字节的数组传递给BitConverter.ToDouble()
会产生奇怪的结果,但最重要的是,结果几乎不会小于1.
我对位操作很好,但浮点数的格式对我来说一直都很神秘。我尝试了很多位组合来应用随机性,并且最终总是发现数字要么刚刚超过1,要么非常接近0,要么非常大。
有人可以解释double
中哪些位应该是随机的,以使其在0和1范围内随机?
答案 0 :(得分:5)
这比你想象的容易;它完全是关于缩放(从0-1范围到其他范围时也是如此)。
基本上,如果你知道你有64个真正的随机位(8个字节),那么就这样做:
double zeroToOneDouble = (double)(BitConverter.ToUInt64(bytes) / (decimal)ulong.MaxValue);
这种算法的问题出现在你的"随机"比特实际上并不是随机的。当您需要专门的算法时,例如Mersenne Twister。
答案 1 :(得分:5)
虽然已经给出了工作答案,但我会给另一个,看起来更糟,但不是:
long asLong = BitConverter.ToInt64(myTrulyRandomBytes, 0);
double number = (double)(asLong & long.MaxValue) / long.MaxValue;
从ulong
转换为double
的问题在于它并非硬件直接支持,因此编译为:
vxorps xmm0,xmm0,xmm0
vcvtsi2sd xmm0,xmm0,rcx ; interpret ulong as long and convert it to double
test rcx,rcx ; add fixup if it was "negative"
jge 000000000000001D
vaddsd xmm0,xmm0,mmword ptr [00000060h]
vdivsd xmm0,xmm0,mmword ptr [00000068h]
鉴于我的建议,它会更好地编译:
vxorps xmm0,xmm0,xmm0
vcvtsi2sd xmm0,xmm0,rcx
vdivsd xmm0,xmm0,mmword ptr [00000060h]
两者都在.NET 4中使用x64 JIT进行了测试,但这一般适用于将ulong
转换为double
的好方法。
请注意,这个以及所呈现的ulong示例可以精确地产生1.0并且在相邻结果之间分配具有稍微不同的间隙的值,因为它们不会除以2的幂。您可以将它们排除在1.0之外,并获得稍微均匀的间距(但请参见下面的第一个图,有一堆不同的间隙,但这种方式非常规则),如下所示:
long asLong = BitConverter.ToInt64(myTrulyRandomBytes, 0);
double number = (double)(asLong & long.MaxValue) / ((double)long.MaxValue + 1);
作为一个非常好的奖励,你现在可以将除法改为乘法(两个幂通常有倒数)
long asLong = BitConverter.ToInt64(myTrulyRandomBytes, 0);
double number = (double)(asLong & long.MaxValue) * 1.08420217248550443400745280086994171142578125E-19;
对于ulong来说,如果你真的想要使用它,那就相同了。
由于您似乎也对如何使用double
- 位技巧感兴趣,我也可以证明这一点。
由于整个有效数/指数交易,它不能以超直接的方式完成(只需重新解释位和它的位),主要是因为选择指数均匀地解决了麻烦(使用一个统一的指数,由于大多数指数在那里,所以数字必然优先于0附近聚集。
但是,如果指数是固定的,那么在该区域内制作double
均匀是很容易的。这不能是0比1,因为它涵盖了很多指数,但它可以是1到2然后我们可以减去1。
首先掩盖那些不会成为有效数字一部分的位:
x &= (1L << 52) - 1;
放入指数(1.0 - 2.0范围,不包括2)
x |= 0x3ff0000000000000;
重新解释并调整偏移量1:
return BitConverter.Int64BitsToDouble(x) - 1;
也应该很快。一个不幸的副作用是,这次它真的确实花费了一点熵,因为只有52但可能有53.这种方式总是留下最低有效位(隐含位窃取)一点)。
对分发有一些担忧,我现在将讨论。
选择随机(u)长并将其除以最大值的方法显然具有统一选择的(u)长,并且在此之后发生的事实际上是有趣的。结果可以合理地称为均匀分布,但是如果你把它看作一个离散的分布(它实际上是它),它看起来(定性地)像这样:(所有小流器的例子)
忽略&#34;更厚的&#34;线条和更宽的间隙,这只是直方图很有趣。这些图使用2的幂除法,因此实际上没有间距问题,它只是奇怪地绘制。
Top是当你使用太多位时发生的事情,就像将一个完整的(u)long除以其最大值时所发生的那样。这使得较低的浮点数具有更好的分辨率,但是许多不同的(u)长度被映射到较高区域中的相同浮点数。如果你&#34;缩小&#34;这不一定是坏事。 密度在任何地方都是一样的。
当分辨率限制在最坏的情况(0.5到1.0区域)时,底部就会发生这种情况,你可以通过首先限制位数然后进行&#34;缩放整数来实现。应对。我的第二个建议是没有实现这一点,它只限于一半那个分辨率。
对于它的价值,NextDouble
System.Random
将非负int
缩放到0.0 .. 1.0范围内。这个分辨率显然低于它可能的 lot 。它还使用不能int
的{{1}}因此缩放大约1 /(2 31 -1)(不能用双精度表示,所以略微圆整),所以在相邻的可能结果之间实际上有33个略微不同的间隙,尽管大多数间隙是相同的距离。
由于int.MaxValue
与现在可以强制使用的内容相比很小,因此您可以轻松生成int.MaxValue
的所有可能结果并进行检查,例如我运行了这个:
NextDouble
答案 2 :(得分:3)
我不知道它是最好的解决方案,但它应该做的工作:
ulong asLong = BitConverter.ToUInt64(myTrulyRandomBytes, 0);
double number = (double)asLong / ulong.MaxValue;
我所做的就是将字节数组转换为ulong
,然后将其除以它的最大值,以便结果在0和1之间。
答案 3 :(得分:2)
要确保long
值在0到1的范围内,您可以应用以下掩码:
long longValue = BitConverter.ToInt64(myTrulyRandomBytes, 0);
longValue &= 0x3fefffffffffffff;
结果值保证在[0, 1)
范围内
备注。 0x3fefffffffffffff
值非常接近1,将打印为1
,但实际上小于1。
如果要使生成的值更大,可以将指数的较高位设置为1
。例如:
longValue |= 0x03c00000000000000;
答案 4 :(得分:1)
为您打印出来的简单代码。
for (double i = 0; i < 1.0; i+=0.05)
{
var doubleToInt64Bits = BitConverter.DoubleToInt64Bits(i);
Console.WriteLine("{0}:\t{1}", i, Convert.ToString(doubleToInt64Bits, 2));
}
0.05: 11111110101001100110011001100110011001100110011001100110011010
0.1: 11111110111001100110011001100110011001100110011001100110011010
0.15: 11111111000011001100110011001100110011001100110011001100110100
0.2: 11111111001001100110011001100110011001100110011001100110011010
0.25: 11111111010000000000000000000000000000000000000000000000000000
0.3: 11111111010011001100110011001100110011001100110011001100110011
0.35: 11111111010110011001100110011001100110011001100110011001100110
0.4: 11111111011001100110011001100110011001100110011001100110011001
0.45: 11111111011100110011001100110011001100110011001100110011001100
0.5: 11111111011111111111111111111111111111111111111111111111111111
0.55: 11111111100001100110011001100110011001100110011001100110011001
0.6: 11111111100011001100110011001100110011001100110011001100110011
0.65: 11111111100100110011001100110011001100110011001100110011001101
0.7: 11111111100110011001100110011001100110011001100110011001100111
0.75: 11111111101000000000000000000000000000000000000000000000000001
0.8: 11111111101001100110011001100110011001100110011001100110011011
0.85: 11111111101011001100110011001100110011001100110011001100110101
0.9: 11111111101100110011001100110011001100110011001100110011001111
0.95: 11111111101110011001100110011001100110011001100110011001101001