【问题标题】:Meaning of ((float) rand() / (float)((1 << 31) - 1))((float) rand() / (float)((1 << 31) - 1)) 的含义
【发布时间】:2016-08-04 15:23:58
【问题描述】:

我正在尝试理解一个包含 .h 文件的 C 程序

#define random                  ((float) rand() / (float)((1 << 31) - 1))

C 程序还包括&lt;math.h&gt;

我的猜测是,这只是从区间 [0,1] 上的均匀分布产生一个随机数;这是正确的吗?

【问题讨论】:

标签: c random


【解决方案1】:

表面上是的。但它在两个主要方面是错误的:

  1. 请改用RAND_MAX。这就是它的用途。它可能比1 &lt;&lt; 31 - 1 小很多。

  2. 1 &lt;&lt; 31 将在具有 32 位 int 或更少的平台上为您提供未定义的行为,这非常常见。不要那样做!

请注意,如果您不想恢复值 1(通常是这种情况),请在分母上使用 RAND_MAX + 1.01.0 强制浮点计算:如果你写 RAND_MAX + 1,你会冒整数类型溢出的风险。

【讨论】:

  • 它也会在多于 16 位但少于 33 位的平台上调用 UB。而RAND_MAX + 1 很可能会在所有平台上调用 UB。
  • 如果你不介意我已经编辑了答案。两者都是非常好的点。谢谢。
  • 但是RAND_MAX + 1.0 不是double 类型吗?
  • 嗯,好点子。我不在我的应用程序中使用浮点数(这在嵌入式系统上大多数时候是一个非常糟糕的主意)。你说的对。如果您忽略潜在的性能问题,您可以使用它。标准的随机函数仍然不能保证算法的任何质量。如果需要真正的均匀分布,则应仔细选择算法,如果确实需要浮点范围,则应直接使用。
【解决方案2】:

rand 函数返回一个介于 0 和 RAND_MAX 之间的值。该程序假设 RAND_MAX 是 2^31 - 1 并将结果除以该数字。

所以是的,如果上述假设成立,那么这个宏会给出一个 [0,1] 中的数字。它与其说是一个均匀的随机分布,不如说是一个“伪随机”值。

至少这是它应该做的。这个表达式(1 &lt;&lt; 31) 调用未定义的行为(假设int 是32 位或更小),因为常量1 的类型为int,并且左移31 使其超出int 的范围。在实践中,如果使用二进制补码表示,一些编译器将允许这种移位,然后随后的-1 会将其放回范围内,但这不能依赖。

可以通过使用(1U &lt;&lt; 31) 来避免这种未定义的行为,这使得常量1 具有unsigned int 类型,以便在范围内移动它。更好的选择是忘记移位和减法,只使用0x7fffffff

但为了最大的可移植性,它应该定义如下:

#define random ((float)rand() / RAND_MAX)

但是还是有问题。 float 通常有一个 23 位尾数。如果rand 返回一个 32 位值,您将无法获得良好的数字分布。最好使用double,它有一个52位尾数:

#define random ((double)rand() / RAND_MAX)

【讨论】:

  • 你没有看到任何 UB?
  • @Olaf 对。添加了详细信息。
  • 这种转变是有问题的——充其量。在少于 32 位的实现中,它仍然调用 UB(移位计数 >= 位宽)。 RAND_MAX 方法更好。但是使用float 很可能会降低精度并将分布限制在通常不超过 23 位(IEEE 的尾数 float)。
【解决方案3】:

我的猜测是,这只是从区间 [0,1] 上的均匀分布产生一个随机数;这是正确的吗?

没有。代码可能永远不会返回 1.0f 或统一的结果。

#define random ((float) rand() / (float)((1 &lt;&lt; 31) - 1)) 有很多问题。这是弱代码。


精度损失:典型的float 具有大约 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] 中获得一个统一的随机数,鉴于@987654329 的可能精度限制,这是不可能的@。


最糟糕的是,(1 &lt;&lt; 31)未定义的行为 UB,除非 int 至少有 33 位长。将 1 移到符号位置是 UB。 C11dr §6.5.7 4.

要避免 UB,请使用 ((1ul &lt;&lt; 31) - 1)

然而,使用幻数 ((1ul &lt;&lt; 31) - 1) 不如基于 RAND_MAX 的分数那么可靠。

进一步(float) ((1ul &lt;&lt; 31) - 1) 可能会遭受如上所述的精度损失,因为它形成了值2147483648.0f,而不是无法获得的2147483647.0f。 OP 的代码可能永远生成1.0f


我怀疑 OP 确实需要 [0..1) 结果而不是 [0..1]。两者都在下面。

// 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_minus_1 的幂。所以RAND_MAX/2 + 1int精确 2 的幂。将int 转换为double 肯定是精确

#define random0to_almost1  (rand() / (2.0*(RAND_MAX/2 + 1)))

float 解决方案是

// 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 Abbott 是的 - 同意。 C 允许FLT_RADIX2,3,4,5... 我还没有遇到FLT_RADIX 除了2,10,16- 当然未来可能会有所不同。当然,2,4,8,16,.. 的值不是上述答案的问题。以 10 为底,使用上述方法仍有好处,但可能达不到精确度。如果您知道当今使用的任何非 base 2 系统,您将最有兴趣了解它们。
猜你喜欢
  • 2016-10-10
  • 1970-01-01
  • 2019-08-25
  • 1970-01-01
  • 2013-05-29
  • 1970-01-01
  • 1970-01-01
  • 2022-12-16
  • 1970-01-01
相关资源
最近更新 更多