【问题标题】:Using bitwise & instead of modulus operator to randomly sample integers from a range使用按位 & 而不是取模运算符从一个范围内随机采样整数
【发布时间】:2013-09-11 13:39:51
【问题描述】:

我需要在 C++ 中的区间 [LB,UB] 上从整数的均匀分布中随机抽样。为此,我从一个“好”的 RN 生成器(来自 Numerical Recipes 3rd ed.)开始,它均匀地随机采样 64 位整数;我们就叫它int64()吧。

使用 mod 运算符,我可以通过以下方式从[LB,UB] 中的整数中采样:

LB+int64()%(UB-LB+1);

使用 mod 运算符的唯一问题是整数除法的速度很慢。于是,我又尝试了here建议的方法,即:

LB + (int64()&(UB-LB))

按位 & 方法的速度大约是 3 倍。这对我来说意义重大,因为我在 C++ 中的一个模拟需要随机采样大约 2000 万个整数。

但是有 1 个大问题。当我分析使用按位 & 方法采样的整数时,它们在区间 [LB,UB] 上看起来并不均匀分布。整数确实是从[LB,UB] 中采样的,但来自该范围内的偶数。例如,这是使用按位 & 方法从 [20,50] 采样的 5000 个整数的直方图:

相比之下,使用 mod 运算符方法时类似的直方图看起来像这样,当然效果很好:

我的按位 & 方法有什么问题?有没有办法修改它,以便在定义的间隔内对偶数和奇数进行采样?

【问题讨论】:

  • 如果您不必做对,那么快速很容易。 <g> 掩码给出有效分布的唯一情况是目标范围的大小是 2 的幂。
  • 您是否考虑过在 (0,1) 范围内使用生成器,将其称为 u0_1(),然后进行乘法运算:LB+(int)(u0_1()*(UB-LB+1)?无论如何,基于模的方法通常会产生不一致的结果,应该避免。
  • @pjs 是的,这就是我今后将使用的。感谢您的建议。
  • @pjs 乘法应该是LB+(int)(u0_1()*(UB-LB+1)) 还是LB+((int)u0_1())*(UB-LB+1)
  • @synaptik 你想要前者,后者总是会将 u0_1() 结果变为零,从而产生 LB。对不起,我的评论中缺少括号。

标签: c++ random bit-manipulation modulo integer-division


【解决方案1】:

只有当区间的大小是 2 的幂时,两者才等价。一般来说 y%x 和 y&(x-1) 是不一样的。

例如,x%5 产生从 0 到 4 的数字(或到 -4,对于负 x),但 x&4 产生 0 或 4,从不产生 1、2 或 3,因为按位运算符的工作方式。 .

【讨论】:

  • 有什么办法可以避免在这里使用 mod 操作符,让采样更快?
  • 是的,但如果值不是 2n-1,您将“丢失位”。
  • 尝试使用 52 和 20 而不是 50 和 20 进行测试。
  • @MatsPetersson 你是什么意思?你能解释一下替代方案吗? (抱歉,我是按位运算的新手。)
  • 由于& 操作仅对两侧存在的位产生“1”,因此右侧的“1”越少,可以获得的不同值就越少。所以对于范围的任意选择,你不能使用这个技巧。对于% 的改进,我想不出任何明显的建议。如果x 是“右侧”中的一位数,则可能值的数量为 2^x。所以对于 7 (0111) 你得到 2^3=8 个不同的值。对于 4 (0100),您会得到 2^1 = 2 个不同的值。
【解决方案2】:

如果范围差 (UB-LB) 为 2n-1,则此方法效果很好,但如果例如为 2n,则效果不佳。

【讨论】:

    【解决方案3】:

    按位& 运算符查看其操作数的每一对对应位,仅使用这两个位执行and,并将结果放入结果的对应位中。

    所以,如果UB-LB的最后一位是0,那么结果的最后一位就是0。也就是说,如果UB-LB是偶数,那么每个输出都是偶数。

    & 不适合目的,除非UB-LB+1 是 2 的幂。如果你想找到一个模数,那么没有通用的捷径:编译器已经以它知道的最快方式实现 % .

    请注意,我说没有通用快捷方式。对于在编译时已知的UB-LB 的特定值,可以有更快的方法。如果你能以某种方式安排UBLB 具有编译器可以在编译时计算的值,那么它会在你编写% 时使用它们。

    顺便说一句,使用% 实际上并不会在范围内产生均匀分布的整数,除非范围的大小是 2 的幂。否则肯定会有轻微的偏向于某些值,因为int64() 函数的范围不能在所需范围内平均分配。可能是偏差太小而无法特别影响您的模拟,但糟糕的随机数生成器过去破坏了随机模拟,并且会再次这样做。

    如果您想要在任意范围内均匀分布随机数,请使用 C++11 中的 std::uniform_int_distribution,或 Boost 中的同名类。

    【讨论】:

    • 是的,我一直在考虑安排LBUB有一定的关系,然后使用采样的方法,但丢弃超出真实期望范围的采样变量.
    • @synaptik:好吧,您可能会很幸运,这取决于您的运行时 UB-LB 与您使用的特殊值的接近程度。但一般来说,重采样可能比除法慢。
    • “但是糟糕的随机数生成器在过去破坏了随机模拟”你是 100% 正确的。这就是我使用 NR3 方法的原因之一。它已经过充分验证。
    • @synaptik:但是你用有偏见的% 转换破坏了它。将正确分布的随机数据转换为不正确分布的数据非常容易。请注意,如果您不喜欢std 提供的默认随机生成器,您可以使用uniform_int_distribution 并仍然传递一个生成器对象,该生成器对象使用来自NR3 的声音算法。
    • 是的,这是真的。 (我将他们的 RN 生成器用于其他目的来采样均匀和正态变量,在这些其他情况下,采样分布的统计有效性更为重要。)但重点。
    猜你喜欢
    • 2020-10-05
    • 2012-08-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多