【问题标题】:How do I generate random numbers with a mean in C++?如何在 C++ 中生成具有平均值的随机数?
【发布时间】:2015-02-15 22:04:56
【问题描述】:

如何在 C++ 中生成 1,000 到 20,000 之间的 100 个随机数,总平均值为 9,000?我正在研究 C++ 11 库,但没有找到允许我包含平均值和范围的方法。

【问题讨论】:

  • 你想要什么分布?
  • 任何发行版都可以。在这种情况下什么是最好的?
  • 如果你不知道你想要什么,你几乎肯定想要一个“正态分布”,也称为“高斯分布”。
  • 正态分布的平均值为 10500 ((20000-1000)/2 + 1000)。曲线会略有偏差,平均值为 9000。
  • 几乎没有一个用例表明特定的平均值可能很重要,而分布无关紧要。您应该尝试描述您的实际问题,以便我们提供有用的答案。您所说的相当于“我想生成一个带有'1'的数字,但我不在乎这个数字的基础是什么。”

标签: c++ c++11 random


【解决方案1】:

由于您在分发方面很灵活,因此无需执行拒绝逻辑即可提供合理结果的简单解决方案是triangular distribution。 IE。您将三角形的下端设置为 1,000,将三角形的上端设置为 20,000,三角形的尖端设置为您想要的平均值 9,000。

上面的维基百科链接表明三角形分布的平均值是:

(a + b + c) / 3

其中ab 分别是您的下限和上限,c 是三角形的尖端。对于您的输入,简单代数表明 c = 6,000 将给出您想要的平均值 9,000。

C++ 的<random> 标头中有一个名为std::piecewise_linear_distribution 的分布,非常适合设置三角分布。这只需要两条直线。构建这种三角分布的一种简单方法是:

std::piecewise_linear_distribution<> dist({1000., 6000., 20000.},
                                          [](double x)
                                          {
                                              return x == 6000 ? 1. : 0.;
                                          });

现在您只需将 URNG 插入此发行版并生成结果。为了理智起见,根据您的问题陈述收集一些重要的统计数据也很有帮助,例如最小值、最大值和平均值。

这是一个完整的程序:

#include <algorithm>
#include <iostream>
#include <numeric>
#include <random>
#include <vector>

int
main()
{
    std::mt19937_64 eng;
    std::piecewise_linear_distribution<> dist({1000., 6000., 20000.},
                                              [](double x)
                                              {
                                                  return x == 6000 ? 1. : 0.;
                                              });
    std::vector<double> results;
    for (int i = 0; i < 100; ++i)
        results.push_back(dist(eng));
    auto avg = std::accumulate(results.begin(), results.end(), 0.) / results.size();
    auto minmax = std::minmax_element(results.begin(), results.end());
    std::cout << "size = " << results.size() << '\n';
    std::cout << "min = " << *minmax.first << '\n';
    std::cout << "avg = " << avg << '\n';
    std::cout << "max = " << *minmax.second << '\n';
}

这个应该可移植地输出:

size = 100
min = 2353.05
avg = 8972.1
max = 18162.5

如果您将采样值的数量提高到足够高,您将看到参数收敛:

size = 10000000
min = 1003.08
avg = 8998.91
max = 19995.5

根据需要播种。

【讨论】:

    【解决方案2】:

    以下是如何使用正态分布执行此操作,使用样本丢弃技术将值保持在范围内。这当然会扭曲分布,因此它不再正常。这种影响有多大取决于您最初选择的标准差。这对您有多重要取决于您的应用程序。

    #include <iostream>
    #include <random>
    
    double get_random_number_with_minimum_mean_and_maximum(
        double minimum,
        double mean,
        double maximum
    ) {
    
        // Any uniform random generator of your choice could be used here.
        // Obviously making this static is not rentrant or thread-safe.
        static std::mt19937 generator;
    
        // We'll start with a normal distribution with a standard deviation set
        // such that ~99.7% of the time we'll get a number within the average width
        // of your upper and lower bounds.
        //
        // Why ~99.7% and not some other number? Because that corresponds to three
        // standard deviations, and you didn't specify any requirements, so I'm
        // just making assumptions on your behalf.
        double const average_bound_width = ((mean-minimum) + (maximum-mean)) / 2;
        double const standard_deviation  = average_bound_width / 3;
        std::normal_distribution<double> distribution(mean, standard_deviation);
    
        // Now, keep fetching numbers until we find one that is within our desired
        // bounds. Throwing numbers away randomly from a normal distribution does
        // not affect the mean, but since our bounds are going to be skewed, our
        // mean will probably get skewed slightly, but this will likely not be
        // very noticable.
        double value;
        do {
            value = distribution(generator);
        } while (value < minimum || maximum < value);
    
        return value;
    }
    
    int main() {
        for (int i=0; i<100; ++i) {
            std::cout << get_random_number_with_minimum_mean_and_maximum(
                1000,
                9000,
                20000
            ) << '\n';
        }
    }
    

    当我运行它时,我得到这个输出:

    9426.01
    10458.7
    9518.42
    9945.55
    9032.35
    7268.34
    11092.2
    13705.6
    6374.58
    7290.7
    7008.3
    10075.4
    15678
    8089.93
    5645.39
    13607.6
    11930.2
    13799.4
    12194.7
    10390
    8594.2
    14625.4
    10487.9
    11116.9
    9473.06
    13868
    12414.5
    12711.2
    9431.92
    3570.35
    3490.47
    3974.69
    6695.1
    8642.8
    10034.3
    8757.84
    10232.6
    10441.1
    18234.9
    9862.67
    2365.8
    9982.83
    10282
    13492.3
    11932.6
    9399.23
    7196.37
    11793.8
    9646.56
    1232.16
    7796.91
    13297.7
    13191.4
    8340.9
    9891.94
    7998.53
    8139.9
    8813.12
    8829.3
    9408.99
    7771.22
    6957.75
    6149.01
    7139.31
    1482.41
    5893.11
    12720.9
    6009.68
    12360.5
    5557.36
    3080.25
    8922.16
    7636.47
    12109.1
    11153.5
    5434.98
    8874.9
    8599.4
    7833.87
    8525.87
    7630.14
    8595.15
    9786.19
    12644.8
    6310.17
    12696.1
    8717.86
    7199.22
    7404.67
    7410.03
    6041.42
    7930.46
    6505.42
    7772.88
    4929.65
    4686.06
    7743.93
    5211.43
    12023.2
    10380.5
    

    平均值约为 8980,仅 100 个样本就非常接近 9000。

    如果我用 100 万个样本运行,平均值约为 9049。

    【讨论】:

      【解决方案3】:

      由于只要满足您的约束,您就不会关心分布,到目前为止,最简单的方法就是始终简单地生成9000。最简单的分布不是这样的东西,比如产生概率为p1000 和概率为200001-p,你已经解决了p 的值,它给出了正确的平均值。

      我强烈怀疑你应该在开始考虑编程之前弄清楚你正在尝试做什么的数学/统计数据。

      【讨论】:

      • 如果我使用正态分布,我必须提供一个标准差,这会降低平均值,对吗?
      • 我如何选择一个标准差,使生成器可以选择范围内的每个数字,但将平均值保持在 9,000?
      • @user3647894 简短回答:你不能。您必须“切掉”高斯的尾巴,因此您将不再有高斯。否则,高斯分布中的任何值都具有非零发生概率。
      【解决方案4】:

      这是一个"power-shifted" distribution,它是连续的,可以返回范围内的任何值,并且一次性执行。均值是所需的,但众数通常位于最接近均值的两端。

      double shiftable_distribution(double minimum, double mean, double maximum)
      {
          double range = maximum - minimum;
          double scaledmean = (mean - minimum) / range;
          return pow((double)rand() / RAND_MAX, 
                     (1.0 - scaledmean) / scaledmean) 
                      * range + minimum;
      }
      

      分布的实际情况(对于 1000、9000、20000)

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-07-14
        • 2015-04-20
        • 1970-01-01
        相关资源
        最近更新 更多