使用rand()在(a,b),[a,b),(a,b]和[a,b]上生成均匀分布的浮点数

时间:2012-09-07 18:03:33

标签: c random

我想收集在一个地方生成所有四种类型间隔的随机数的“最佳”方式。我厌倦了谷歌搜索这个。搜索结果出现了很多垃圾。甚至相关的结果都是页面或博客,这些页面或博客通常都是错误的,或者讨论过自我指定的专家在某些技术性方面彼此不同意,通常他们的“答案”似乎暴露出他们不了解不同的类型(关闭)间隔开放,半开放。我厌倦了阅读关于在C中为这样一个“简单”问题生成随机数的不良信息。

请告诉我如何生成均匀分布的浮点数。这是我在(a,b),[a,b),(a,b)和[a,b]上的典型方式(使用“long double”作为例子):

long double a=VALUE1,b=VALUE2;
long double x1,x2,x3,x4;

srand((unsigned)time(NULL));

/* x1 will be an element of [a,b] */
x1=((long double)rand()/RAND_MAX)*(b-a) + a;

/* x2 will be an element of [a,b) */
x2=((long double)rand()/((long double)RAND_MAX+1))*(b-a) + a;

/* x3 will be an element of (a,b] */
x3=(((long double)rand()+1)/((long double)RAND_MAX+1))*(b-a) + a;

/* x4 will be an element of (a,b) */    
x4=(((long double)rand()+1)/((long double)RAND_MAX+2))*(b-a) + a;

对于单位区间(0,1),[0,1),(0,1]和[0,1]的特殊情况:

long double x1,x2,x3,x4;

srand((unsigned)time(NULL));

/* x1 will be an element of [0,1] */
x1=((long double)rand()/RAND_MAX);

/* x2 will be an element of [0,1) */
x2=((long double)rand()/((long double)RAND_MAX+1));

/* x3 will be an element of (0,1] */
x3=(((long double)rand()+1)/((long double)RAND_MAX+1));

/* x4 will be an element of (0,1) */    
x4=(((long double)rand()+1)/((long double)RAND_MAX+2));

我认为RAND_MAX和rand()的返回值都是必要的,不仅因为我们想要避免整数除法,而且因为它们是整数,否则添加一个(或两个)可能会溢出它们。

我认为“double”和“float”的版本完全相同,只是替换了类型。对于不同的浮点类型,是否存在任何微妙之处?

您认为上述实施有任何问题吗?如果是这样,你会怎样以及如何解决它?

编辑:上面的实现通过了必要的测试,以确保它们是正确的(至少在运行64位Linux的64位Intel Core 2 Duo机器上):x1可以生成0和1,x2可以生成0但是没有没有看到生成1,x3可以生成1,但是没有看到生成0,并且还没有看到x4生成0或1。

5 个答案:

答案 0 :(得分:6)

如果你想让范围内的每一个双重成为可能,概率与它与其相邻的双重值之间的差异成正比,那么它实际上非常难。

考虑范围[0, 1000]。在该范围的非常微小的第一部分中存在绝对的桶值:在01000000*DBL_MIN之间有一百万个,而DBL_MIN约为2 * 10 -308 。该范围内的值超过2^32,所以显然只有一次调用rand()并不足以生成所有值。您需要做的是均匀地生成双精度的尾数,并选择具有指数分布的指数,然后稍微捏一下以确保结果在范围内。

如果要求范围内的每一个双重都是可能的,那么开放和闭合范围之间的差异是相当无关紧要的,因为在“真实的”连续均匀随机分布中,概率无论如何,任何确切值的出现是0。所以你不妨在开放范围内生成一个数字。

所有这些都说:是的,您提出的实现会生成您所说的范围内的值,而对于封闭范围和半封闭范围,它们会生成概率为1/(RAND_MAX+1)左右的端点。这对于许多或大多数实际目的来说已经足够了。

如果RAND_MAX+2double可以准确表示的范围内,则可以使用+1和+2作品。 IEEE双精度和32位int都是如此,但C标准实际上并不能保证这一点。

(我忽略了你对long double的使用,因为它使事情有点混乱。它保证至少与double一样大,但是有一些常见的实现与它完全相同double,因此long除了不确定性之外不会添加任何内容。

答案 1 :(得分:3)

此问题尚未准备好回答,因为问题未完全指定。特别是,没有说明应该如何精细地分配可以生成的值集合的规范。为了便于说明,请考虑为[0,1]生成值,并考虑具有可表示值的浮点格式:

0,1 / 16,2 / 16,3 / 16,4 / 16,6 / 16,8 / 16,12 / 16,1。

对这些值的几个分布可能被认为是“统一的”:

  • 以相同的概率选择每一个。这在离散值上是均匀的,但在值之间的实际距离上没有统一的密度。
  • 选择每个可能的概率与其附近可表示值的密度成比例。
  • 以相同的概率选择0,4 / 16,8 / 16,12 / 16和1,以在整个时间间隔内保持相同的粒度。

我怀疑这些中的第一个是有意的,我会驳回它。第二个类似于Steve Jessop的建议,但仍未完全明确。是否应该选择0,其概率与从中间点到下一个点的间隔成比例? (这将给出1/32的概率。)或者它应该与以它为中心的区间相关联,从-1/32到1/32? (这会给它一个概率为1/17,假设1也被分配了超出自身1/32的区间。)

你可能会认为这是一个封闭的时间间隔,所以它应该在0和1处停止。但是假设我们在某些应用中将[0,2]上的分布切换到区间[0,1]和(1,2)。我们希望后两个区间的分布联合等于前一区间的分布。所以我们的分布应该很好地融合。

第三种情况也存在类似问题。也许,如果我们希望保留像这样的粒度,则应选择0,概率为1/8,三个点为1 / 4,1 / 2和3/4,概率为1/4,1,概率为1/8

除了指定生成器所需属性的这些问题之外,提问者提出的代码还存在一些问题:

  • 假设RAND_MAX + 1是2的幂(因此在二进制浮点运算中除以它是“好的”),除以RAND_MAX或RAND_MAX + 2可能会导致生成的值出现一些不规则。其中可能存在奇数量化。

  • 当1 /(RAND_MAX + 1)≤1/ 4 ULP(1)时,RAND_MAX /(RAND_MAX + 1)将向上舍入并在不应该返回1时因为间隔为[0,1]。 (“ULP(1)”表示正在使用的浮点格式中值1的最小精度单位。)(在具有long double的测试中不会观察到这一点,其中RAND_MAX适合有效数字的位,但是例如,它将发生在RAND_MAX为2147483647且浮点类型为float且具有24位有效位数的情况下。)

  • 乘以(b-a)并添加a会引入舍入错误,必须评估其后果。有许多情况,例如当b-a很小且a很大时,ab跨越零(因此导致b附近的粒度损失)更精细的结果可以表示,等等。

  • (0,1)的结果的下限是最接近1 /(RAND_MAX + 2)的浮点值。该界限与浮点值的精细度或期望的分布无关;它只是rand实现的工件。省略(0,1 /(RAND_MAX + 2))中的值,而不会产生问题规范的任何原因。上端可能存在类似的工件(取决于特定的浮点格式,rand实现和间隔端点,b)。

我提交提问者遇到不满意答案的原因这个“简单”的问题是,这不是一个简单的问题。

答案 2 :(得分:2)

首先,在[a,b]上生成随机数。要在[a,b)上生成随机数,只需在[a,b]上生成一个随机数,检查它是否等于b,如果是,则再试一次。类似地,对于所有其他开放区间变体。

答案 3 :(得分:1)

在我的脑海中,我只提供不同浮点和整数类型的所有变体(模板化C ++实现的奖励点),并用更好的东西替换rand()({ {1}}浮现在脑海中)

答案 4 :(得分:0)

以下是我用于查找正在生成的数字中的基本错误的(非常粗略的)测试。它并不是要显示生成的数字是好的,但它们并不坏。

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

int main(int argc, char *argv[]) {

    long double x1,x2,x3,x4;
    if ( argc!=2 ) {
        printf("USAGE: %s [1,2,3,4]\n",argv[0]);
        exit(EXIT_SUCCESS);
    }

    srand((unsigned int)time(NULL));

    printf("This program simply generates random numbers in the chosen interval\n"
               "and looks for values on the boundary or outside it. When an\n"
               "allowable boundary is found, it reports it. Unexpected \"impossible\"\n"
               "values will be reported and the program will terminte. Under\n"
               "normal circumstances, the program should not terminate. Use ctrl-c.\n\n");

    switch ( atoi(argv[1]) ) {
        case 1:
            /* x1 will be an element of [0,1] */
            printf("NOTE: Testing [0,1].\n");
            while ( 1 ) {
                x1=((long double)rand()/RAND_MAX);
                if ( x1==0 ) {
                    printf("x1=0 ENCOUNTERED.\n");
                } else if ( x1==1 ) {
                    printf("x1=1 ENCOUNTERED.\n");
                } else if ( x1 < 0 ) {
                    printf("x1<0 ENCOUNTERED. Abnormal termination.\n");
                    exit(EXIT_FAILURE);
                } else if ( x1 > 1 ) {
                    printf("x1>0 ENCOUNTERED. Abnormal termination.\n");
                    exit(EXIT_FAILURE);
                }
            }
            break;
        case 2:
            /* x2 will be an element of [0,1) */
            printf("NOTE: Testing [0,1).\n");
            while ( 1 ) {
                x2=((long double)rand()/((long double)RAND_MAX+1));
                if ( x2==0 ) {
                    printf("x2=0 ENCOUNTERED.\n");
                } else if ( x2==1 ) {
                    printf("x2=1 ENCOUNTERED. Abnormal termination.\n");
                    exit(EXIT_FAILURE);
                } else if ( x2 < 0 ) {
                    printf("x2<0 ENCOUNTERED. Abnormal termination.\n");
                    exit(EXIT_FAILURE);
                } else if ( x2 > 1 ) {
                    printf("x2>0 ENCOUNTERED. Abnormal termination.\n");
                    exit(EXIT_FAILURE);
                }
            }
            break;
        case 3:
            /* x3 will be an element of (0,1] */
            printf("NOTE: Testing (0,1].\n");
            while ( 1 ) {
                x3=(((long double)rand()+1)/((long double)RAND_MAX+1));
                if ( x3==1 ) {
                    printf("x3=1 ENCOUNTERED.\n");
                } else if ( x3==0 ) {
                    printf("x3=0 ENCOUNTERED. Abnormal termination.\n");
                    exit(EXIT_FAILURE);
                } else if ( x3 < 0 ) {
                    printf("x3<0 ENCOUNTERED. Abnormal termination.\n");
                    exit(EXIT_FAILURE);
                } else if ( x3 > 1 ) {
                    printf("x3>0 ENCOUNTERED. Abnormal termination.\n");
                    exit(EXIT_FAILURE);
                }
            }
            break;
        case 4:
            /* x4 will be an element of (0,1) */
            printf("NOTE: Testing (0,1).\n");
            while ( 1 ) {
                x4=(((long double)rand()+1)/((long double)RAND_MAX+2));
                if ( x4==0 ) {
                    printf("x4=0 ENCOUNTERED. Abnormal termination.\n");
                    exit(EXIT_FAILURE);
                } else if ( x4==1 ) {
                    printf("x4=1 ENCOUNTERED. Abnormal termination.\n");
                    exit(EXIT_FAILURE);
                } else if ( x4 < 0 ) {
                    printf("x4<0 ENCOUNTERED. Abnormal termination.\n");
                    exit(EXIT_FAILURE);
                } else if ( x4 > 1 ) {
                    printf("x4>0 ENCOUNTERED. Abnormal termination.\n");
                    exit(EXIT_FAILURE);
                }
            }
            break;
        default:
            printf("ERROR: invalid argument. Enter 1, 2, 3, or 4 for [0,1], [0,1), (0,1], and (0,1), respectively.\n");
            exit(EXIT_FAILURE);
    }

    exit(EXIT_SUCCESS);
}