Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RAND_MAX macro: signed or unsigned?

Tags:

c

I've looked up the C standard (from 1999) and it only says that RAND_MAX should be at least 32767 but says nothing about whether this macro should expand to a signed or an unsigned int. The Single UNIX Specification (link 1, link 2) and Linux man (link) don't add any clarity.

One would think RAND_MAX should be a signed int since that's what rand() returns.

However, I've found that some compilers define it as unsigned:

  • The ancient Turbo C++ 1.01: #define RAND_MAX 0x7FFFU
  • The not so ancient C++ Builder 5.5: #define RAND_MAX 0x7FFFU
  • The still alive Open Watcom C/C++ 1.9: #define RAND_MAX 32767U
  • DJGPP (gcc 3.3.4 for DOS): #define RAND_MAX 2147483647
  • MinGW (gcc 4.6.2 for Windows): #define RAND_MAX 0x7FFF
  • MS Visual Studio 2010 (link): RAND_MAX is defined as the value 0x7fff
  • Tiny C Compiler 0.9.25: #define RAND_MAX 0x7FFF
  • lcc-win32 3.8: #define RAND_MAX 0x7fff
  • Pelles C 6.50: #define RAND_MAX 0x3fffffff OR #define RAND_MAX 0x7fff
  • Digital Mars C/C++ 8.52: #define RAND_MAX 32767

This makes seemingly innocuous code like the below become non-portable and blow up due to signed to unsigned promotion:

cos(w * t) + (rand() - RAND_MAX / 2) * 0.1 / (RAND_MAX / 2);

rand() returns a signed int in the range [0,RAND_MAX].

If RAND_MAX is defined as an unsigned int, the value from rand() gets promoted to unsigned int too.

And if that's the case, the difference (rand() - RAND_MAX / 2) becomes an unsigned difference of unsigned integers with the value in the ranges [0,RAND_MAX-RAND_MAX/2] & [UINT_MAX+1-RAND_MAX/2,UINT_MAX-1] instead of being a signed difference of signed integers with the value in the range [-RAND_MAX/2,RAND_MAX-RAND_MAX/2].

Anyhow, it seems like RAND_MAX should be signed and most(?) compilers define it as such, but is there any authoritative source that says it should be signed? Older standard? K&R? Another UNIX spec?

like image 569
Alexey Frunze Avatar asked May 22 '12 19:05

Alexey Frunze


2 Answers

Yes this looks like a defect in the standard.

First, nowadays probably nobody would define rand() to return an int. The intention is clearly to return a positive number and there is no error return that could use the negative returns. Such function would if it were introduced today be designed with an unsigned integer type as return type. My guess is that it predates C89 and the introduction of concept of unsigned integers to the language.

Then, clearly one has to expect that a definition of the maximum value that a function returns has the same type as the one of the function. At other places macros are well defined as expanding to expressions with a certain type so this would have been possible here, too.

As to your problem, I think the easiest to have something portable is to scale the value first to a double in [0, 1) by doing something like rand()/(RAND_MAX+1.0) and to derive all your computations from there. In any case you should use rand() only as a last resort if your platform has no better pseudo-random generator available. E.g on POSIX systems the rand48 family is a convenient replacement with well described properties.

like image 134
Jens Gustedt Avatar answered Sep 24 '22 02:09

Jens Gustedt


The answer is: that code is making an unwarranted assumption, so it needs to be fixed. What that code should have done is cast RAND_MAX to (int) on usage.

While it would make a bit more sense for compilers to define their RAND_MAX macros as signed, the standard carefully avoids requiring them to do so. That makes it a portability bug for any code to blindly assume it to be signed.

like image 22
HBB Avatar answered Sep 27 '22 02:09

HBB