虽然已经给出了可行的答案,我会提供另一个答案,看起来更糟糕但实际上并不是:
long asLong = BitConverter.ToInt64(myTrulyRandomBytes, 0);
double number = (double)(asLong & long.MaxValue) / long.MaxValue;
vxorps xmm0,xmm0,xmm0
vcvtsi2sd xmm0,xmm0,rcx
test rcx,rcx
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
。
不要担心失去熵位:首先,0.0 和 1.0 之间只有 262 个 double,大多数比较小的 double 不能被选择,因此可能的结果数量更少。
请注意,这以及所提供的 ulong 示例都可能导致恰好为 1.0,并且在相邻结果之间分配值的间隔略有不同,因为它们不是 2 的次幂。您可以将它们更改为排除 1.0 并获得稍微更均匀的间距(但请参见下面的第一个绘图,其中有一堆不同的间隔,但这样做非常规律):
long asLong = BitConverter.ToInt64(myTrulyRandomBytes, 0);
double number = (double)(asLong & long.MaxValue) / ((double)long.MaxValue + 1);
作为一个非常好的额外收益,现在你可以将除法转换为乘法(2的幂通常有倒数)。
long asLong = BitConverter.ToInt64(myTrulyRandomBytes, 0);
double number = (double)(asLong & long.MaxValue) * 1.08420217248550443400745280086994171142578125E-19;
- 对于ulong也是同样的想法,如果你真的想使用它。
- 既然你似乎特别感兴趣如何通过double-bit巧妙地处理它,我也可以展示一下。
- 由于整个尾数/指数的关系,它不能以超级直接的方式完成(只需重新解释位并完成),主要原因是选择均匀的指数会带来麻烦(具有均匀指数,数字必定偏向于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个。这种方法总是使最低有效位为零(隐式位占用了一位)。
对于分布存在一些顾虑,我现在来解决。
选择一个随机的长整型数并将其除以最大值的方法显然具有均匀选择的长整型数,之后发生的事情其实很有趣。结果可以被称为均匀分布,但如果你把它看作离散分布(实际上它就是),它看起来(定性地)像这样:(所有示例都是关于minifloats)
![float distribution](https://istack.dev59.com/0Osfr.webp)
忽略“粗”线和更宽的间隙,那只是直方图有点搞笑。这些曲线使用了除以2的幂,所以在现实中没有间隔问题,只是绘制得有点奇怪。
上面是当你使用过多位时会发生的情况,例如在将完整的长整型数除以其最大值时发生的情况。这将为较低的浮点数提供更好的分辨率,但许多不同的长整型数会被映射到高区域中的相同浮点数上。如果你“缩小”密度,这不一定是坏事。
底部是当分辨率在任何情况下都被限制为最差情况(0.5到1.0区域)时会发生的情况,你可以通过先限制位数然后执行“比例整数”操作来实现这一点。我第二个建议使用的位操作无法达到这个目标,它只能达到该分辨率的一半。
值得一提的是,
System.Random
中的
NextDouble
将非负
int
缩放到0.0..1.0范围内。它的分辨率明显比可能的更低。它还使用了一个不能为
int.MaxValue
的
int
,因此缩放大约为1/(2
31-1)(不能用double表示,所以有些舍入),因此实际上存在33个略微不同的相邻可能结果之间的差距,虽然大多数空隙的距离是相同的。
由于
int.MaxValue
与今天的暴力破解相比很小,你可以轻松生成
NextDouble
的所有可能结果并检查它们,例如我运行了以下代码:
const double scale = 4.6566128752458E-10;
double prev = 0;
Dictionary<long, int> hist = new Dictionary<long, int>();
for (int i = 0; i < int.MaxValue; i++)
{
long bits = BitConverter.DoubleToInt64Bits(i * scale - prev);
if (!hist.ContainsKey(bits))
hist[bits] = 1;
else
hist[bits]++;
prev = i * scale;
if ((i & 0xFFFFFF) == 0)
Console.WriteLine("{0:0.00}%", 100.0 * i / int.MaxValue);
}
decimal
,而不是double
。Double
没有足够的精度来区分高ulong
值之间的差异。例如(double)(ulong.MaxValue) == (double)(ulong.MaxValue - 1)
。 - Jakub LortzNaN
值。 - Dai