C语言中的随机数完整变化

3
我正在尝试使用以下代码生成64位随机数。我想要二进制数字,但问题是我不能让所有比特都变化。我希望数字尽可能地变化。
void PrintDoubleAsCBytes(double d, FILE* f)
{

f = fopen("tb.txt","a");

  unsigned char a[sizeof(d)];
  unsigned i;
  memcpy(a, &d, sizeof(d));
  for (i = 0; i < sizeof(a); i++){
    fprintf(f, "%0*X", (CHAR_BIT + 3) / 4, a[sizeof(d)-1-i]);

  }
   fprintf(f,"\n");
 fclose(f); /*done!*/
}

int main (int argc, char *argv)
{

int limit = 100 ;
double a, b;                
double result;
int i ;           
printf("limit = %d", limit );

for (i= 0 ; i< limit;i++)
    {
    a= rand();
    b= rand();
    result = a * b;
    printf ("A= %f B = %f\n",a,b);
    printf ("result= %f\n",result);
    PrintDoubleAsCBytes(a, stdout); puts("");
    PrintDoubleAsCBytes(b, stdout); puts("");
    PrintDoubleAsCBytes(result, stdout); puts("");

    }
}

输出文件

41DAE2D159C00000        //Last bits remain zero, I want them to change as well as in case of the result
41C93D91E3000000
43B534EE7FAEB1C3
41D90F261A400000
41D98CD21CC00000
43C4021C95228080
41DD2C3714400000
41B9495CFF000000
43A70D6CAD0EE321

我应该怎么做才能实现这个?我对软件编程没有太多经验。


4
生成一个随机的二进制浮点数并不是一件简单的事情。大多数流行的明显技术(例如生成[0,2^53)范围内的随机整数,然后除以2^53)都有微妙的错误。正确的算法在这里给出:http://allendowney.com/research/rand/。他的算法只生成半开区间[0,1)内的数字,但您可以安全地将结果缩放到所需的任何区间。 - zwol
@Zack 他想生成一个随机的64位整数,而不是一个随机浮点数。 - Patashu
你想要一个随机浮点数还是只需要64个随机位? - Detheroc
@Patashu,我在代码中看到的是double,而不是uint64_t。也许这是另一个错误。 - zwol
我想要一个64位的随机浮点数,而不仅仅是64个随机位。我将前两个浮点数相加以得到答案,并使用我的硬件进行验证。 - chitranna
显示剩余8条评论
3个回答

3

在Java中非常容易:

Random rng = new Random(); //do this only once

long randLong = rng.NextLong();
double randDoubleFromBits = Double.longBitsToDouble(randLong);

在C语言中,我只知道一种简单的方法来实现它:)


由于RAND_MAX可能低至2^15-1,但其实现是定义的,因此您可以通过使用屏蔽和位移来从rand()获取64个随机位:

//seed program once at the start
srand(time(NULL));

uint64_t a = rand()&0x7FFF;
uint64_t b = rand()&0x7FFF;
uint64_t c = rand()&0x7FFF;
uint64_t d = rand()&0x7FFF;
uint64_t e = rand()&0x7FFF;
uint64_t random = (a<<60)+(b<<45)+(c<<30)+(d<<15)+e;

将它放入一个union中,使用union的另一个成员将其位解释为double。类似于以下的代码:

union
{
    double d;
    long l;
} doubleOrLong;

doubleOrLong.l = random;
double randomDouble = doubleOrLong.d;

(我尚未测试此代码)


编辑:它的工作原理解释

首先,srand(time(NULL));使用当前时间戳对 rand 进行种子。因此,您只需要在开始时执行此操作,如果您想要重新生成早期的 RNG 序列,则可以重复使用该种子。

rand() 返回一个介于 0 和 RAND_MAX(包括)之间的随机、无偏整数。RAND_MAX 至少保证为 2^15-1,即 0x7FFF。为了使编写的程序不受 RAND_MAX 的影响(例如,它可以是 2^16-1、2^31-1、2^32-1...),我们屏蔽除底部 15 位以外的所有内容 - 0x7FFF 在二进制中为 0111 1111 1111 1111,即为底部 15 位。

现在,我们必须将所有 15 个随机位打包到 64 位中。位移运算符 << 将左操作数(右操作数)位向左移动。因此,我们称之为随机的最终 uint64_t 具有从其他变量派生的随机位,如下所示:

aaaa bbbb bbbb bbbb bbbc cccc cccc cccc ccdd dddd dddd dddd deee eeee eeee eeee

但是,它仍然被视为 uint64_t,而不是 double。这是未定义的行为,因此您应该确保它在您选择的编译器上按预期工作,但是如果您将此 uint64_t 放入 union 中,然后读取 union 的其他 double 成员,则您将(希望!)将这些相同的位解释为由随机位组成的 double。


@未定义行为 有更好的方法吗? - Patashu
@Patashu更好。形容词。更优秀或更有效的类型或质量。您需要详细说明。 - autistic
2
@Patashu 如果将uint64_t的位解释为double的位,也会引发未定义的行为。 - autistic
@Alexey Frunze 因为0x8F是底部的15位 - 0111 1111 1111 1111。 - Patashu
1
@undefinedbehaviour:“使用联合体的不同成员是未定义行为。” 不,C99 TC3脚注82明确允许使用联合体进行类型转换。 - Pascal Cuoq
显示剩余14条评论

1

根据您的平台,但假设使用IEEE 754标准,例如维基百科,为什么不明确处理内部双精度格式?

(除非出现错误),这将生成随机但有效的双精度数。 [ 这里并没有完全涵盖所有情况,例如当exp = 00x7ff的情况 ]

double randomDouble()
{
    uint64_t buf = 0ull;

    // sign bit
    bool odd = rand()%2 > 0;
    if (odd)
        buf  = 1ull<<63;

    // exponent

    int exponentLength = 11;

    int exponentMask = (1 << exponentLength) - 1;

    int exponentLocation = 63 - exponentLength;

    uint64_t exponent = rand()&exponentMask;

    buf += exponent << exponentLocation;

    // fraction

    int fractionLength = exponentLocation;
    int fractionMask  = (1 << exponentLocation) - 1;

    // Courtesy of Patashu

    uint64_t a = rand()&0x7FFF;
    uint64_t b = rand()&0x7FFF;
    uint64_t c = rand()&0x7FFF;
    uint64_t d = rand()&0x7FFF;

    uint64_t fraction = (a<<45)+(b<<30)+(c<<15)+d;
    fraction = fraction& fractionMask;
    buf += fraction;

    double* res = reinterpret_cast<double*>(&buf);
    return *res;
}

该问题被标记为C语言,而不是C++。 - Alexey Frunze
@Alexey Frunze,这确实是个困惑的问题,因为在问题的评论中,OP说他可以接受C++或Java。 (不确定这是否意味着它可以在C++或Java中实现,还是说如果他用C++或Java阅读,他会理解你正在做什么...) - Patashu
我只需要生成几百万个测试用例到一个文件中,以运行我的硬件模拟。 - chitranna
你是怎么得出几百万的? - Alexey Frunze

0
你可以使用以下代码:
void GenerateRandomDouble(double* d)
{
  unsigned char* p = (unsigned char*)d;
  unsigned i;
  for (i = 0; i < sizeof(d); i++)
    p[i] = rand();
}

这种方法的问题在于,您的C程序可能无法使用此函数返回的某些值,因为它们是无效或特殊的浮点值。

但是,如果您正在测试硬件,则可以生成随机字节并直接将它们馈送到该硬件中,而无需先将它们转换为double

您唯一需要将这些随机字节视为double的地方是验证由硬件返回的结果的时候。

在那个时候,您需要查看字节并查看它们是否表示有效值。如果是,则可以将字节memcpy()double中并使用它。

下一个要处理的问题是溢出/下溢和由您需要对这些随机doubles执行的任何操作引起的异常。您需要弄清楚如何在您的平台(编译器+ CPU + OS)上处理它们,无论您是否能够安全可靠地检测到它们。

但这似乎是一个单独的问题,可能已经被提出并回答了。


有没有办法我可以将这两个数字连接起来,使它们成为一个更大的数字?并且可以重复使用你提供给我的代码吗? - chitranna
什么两个数字?是什么类型的?你所说的“连接”具体指什么?比什么更大? - Alexey Frunze

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接