【问题标题】:Generate a random double between -1 and 1生成介于 -1 和 1 之间的随机双精度
【发布时间】:2016-01-08 14:48:53
【问题描述】:

我已经为此工作了一段时间,遇到了很多麻烦。我想生成一个从 -1 到 1 的随机值进行计算。我不能使用 % 运算符,因为它仅用于整数。我也尝试过使用fmod(),但我在这里也遇到了困难。

我尝试使用的是...

double random_value;
random_value = fmod((double) rand(),2) + (-1);

这似乎是不正确的。我也尝试用时间播种 srand,但我认为我在那里做错了什么,因为它一直抛出这个错误:

"error: expected declaration specifiers or '...' before time"

代码:

srand((unsigned) time(&t));

任何有关这些问题的帮助将不胜感激。

【问题讨论】:

  • rand() 仍然返回一个整数,然后您将其转换为加倍。这意味着可能的解决方案是 -1,0 和 1,不可能有其他双精度。看c-random-float-number-generation
  • 目前提供的答案(包括我的)不会涵盖double 中可用的全部精度范围。您需要多少精度?
  • 我编辑了我的答案以反映精度限制。如果您想要“更好”的东西,请提供有关您要求的更多信息。

标签: c


【解决方案1】:

您可以像这样随时间播种(在所有调用 rand 之前一次):

#include <time.h>

// ...
srand (time ( NULL));

使用此功能,您可以根据需要设置最小值/最大值。

#include <stdio.h>
#include <stdlib.h>

/* generate a random floating point number from min to max */
double randfrom(double min, double max) 
{
    double range = (max - min); 
    double div = RAND_MAX / range;
    return min + (rand() / div);
}

来源:[SOLVED] Random double generator problem (C Programming)Ubuntu Forums

那么你可以这样称呼它:

double myRand = randfrom(-1.0, 1.0);

但是请注意,这很可能不会涵盖double 提供的全部精度范围。甚至不考虑指数,IEEE-754 double 包含 52 位有效数字(即非指数部分)。由于rand0RAND_MAX 之间返回int,所以RAND_MAX 的最大可能值为INT_MAX。在许多(大多数?)平台上,int 是 32 位的,所以 INT_MAX0x7fffffff,覆盖 31 位范围。

【讨论】:

  • 您需要#include &lt;time.h&gt; 才能使用time(NULL)
  • 注意:RAND_MAX == 32767VS
【解决方案2】:

这将为随机数生成器提供种子,并给出 -1.0 到 1.0 范围内的双精度数

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main()
{
    double random_value;

    srand ( time ( NULL));

    random_value = (double)rand()/RAND_MAX*2.0-1.0;//float in range -1 to 1

    printf ( "%f\n", random_value);

    return 0;
}

【讨论】:

  • 注意:这将最多返回 RAND_MAX+1 不同的值,即使在 -1 到 1 的范围内可能还有 很多double 值。
  • @chux,确实,我的回答(不是这个)解决了这个问题。
【解决方案3】:

我认为创建真正随机双精度的最佳方法是使用它的结构。 Here's 一篇关于如何存储浮点数的文章。如您所见,float 介于 1 和 -1 之间的唯一限制条件是指数值不超过 128。

Ieee754SingleDigits2Double 将 0 和 1 的字符串转换为浮点变量并返回。我是从this 问题的答案中得到的。

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

double Ieee754SingleDigits2Double(const char s[32])
{
    double f;
    int sign, exp;
    unsigned int mant;
    int i;

    sign = s[0] - '0';

    exp = 0;
    for (i = 1; i <= 8; i++)
        exp = exp * 2 + (s[i] - '0');

    exp -= 127;

    if (exp > -127)
    {
        mant = 1; // The implicit "1."
        exp -= 23;
    }
    else
    {
        mant = 0;
        exp = -126;
        exp -= 23;
    }

    for (i = 9; i <= 31; i++)
        mant = mant * 2 + (s[i] - '0');

    f = mant;

    while (exp > 0)
        f *= 2, exp--;

    while (exp < 0)
        f /= 2, exp++;

    if (sign)
        f = -f;

    return f;
}

主要功能如下:

int main(void)
{
    srand ( time ( NULL));
    int i;
    char s[33];
    for(i = 0; i < 32; i++)
    {
        if(i == 1)
            continue;
        s[i] = rand() % 2 + '0';
    }
    s[1] = '0';
    s[32] = 0;
    printf("%s\n", s);
    printf("%+g\n", Ieee754SingleDigits2Double(s));

    return 0;
}

【讨论】:

    【解决方案4】:

    这样做可能不是一个好主意,但仅仅因为它有效,这是一种在 -1 和 1 之间生成随机双精度的方法,包括使用 /dev/urandomcos()

    #include <stdio.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <math.h>
    
    int main()
    {
      int fd;
      double x;
    
      fd = open("/dev/urandom", O_RDONLY);
      if (fd == -1)
        return (1);
      read(fd, &x, sizeof(x));
      close(fd);
      x = cos(x);
      printf("%f\n", x);
      return (0);
    }
    

    【讨论】:

    • 乐于创造性地使用余弦。
    • 每千次左右调用,您将获得一个 NaN。 (不是因为 cos,虽然我相信 cos(infinity) 也是 NaN,但你很少会得到无穷大。)与 cos 相关的是这里的 rng 不会均匀分布。
    【解决方案5】:

    与其他答案类似,您可能需要进行一些改进以使您的代码更加安全和连贯:

    #include <stdlib.h> /* srand and rand */
    #include <unistd.h> /* getpid */
    #include <time.h> /* time */
    #include <errno.h> /* errno */
    #include <math.h> /* NAN  */
    
    /* generate a float random number in a range */
    float randmm(float min, float max)
    {
         static int first = -1;
         if((first = (first<0)))
             srand(time(NULL)+getpid());
         if(min>=max)
             return errno=EDOM, NAN;
    
         return min + (float)rand() / ((float)RAND_MAX / (max - min));
    }
    

    浏览我们拥有的代码:

    • 一个静态变量first 将保证您不会忘记为伪随机数生成器 (PRNG) 播种。逻辑简单而优雅:在第一次调用中,first 为 -1,然后将其与小于零进行比较,从而将其更新为 true(值 1)。第二个调用询问first(现在为 1)是否小于零,即 false(值 0),因此不调用 srand()。他们说,第三个是魅力,所以现在问first,即 0,它是否小于零,这对于本次和下一次迭代来说一直是错误的。
    • 接下来,您可能需要保证 min-max 不为零,否则您将得到一个令人讨厌的除零(或 NAN)。为此,我们将明确导致正确的错误。使用errno.h 设置错误并使用math.h 使NAN(不是数字)宏可用。不建议比较两个浮点数的相等性(如if(min==max)),因此在最小值更大的情况下尝试反转最小值/最大值并不是一个好主意,并在它们相等的情况下使用第三个选项。只需简化您的if,只有两个选项:正确或错误。
    • 最后,我更喜欢使用float 而不是double,因为我不太相信这个函数可以生成什么。一个 32 位整数(即RAND_MAX)只能做这么多。对于所有位,填充float 是合理的。 float 只有 23 位数字,加上 8 位指数。如果你使用double,你会误导你并且对这个功能的能力过于自信。如果您需要真正的双精度,请考虑使用/dev/urand 或其他适当的真正的随机数生成器 (TRNG)。
    • 最后一行,return,只是一个简单的等式。我想你可以很容易地弄清楚这一点。我只是想显式转换为float,这样除了编译器的解释之外,我还可以看到代码的意图。
    • 当然,要按照 OP 的要求使用,只需调用 float x = randmm(-1.0, 1.0);

    【讨论】:

    • 我不喜欢这种方法。将种子移动到生成器函数中会引入(小)运行时成本,并使测试更加困难(不可重复)。您表达first 检查的方式是不必要的神秘,IMO(最好使用bool,并且不要将作业和检查结合在一行上)。 OP 没有指定平台; getpid() 不可移植。关于防止除以 0 的好方法,但我不喜欢在 errno 中添加错误,尤其是在用户代码中。
    • 运行时成本为 picayune。但对于更健全的测试批评者,可以轻松添加if(DEBUG) 条件来使用。关于“critic”,我想是的,是的。实际上,这是答案的精彩部分。是的,如果 OP 需要,getpid() 可能需要便携式类似设备。但这个想法就在那里。感谢您的零分 cumpliment。而且,errno 可以使用。总而言之,感谢您的评论。我希望这个答案至少能激发你的创造力。我最好的。
    • 添加 if(DEBUG) 进行测试会改变行为,这会引入您是否真正测试或测试效果如何的问题。神秘代码(您的 first 实现)会损害可维护性。我不认为我们的其他意见分歧值得追求。感谢您按预期接受我的建设性批评。
    【解决方案6】:

    这个答案主要适用于在 x86_64 机器上寻找随机双打的人。 作为一个长期的 C 用户(自 1980 年代后期以来),我不再关心当天的 RAND_MAX 值是多少。

    另外,srand(time(NULL) 向我表明,这些数字是由一些(至少对我而言)质量未知的准随机数生成器生成的。所有这一切,虽然您距离现代 x86_64 机器上的 CPU 随机数只有 1 条汇编指令。

    因此,下面的代码通过内部函数使用rdrand,已知它是一个完整的 64 位随机数作为随机源。这样,至少,您有足够的位来生成双精度数而无需多言。如果 - 相反 - 您选择了 C 库 rand() 并且它返回 32 位值,那么您可能没有足够的位来容纳 64 位浮点数。并且在 Ansi C 中没有 randl(), randul() 或类似名称,afaik。

    但是 - 如果您查看 _rdrand_step() 内在函数的签名,则该指令似乎在某些条件下可能会失败。 (有人说,负载相关)。因此,在下面的代码中,围绕内部调用编写一个 while() 循环或类似的东西可能(也可能不是)是个好主意。

    #include <stdio.h>
    #include <stdint.h>
    #include <immintrin.h>
    #include <float.h>
    
    int randomf64(double minVal, double maxVal, double* out) {
        if (NULL == out)
            return 0;
        uint64_t result = 0ULL;
        // cast in next line works for amd64 (x86_64) on linux at least.
        int rc = _rdrand64_step((unsigned long long*)&result);
        if(rc) {
            double unscaled = (double)result/(double)UINT64_MAX;
            *out = minVal + (maxVal - minVal) * unscaled;
            return 1;
        }
        return 0;
    }
    
    int main(int argc, const char* argv[]) {
        size_t nvals = 1;
        if(argc > 1) {
            nvals = atol(argv[1]);
        }
        // We want to see if all that "can fail under stress" thing happens...
        double *values = malloc(nvals * sizeof(double));
        if (NULL != values) {
            for(size_t i = 0; i < nvals; ++i ) {
                if(!randomf64(-100.0,100.0, &values[i])) {
                    printf("boom! after %lu random numbers generated.\n",
                            i);
                    free(values);
                    exit(-1);
                }
            }
            for(size_t i = 0; i < nvals; ++i) {
                int Digs = DECIMAL_DIG;
                printf("%lu %.*e\n", i, Digs, values[i]);
            }
            free(values);
        }
        return 0;
    }
    

    如果你提供一个整数作为命令行参数,它会生成一个相应的 随机双精度数并将它们存储在堆分配的数组中。 这允许测试是否可能发生“零星失败”。我尝试了几次,一次最多创建了 1E6 个值,但从未失败(在一些便宜的 AMD CPU 上)。

    为了编译这个,例如用clang,我用过:

    clang -mrdrnd -O3 -std=c17 -o r64i r64intrin.c

    请注意,您必须使用 -mrdrnd 启用内部函数才能使编译器满意。

    【讨论】:

      【解决方案7】:

      经过大量搜索并从周围获得提示后,我创建了此函数以生成特定范围内的随机双数。

      #include <stdio.h>
      #include <stdlib.h>
      #include <time.h>
      #include <math.h>
      
      double random(double min, double max) 
      {
          //used it to generate new number each time
          srand( (unsigned int) time(NULL) );
      
          double randomNumber, range , tempRan, finalRan;
          //generate random number form 0.0 to 1.0
          randomNumber = (double)rand() / (double)RAND_MAX;
          //total range number from min to max eg. from -2 to 2 is 4
          //range used it to pivot form -2 to 2 -> 0 to 4 for next step
          range = max - min
          //illustrate randomNumber to range
          //lets say that rand() generate 0.5 number, thats it the half 
          //of 0.0 to 1.0, show multiple range with randomNumber we get the
          //half in range. eg 4 * 0.5 = 2
          tempRan = randomNumber * range;
          //add the min to tempRan to get the correct random in ours range
          //so in ours example we have: 2 + (-2) = 0, thats the half in -2 to 2
          finalRan = tempRan + min;
          return finalRan;
      }
      

      这说明了我们范围内随机数的百分比。

      【讨论】:

      • 这个函数根本不会产生随机数,因为它每次都会调用srand()
      • srand() 使用其参数种子作为新的伪随机数序列的种子,这些伪随机数将由后续调用 rand() 返回。有些人发现使用 time() 函数的返回值作为 srand() 的参数很方便,可以确保随机数的随机序列。 from ibm doc
      • 1) time() 每秒只更改一次值。此函数将每秒返回相同的值。 2)如果你每次调用 srand() ,你每次都会开始一个新的序列。如果您只使用每个序列的第一个数字,您知道数字仍然是均匀分布的吗?例如,rand() 可以将种子作为第一个值返回,在这种情况下,返回值会稳步增加。
      【解决方案8】:
      random_value = (double)rand() * rand() / (RAND_MAX * RAND_MAX) * 2 - 1;
      

      【讨论】:

        【解决方案9】:

        有一种简单的方法可以获取 [-1.0; 范围内的随机值; 1.0] 三角函数 sine 接受 rand() 返回的数字并返回该范围内的值。

        #include <stdio.h>
        #include <stdlib.h>
        #include <time.h>
        #include <math.h>
        
        /* macro returning value in range [-1.0; 1.0] */
        #define double_rand() ( sin(rand()) )
        
        int main(void) {
            int i;
        
            srand(time(NULL));
        
            /* 20 tests to show result */
            for ( i = 0; i < 20; ++i )
                printf("%f\n", double_rand());
        
            return 0;
        }
        

        在 linux 系统上不要忘记链接数学库
        $ gcc -Wall sin_rand.c -lm
        $ ./a.out
        0.014475
        -0.751095
        -0.650722
        0.995111
        -0.923760
        ...

        【讨论】:

        • 你能edit你的问题并解释一下代码吗?仅代码的答案往往被认为是低质量的,因为人们无法从中学习并最终从互联网上复制和粘贴一些他们不理解的代码。见How to Answer
        • 通常当有人说他们想要某个范围内的随机数时,没有指定概率分布,他们的意思是平坦分布,即在该范围内输出任何特定数字的概率相等。使用正弦使其不平坦,如图所示:stats.stackexchange.com/questions/126273/…
        猜你喜欢
        • 1970-01-01
        • 2012-11-03
        • 1970-01-01
        • 1970-01-01
        • 2021-01-06
        • 1970-01-01
        • 1970-01-01
        • 2021-01-01
        • 1970-01-01
        相关资源
        最近更新 更多