【问题标题】:How to generate a n-sized random float array that sums up to 0.0?如何生成总和为 0.0 的 n 大小的随机浮点数组?
【发布时间】:2020-02-13 16:19:18
【问题描述】:

考虑到我需要一个 n 大小的向量,其中每个元素都定义在 [-1,1] 之间。元素 a[i] 是由 -1 + 2*rand() 生成的浮点数。我需要一种优雅的方式来确保我的数组元素之和为零。

我找到了两种可能的解决方案:

第一个是这个matlab函数https://www.mathworks.com/matlabcentral/fileexchange/9700-random-vectors-with-fixed-sum。它在 R 中也有一个实现,但是在 C 上实现它的工作量太大,因为这个函数用于 2d 数组。

第二个在此线程中提供:Generate random values with fixed sum in C++。本质上,这个想法是生成 n 个具有正态分布的数字,然后用我的总和将它们归一化。 (我已经使用 python bellow 实现了它)对于总和为 1.0 的向量。它适用于除零以外的所有总和值。

import random as rd

mySum = 1;
randomVector = []
randomSum = 0

for i in range(7):
    randomNumber = -1 + 2*rd.random()
    randomVector.append(randomNumber)
    randomSum  += randomNumber

coef = mySum/randomSum
myNewList = [j * coef for j in randomVector]
newsum = sum(myNewList)

那么,有没有办法使用 C 或 C++ 来做到这一点?如果你知道一个已经实现的功能,那就太棒了。谢谢。

【问题讨论】:

  • 是的,你可以用任何一种语言来做。但是,请以tour 开头并阅读How to Ask
  • 首先你需要意识到它不是随机的。然后你需要决定你在哪里“牺牲”你的随机性。例如,您可以生成 n/2 个随机元素,然后生成另一个 n/2 个元素,而每个元素都是随机数组中相应项目的否定。
  • 多个实例的标准化。如果您想要一组介于 -1 和 1 之间的和为零的随机数,则将负数相加并将它们归一化为总计 -1,然后将正数相加并将它们归一化为 +1。它牺牲了真正的随机性,但它们仍然保持伪随机性,并且向量的总和为零。从您的代码中,您需要确保至少有 1 个负数和 1 个正数 - 否则会引发 div0 错误。
  • @Cubo78 可能超出范围
  • n=3 本质上是正六边形时,可能答案的空间是你在用平面x+y+z=0 将顶点坐标为正或负一的立方体切片时得到的。那么您希望该六边形上的分布如何?与面积成正比?与投影到某个区域的原始立方体的体积成正比?

标签: c++ c arrays random


【解决方案1】:

我找到了解决您问题的方法。这并不完美,因为它的随机性受到范围要求的限制。

策略是:

  1. 定义一个能够在可自定义范围内生成随机浮点数的函数。无需重新发明轮子:我从https://stackoverflow.com/a/44105089/11336762借来的
  2. Malloc 数组(我在示例中省略了指针检查)并初始化种子。在我的示例中,我只使用了当前时间,但可以改进
  3. 对于要生成的每个元素,预先计算随机范围。给定第 i 个总和,确保下一个总和永远不会超出范围:如果总和为正,则范围需要为 (-1,1-sum);如果它是负数,则范围需要是 (-1-sum,1)
  4. 直到第 (n-1) 个元素为止。最后一个元素必须直接赋值为改变符号的总和。
    #include<stdio.h>
    #include<stdlib.h>
    #include<time.h>

    float float_rand( float min, float max )
    {
        float scale = rand() / (float) RAND_MAX; /* [0, 1.0] */
        return min + scale * ( max - min );      /* [min, max] */
    }

    void main( int argc, char *argv[] )
    {
        if( argc == 2 )
        {
            int i, n = atoi ( argv[1] );
            float *outArr = malloc( n * sizeof( float ) );
            float sum = 0;

            printf( "Input value: %d\n\n", n );

            /* Initialize seed */
            srand ( time( NULL ) );

            for( i=0; i<n-1; i++ )
            {
                /* Limit random generation range in order to make sure the next sum is  *
                 * not outside (-1,1) range.                                            */
                float min = (sum<0? -1-sum : -1);
                float max = (sum>0? 1-sum : 1);

                outArr[i] = float_rand( min, max );
                sum += outArr[i];
            }

            /* Set last array element */
            outArr[n-1] = -sum;

            /* Print results */
            sum=0;
            for( i=0; i<n; i++ )
            {
                sum += outArr[i];
                printf( "  outArr[%d]=%f \t(sum=%f)\n", i, outArr[i], sum );
            }

            free( outArr );
        }  
        else
        {
          printf( "Only a parameter allowed (integer N)\n" );
        }
    }

我试过了,当 n=1 时也有效。在 n=0 的情况下,应在我的示例中添加完整性检查。

一些输出示例:

N=1:

Input value: 1

  outArr[0]=-0.000000   (sum=-0.000000)

N=4

Input value: 4

  outArr[0]=-0.804071   (sum=-0.804071)
  outArr[1]=0.810685    (sum=0.006614)
  outArr[2]=-0.353444   (sum=-0.346830)
  outArr[3]=0.346830    (sum=0.000000)

N=8:

Input value: 8

  outArr[0]=-0.791314   (sum=-0.791314)
  outArr[1]=0.800182    (sum=0.008867)
  outArr[2]=-0.571293   (sum=-0.562426)
  outArr[3]=0.293300    (sum=-0.269126)
  outArr[4]=-0.082886   (sum=-0.352012)
  outArr[5]=0.818639    (sum=0.466628)
  outArr[6]=-0.301473   (sum=0.165155)
  outArr[7]=-0.165155   (sum=0.000000)

【讨论】:

  • 注意outArr[i]的分布与outArr[j]的分布不同。
  • 它甚至没有覆盖空间。使用n==4,您无法在0.8, 0.8, -0.8, -0.8 附近获得结果。
  • 我知道。但我认为这主要是由于种子,在我的测试中,这让我得到了一个离负极限不远的值(这导致了不自然的负正交替)。改进种子初始化肯定也会改善 outArr[i] 与 outArr[j] 的分布。但是..嘿!它符合要求! :)
  • 我不是数学书呆子;但是我感到不安,因为您对 min,max 的调整是基于历史的,因此您已将 memory 添加到随机数序列中。随机在线性运算上是封闭的,但基于内存的运算实际上是线性的吗?
【解决方案2】:

再次感谢大家的帮助。

所以,基于Cryostasys的思路,我开发了以下C代码来解决我的问题:

#include <stdio.h>      /* printf, scanf, puts, NULL */
#include <stdlib.h>     /* srand, rand */
#include <time.h>       /* time */
#include <math.h>

int main()
{
    int arraySize = 10; //input value 
    double createdArray[arraySize]; //output value

    double randomPositiveVector[arraySize];
    double randomNegativeVector[arraySize];
    double positiveSum = 0.;
    double negativeSum = 0.;

    srand(time(NULL)); //seed for random generation

    for(int i = 0; i < arraySize; ++i)
    {
        double randomNumber = -1.+2.*rand()/((double) RAND_MAX); //random in [-1.0,1.0]
        printf("%f\n",randomNumber);
        if(randomNumber >=0)
        {
            randomPositiveVector[i] = randomNumber;
            positiveSum += randomNumber;
        }
        else
        {
            randomNegativeVector[i] = randomNumber;
            negativeSum += randomNumber;
        }
    }
    if(positiveSum == 0. || negativeSum == 0.) printf("ERROR\n");

    double positiveCoefficient =  1.0/positiveSum;
    double negativeCoefficient = -1.0/negativeSum;
    for(int i = 0; i < arraySize; ++i)
    {
        randomPositiveVector[i] = positiveCoefficient * randomPositiveVector[i];
        randomNegativeVector[i] = negativeCoefficient * randomNegativeVector[i];
        if(fabs(randomPositiveVector[i]) > 1e-6) //near to zero 
        {
            createdArray[i] = randomPositiveVector[i];
        }
        else
        {
            createdArray[i] = randomNegativeVector[i];
        }
    }

    for(int i = 0; i < arraySize; ++i)
    {
        printf("createdArray[%d] = %9f\n",i,createdArray[i]);

    }

    return(0);
}

请注意,生成的值的随机性会降低,如问题的 cmets 中所述。此外,随机分布的类型由您用于生成上述 randomNumber 的函数确定。在这种情况下,我使用了stdlib.h 中的rand(),它基于给函数一个种子,它将生成一个伪随机数。您也可以使用不同的选项,例如,drand48()stdlib.h

但是,为了使该代码工作,需要至少生成一个正值和一个负值。代码中添加了一个验证步骤,如果达到此条件,则应再次运行代码或做一些事情。

输出示例(arraySize = 10):

createdArray[0] = -0.013824
createdArray[1] =  0.359639
createdArray[2] = -0.005851
createdArray[3] =  0.126829
createdArray[4] = -0.334745
createdArray[5] = -0.473096
createdArray[6] = -0.172484
createdArray[7] =  0.249523
createdArray[8] =  0.262370
createdArray[9] =  0.001640

【讨论】:

  • 您的解决方案似乎生成了压缩范围内的值(接近 0)。与我的解决方案相比,它实际上是相反的,我认为由于随机生成器总是选择接近下限的第一个值,因此在两个限制附近振荡。
【解决方案3】:

一种选择是生成一些样本,然后围绕平均值缩放它们的值。在 C++ 中,它将类似于以下内容

#include <iostream>
#include <iomanip>
#include <random>
#include <algorithm>
#include <cmath>

int main()
{
    std::random_device rd;
    std::seed_seq ss{rd(), rd(), rd(), rd()};
    std::mt19937 gen{ss};

    const int samples = 9;

    // Generates the samples in [0, 2]
    std::uniform_real_distribution dist(0.0, std::nextafter(2.0, 3.0));
    std::vector<double> nums(samples);
    double sum = 0.0;
    for ( auto & i : nums )
    {
        i = dist(gen);
        sum += i;
    }
    double average = sum / samples;
    double k = 1.0 / std::max(average, 2.0 - average);

    // Transform the values (apart from the last) to meet the requirements
    sum = 0.0;
    for ( size_t i = 0; i < nums.size() - 1; ++i )
    {
        nums[i] = (nums[i] - average) * k;
        sum += nums[i];
    };
    // This trick (to ensure the needed precision) only works if the sum
    // is always evaluated in the same order
    nums.back() = 0.0 - sum;

    sum = 0.0;
    for ( size_t i = 0; i < nums.size(); ++i )
    {
        sum += nums[i];
        std::cout << std::setw(10) << std::fixed << nums[i] << '\n';
    }
    if (sum != 0.0)
        std::cout << "Failed.\n";
}

可测试here

【讨论】:

    猜你喜欢
    • 2020-04-09
    • 1970-01-01
    • 1970-01-01
    • 2023-03-08
    • 2012-11-25
    • 2023-01-03
    • 1970-01-01
    • 1970-01-01
    • 2010-10-15
    相关资源
    最近更新 更多