我正在尝试理解一个包含以下代码行的C程序中的.h文件:
#define random ((float) rand() / (float)((1 << 31) - 1))
该C程序还包括
<math.h>
库。我的猜测是它只会从区间[0,1]上的均匀分布中产生一个随机数,这正确吗?
使用 RAND_MAX
。那是为什么存在的。它可能比 1 << 31 - 1
要小得多。
1 << 31
在具有 32 位 int
或更少的平台上会产生不确定行为,这是非常普遍的情况。不要这样做!
请注意,如果您不想恢复值1(通常情况下),则在分母上使用 RAND_MAX + 1.0
。 1.0
强制执行浮点运算:如果您写入 RAND_MAX + 1
,则可能会溢出整数类型。
RAND_MAX + 1.0
不是 double
类型吗? - Fitzwilliam Bennet-Darcyrand
函数返回一个0到RAND_MAX
之间的值。这个程序假设RAND_MAX
是2^31 - 1,然后将结果除以该数字。
所以,如果上述假设成立,则此宏会给出一个[0,1]之间的数字。它不是均匀随机分布,而是“伪随机”值。
至少它应该做到这一点。这个表达式(1 << 31)
会引发未定义行为(假设int
为32位或更小),因为常量1
的类型是int
,左移31位使其超出了int
的范围。实际上,如果使用二进制补码表示法,则一些编译器将允许进行这种移位,然后后续的-1
将把它放回范围内,但不能依赖于此。
可以通过使用(1U << 31)
来避免这种未定义行为,这样常量1
就具有unsigned int
类型,以便移位在范围内。更好的选择是忘记移位和减法,直接使用0x7fffffff
。
但是为了最大限度地提高可移植性,应该定义如下:
#define random ((float)rand() / RAND_MAX)
但是仍然存在一个问题。一个浮点数通常有23位的尾数。如果rand
返回32位值,你将得不到良好的数字分布。最好使用double
,它有52位的尾数:
#define random ((double)rand() / RAND_MAX)
RAND_MAX
方法更好。但是使用float
很可能会失去精度,并且通常将分布限制为不超过23位(IEEE float
的尾数)。 - too honest for this sitefloat
大约有24位精度。如果将rand()
的结果转换为超过24位,将得到一个float
,由于四舍五入而可能与原始值不同。这会削弱/破坏随机数生成的均匀性。不同的rand()
结果将产生相同的答案。另请参见@Olaf
解决此问题很棘手,因为OP显然想要从集合[0,1/2,147,483,648,2/2,147,483,648,... 2,147,483,647/2,147,483,648]中获得均匀分布的随机数,但考虑到float
的可能精度限制,这是不可能的。
最糟糕的是 (1 << 31)
是未定义行为 UB,除非 int
至少有33位。将1进行移位操作到符号位上是UB。C11dr §6.5.7 4.
为了避免 UB,使用 ((1ul << 31) - 1)
。
然而,使用神奇数字 ((1ul << 31) - 1)
比基于 RAND_MAX
的分数更不可靠。
进一步地,(float) ((1ul << 31) - 1)
可能会因为如上所述产生精度损失,它形成的值是 2147483648.0f
而不是不可获取的 2147483647.0f
。OP的代码可能永远也无法生成 1.0f
。
// generate a `double` in the range [0 ... 1)
#define random0to_almost1 (rand() / (RAND_MAX + 1.0))
// or
// generate a `double` in the range [0 ... 1]
#define random0to1 (rand() / (RAND_MAX + 0.0))
double
(通常为53位)的精度超过RAND_MAX
的需求,则会像OP的原始代码一样遇到问题。RAND_MAX + 1.0
被准确执行。在极其常见但不是C指定的情况下,RAND_MAX
是2的幂减1。因此,RAND_MAX/2 + 1
是一个int
和一个精确的2的幂。将该int
转换为double
肯定是精确的。#define random0to_almost1 (rand() / (2.0*(RAND_MAX/2 + 1)))
// This value is platform dependent, but very common
// Do not a a highly portable generation method yet.
#define FLT_POWER2_INTEGER_LIMIT (1ul << 24)
#define random0to_almost1 ( \
(rand() % FLT_POWER2_INTEGER_LIMIT) / \
(RAND_MAX % FLT_POWER2_INTEGER_LIMIT + 1.0f) \
)
FLT_RADIX
不是2的幂,则将2的幂int
值转换为double
可能不精确。 - Ian AbbottFLT_RADIX
为2,3,4,5...
。我还没有遇到过除了2,10,16
之外的FLT_RADIX
-当然未来可能会有所不同。当然,使用2,4,8,16,...
等值并不是以上答案的问题。对于基数为10,使用上述方法仍然有好处,但可能无法达到_精确性_。如果您知道今天使用的任何非基数2系统,我很想听听它们的情况。 - chux - Reinstate Monica
int
平台上,会出现未定义行为。任何事情都可能发生,案子已经结案 - 请下一个! - too honest for this site