【问题标题】:Random doubles distributed between -DBL_MAX and DBL_MAX随机双打分布在 -DBL_MAX 和 DBL_MAX 之间
【发布时间】:2017-06-29 01:14:20
【问题描述】:

我正在尝试为所有可表示的有限值生成概率大致相等的随机双精度数。我相信这类似于随机签名的exponential_distributionuniform_distribution 将不起作用,因为可表示的双精度数不是均匀分布的。我有这个 hacky 代码,似乎可以做我想做的事:

#include <cmath>
#include <iostream>
#include <random>

template < class GEN >
double double_distribution (GEN& generator)
{
    using gen_type = typename GEN::result_type;
    static_assert (sizeof (gen_type) == sizeof (double), "");
    union {gen_type g; double f;} result;
    do {
        result.g = generator();
    } while (!std::isfinite (result.f));
    return result.f;
}

int main() {
    std::mt19937_64 mt ((std::random_device())());
    for (int n = 0; n < 10; ++n) {
        std::cout << double_distribution (mt) << ' ';
    }
    std::cout << '\n';
}

但我想知道是否有一种更简单的方法可以使用 STL 现有的 RandomNumberDistribution 类之一来近似此值?

【问题讨论】:

  • 你不能从[-1, 1] 得到一个统一的范围,然后乘以你的最大值吗?
  • @NathanOliver:也许,所有可能的双打都可以这样生成吗?直觉上,似乎将范围限制为[-1,1] 会限制熵,乘以常数不会增加熵,但也许我遗漏了一些东西。
  • @atb -- 你的直觉是对的。 [-1,1] 中的可表示值比 [-DBL_MAX, DBL_MAX] 中的少。对于存在性证明,只需考虑[-1,1] 中的所有值也在[-DBL_ MAX, DBL_MAX] 中,而 2.0 在后者而不是前者。更不用说更多的价值了......
  • 您还需要过滤掉 NaN。
  • 我面前没有,但是如果我没记错的话,在浮点值的IEEE表示中,所有的有限值,非NaN值都用连续的位值表示.也就是说,如果您将 RNG 限制在 64 位值的适当子范围内,您将只能获得有限的非 NaN 值。

标签: c++ c++11 stl


【解决方案1】:

如果您假设是 64 位 IEEE double,这里有一个方法。 (对于双精度的不同表示,您应该能够修改此基本方法。)

64 位分为 1 位符号、11 位指数和 52 位小数。 NaN 和 inf 等特殊值由指数位的特殊值表示。您有两个基本选择:

  1. 从所有可能的 64 位组合的集合中生成一个随机数(如适当大小的整数类型上的统一),并从中设置双精度位。在这种情况下,您有时会绘制一个对应于例如 NaN 的数字。您需要丢弃这些并重新绘制或接受它们,具体取决于您是否希望返回 NaN。 (听起来你可能不会。)

  2. 在一次绘制中生成符号和小数部分的位,然后分别绘制指数。如果你这样做,你应该能够修改你的平局,这样你就不会拉出 NaN 值 - 特殊值似乎对应于指数的极值,所以这只是改变你的平局范围整数,我认为许多软件包很容易支持它。在这种情况下,您不必“重新绘制”以避免特殊值,但您可能会在每次绘制时得到一些“额外”位,因为您最终可能会从整数类型的分布中提取(位数为 8 的倍数),但符号+分数使用 53,指数仅使用 11。您可能可以做一些聪明的事情来节省这些比特,以便在下一次抽奖中使用一次抽奖中“丢弃”的比特。您必须仔细考虑以确保这不会在您的平局之间引入依赖关系,但我认为应该没问题。

如果效率对您来说不是什么大问题,那么#1 看起来更容易实现。如果您需要效率,那么#2 可能会更好,尽管可能会以更复杂的代码为代价。

【讨论】:

  • 我认为(1)基本上是我已经在做什么了?我认为 (2) 可能还需要丢弃,因为 NaN 和 Inf 被编码在 IEEE 指数和尾数位中,我希望它们会在可以被屏蔽但它们不是的单独位中。
  • 我承认我在遵循您的代码时遇到了麻烦,所以我的#1 可能与您正在做的事情相对应。我认为您对#2的看法是错误的。特殊值是“有符号零”、inf 和 NaN。有符号零的指数为 0x0。 inf 和 NaN 都有指数 0x7FF,这是最大的指数(所有 11 位都等于 1)。如果你画一个大于零且小于 0x7FF 的整数,你应该很好。 en.wikipedia.org/wiki/Double-precision_floating-point_format
  • 对于#1,我是按照位操作而不是投射。在尝试之后,我发现这并不像我想象的那么容易。如果您想操作双精度位,这是一个相关的问题。启发式地,我认为你会通过random_device rd 绘制unsigned u,然后设置double d = 0 | u。但这并不完全正确,因为 u 和 d 具有不同的位数。为了让它变得更好,我想对 double 进行位操作,这是不允许的。这导致了相关的问题(等等)。 stackoverflow.com/questions/37136667/changing-one-bit-in-double
【解决方案2】:

你要找的是std::uniform_real_distribution

请注意,这将 - 至少在 Visual Studio 上 - 不会适用于从 numeric_limits&lt;double&gt;::lowest()numeric_limits&lt;double&gt;::max() 的整个范围,因为计算涉及范围上下限之间的距离,这显然比double 所允许的要大。

无论如何,我建议您重新考虑您的方法,因为它实际上可能不是您想要的 - 所有 double 值之间的均匀分布几乎完全会生成具有极大幅度的值(几乎所有你会得到的数字都将是某事-某事*10^300)。

无论如何,如果你真的想使用均匀分布,这里有一个例子:

#include <random>
#include <limits>
#include <iostream>
#include <cmath>

int main()
{
    std::random_device rd;
    std::mt19937 gen(rd());
    // sqrt so distance between min and max fits into a double
    auto max = std::sqrt(std::numeric_limits<double>::max());
    auto min = -max;
    std::cout << "min: " << min << " max: " << max << std::endl;
    std::uniform_real_distribution<double> dis(min, max);
    for (int n = 0; n < 100; ++n) {
        std::cout << dis(gen) << '\n';
    }
    std::cout << std::endl;
}

【讨论】:

  • 我不认为我想要一个均匀分布,因为可表示的双精度在实数线上不是均匀分布的,我认为我想要的更接近指数分布。
  • @atb 这不是这里的主要问题;在均匀分布的情况下,99.999% 的数字将在最大数字的 5 个数量级之内 - 所以基本上,你几乎永远不会得到小于 +-10^300 的值(数量级)。但是,是的,我想指数或正态分布可能更符合您的要求。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-10-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-07-10
相关资源
最近更新 更多