C's random numbers come from two functions in <stdlib.h>: rand(), which produces the next value, and srand(), which sets the starting point. They are not truly random - they are a pseudorandom sequence, computed deterministically from a seed - which is a limitation for cryptography and a feature for testing.
rand() and RAND_MAX
rand() returns an int somewhere between 0 and RAND_MAX, inclusive. RAND_MAX is a macro guaranteed to be at least 32767; on Linux and macOS it is 2147483647.
Run that twice. The numbers are identical both times - and that is not a bug.
Seeding with srand
With no call to srand, the sequence behaves as if you had called srand(1). Same seed, same sequence, every run. To get different numbers per run, seed with something that changes - conventionally the current time:
time(NULL) from <time.h> returns the seconds since the start of 1970, so each run gets a different seed. The cast to unsigned int silences a warning about narrowing time_t.
Three rules about seeding, all of which people get wrong:
Seed exactly once, at the start of main. Calling srand before every rand() is the classic anti-pattern - inside a loop that finishes in under a second, time(NULL) returns the same value each iteration, so you reseed with the same number and rand() returns the same first value every time. The output is a column of identical "random" numbers.
Do not reseed to "improve" randomness. The generator's quality comes from its internal state advancing; resetting that state throws the sequence away.
time(NULL) has one-second resolution. Two programs launched in the same second get the same sequence. That is fine for a game and wrong for anything where independence matters.
A Number in a Range
The standard idiom uses the remainder operator:
rand() % n /* 0 to n-1 */
rand() % n + min /* min to min+n-1 */
To get min through max inclusive, the count of possible values is max - min + 1:
The + 1 is where off-by-one errors live. rand() % 6 gives 0 through 5, so a dice roll is rand() % 6 + 1. Writing rand() % 7 + 1 to "include 6" gives you a seven-sided die.
The Honest Note About Modulo Bias
rand() % n is not perfectly uniform unless n divides RAND_MAX + 1 exactly.
Think of it with small numbers. If RAND_MAX were 9 - so rand() returns 0 through 9, ten equally likely values - then rand() % 3 maps 0,3,6,9 to 0; 1,4,7 to 1; and 2,5,8 to 2. Result 0 occurs four ways out of ten, results 1 and 2 three ways each. Zero is 33% more likely.
The same skew exists with the real RAND_MAX, just far smaller: the leftover values are the first (RAND_MAX + 1) % n outcomes, each gaining one extra chance out of roughly 2.1 billion. For a dice roll, a shuffled deck, or a simulation, that is unmeasurable - use % and move on.
When it does matter - statistical work, anything security-related - reject the leftover values rather than folding them in:
The loop throws away the small range of values that would cause the skew and draws again. It terminates quickly - the rejected slice is a vanishing fraction of the whole.
For genuinely security-sensitive randomness, rand() is the wrong tool at any level of care: use arc4random_buf on macOS and BSD, getrandom() on Linux, or BCryptGenRandom on Windows.
Random Doubles
Divide by RAND_MAX to land in [0.0, 1.0], then scale:
The cast on (double) rand() is essential. Without it, rand() / RAND_MAX is integer division and evaluates to 0 almost always, 1 on the one-in-two-billion chance of hitting the maximum - a bug that looks like "my random doubles are all zero". See type casting for why.
Reproducible Sequences
A fixed seed gives the identical sequence every run, which is exactly what you want for a test, a debugging session, or a game with shareable level codes:
Seed 42 produces the same five numbers every time it is used, in this run and in any other on the same library. That reproducibility is why a simulation should let the seed be chosen: run with the clock normally, pass a fixed seed when reproducing a bug.
One caveat: the sequence for a given seed is not portable. Different C libraries use different generators, so seed 42 on glibc and seed 42 on Windows give different numbers. Reproducible on one machine, not across machines.
A Dice Game
Everything together - seeding once, a helper for the range, and an array tallying the results:
The histogram should peak at 7 and taper toward 2 and 12 - there are six ways to make 7 and one way each to make 2 or 12. A generator producing a flat distribution here would be broken.
Two related pages: the standard library maps the rest of <stdlib.h>, and math functions covers <math.h>, which you will want as soon as random values feed into real calculations.
Frequently Asked Questions
How do I generate a random number in C?
Include <stdlib.h>, seed once at the start of main with srand((unsigned) time(NULL)) (which needs <time.h>), then call rand() for each value. rand() returns an int between 0 and RAND_MAX inclusive.
How do I get a random number between two values in C?
Use rand() % (max - min + 1) + min. For a dice roll between 1 and 6 that is rand() % 6 + 1. The % n maps the result into 0..n-1 and adding min shifts the window - just make sure the count includes both endpoints, which is what the + 1 does.
Why does my C program print the same random numbers every time?
Because you never called srand. Without a seed, rand() behaves as if seeded with 1, so every run produces the identical sequence. Call srand((unsigned) time(NULL)) once at program start - once, not before every rand() call, which would make things worse.
What is modulo bias in random number generation?
rand() % n is only perfectly uniform when n divides RAND_MAX + 1 evenly. Otherwise the first few values occur one extra time across the full range, making them very slightly more likely. With RAND_MAX at 2147483647 and a small n the skew is far below anything a game or simulation notices, but for cryptography or statistics use a rejection loop or a proper generator.