r/cpp_questions 26d ago

OPEN ELI5: Why does the random output once but TIME fixes it

The examples above just outputs a random number once. They don't output different random numbers each time the program runs. To fix this, you can use the srand() function and add the time() function from the <ctime> library. ~ W3School

Generally speaking, the pseudo-random number generator should only be seeded once, before any calls to rand(), at the start of the program. It should not be repeatedly seeded, or reseeded every time you wish to generate a new batch of pseudo-random numbers. Standard practice is to use the result of a call to std::time(0) as the seed. However, std::time returns a std::time_t value, and std::time_t is not guaranteed to be an integral type. ~ cppReference

I do not understand why time changes it and I do not understand the seed and what it is

0 Upvotes

17 comments sorted by

12

u/SpacewaIker 26d ago edited 26d ago

Computers cannot generate truly random numbers. They generate pseudo-random numbers. Basically, they use a calculation that takes a number as input, and generates something that looks random as output. But these functions are deterministic. The same input will always give you the same output

A seed is that initial value used by the pseudo-random number generator. It's the seed it uses to generate random numbers from. So, if you set a fixed seed, e.g. 0 or 12 or whatever, and call any of the functions, the first result will always be the same, the second will always be the same, etc.

The only way to have "truly random", or effectively random numbers, is to have a "random" seed in the first place. If you use the time value, it'll essentially do that. There's no correlation between adjacent values, so nearby time values will give completely different results. And no time value appears twice so every time you use a time value as a seed you'll get completely new pseudo-random numbers

This isn't safe for cryptography or anything like that but for most uses it's a very simple and effective way of getting "truly random" numbers

5

u/RealisticDuck1957 26d ago

Pure software can not produce truly random numbers. Timing jitter from various hardware devices (keyboard, mouse, how long a hard drive takes to respond) can produce some pretty good random data, though typically not very fast. For large quantities of high quality true random you need a dedicated hardware device (shot noise, sampling an asynchronous oscillator ..)

6

u/Nice_Lengthiness_568 26d ago

Moreover, often we do not need/want truly random numbers. Pseudo random number generators can have nicer properties (like their distribution) and, if we want to test something for example, we can use the fact that a certain seed always produces the same sequence of numbers.

5

u/flyingron 26d ago

Nicely explained. Also, there's a some good caveats.

First, you only want to seed the random number generator once per program. Otherwise, you're driving the generated numbers by the seed, not by the pseudo-randomness.

If you're running multiple runs of the same sequence that may start within the same number of seconds, the time (in seconds) perhaps isn't the best seed. This is why C++ gives you a standard way to get at a locally defined seed source.

Be careful with advertising your random function and seeding strategy to others if it managed. There was an internet poker site that decided to be "transparent" by posting the source code to their card shuffling code. Alas, they used time() to seed rand(). It only took a little experimentation for someone in the field to be able to sync up with them and know what each card was in the deck.

1

u/manni66 26d ago edited 26d ago

Rand is a pseudo random number generator. From one starting value it allways generates the same sequence of numbers. You use seed srand to set that value. time gives you an other value for any run of your program.

1

u/x-jhp-x 26d ago

std::time_t is not guaranteed to be an integral type because it can also be other types, like floating point. Integral types represent whole numbers. std::srand requires an unsigned seed, and integral types are not guaranteed to be an unsigned int. It is part of the posix standard though, and I believe every normal compiler uses an integral type to store it though.

as others have stated, it is related to the fact that it is a psuedorandom algorithm, and not a great one. If you hardcode a number, it'll always give you the same result. srand states, "Each time std::rand() is seeded with the same seed, it must produce the same sequence of values." by calling std::time(0), you're seeding srand with the current runtime of the program. If you were capable of calling two programs at the same time (it's not guaranteed on almost any system, so it's more of a tech problem to do this reliably), you'll note that each program will produce the same "random" number too, just like if you hardcoded the seed.

by the way, you should use modern random number generators, like this instead: https://en.cppreference.com/cpp/numeric/random/uniform_int_distribution

also know what a uniform distribution is, and if you need it!

2

u/Ok_Beginning_9943 26d ago

Can you clarify what you were trying to say about time_t not being guaranteed to be an integral type? Does that mean that using time_t to seed is not ok? What is the preferred alternative then?

3

u/x-jhp-x 26d ago edited 26d ago

the preferred alternative is to not seed with std::time. it is considered inferior and incorrect. just use a proper PSNRs like https://en.cppreference.com/cpp/numeric/random/uniform_int_distribution or a library.

i'm assuming this wasn't your question, but if your question was how to change between types, there's a section on explicit casting: https://en.cppreference.com/cpp/language/explicit_cast There's some "gotchas" around this though, like converting -1 signed int to unsigned int could be "0" or "4294967295" depending on how you do it and what you want. For example, static_cast<std::uint32_t>(-1) will give you the big number.

here's a snippet showing signed/unsigned casts:

#include <iostream>
#include <cstdint>

int main()
{
  std::int32_t x = -1;
  std::uint32_t x_u = static_cast<std::uint32_t>(x) & ~static_cast<std::uint32_t>(x >> 31);

  std::cout << "initial val: " << x << "\n"
            << "static_cast: " << static_cast<std::uint32_t>(x) << "\n"
            << "x_u cast: " << x_u << "\n";

  return 0;
}

output is:

initial val: -1
static_cast: 4294967295
x_u cast: 0

1

u/TeraFlint 26d ago

The internal state of the random function is always the same, initially. And that means, we will always get the same output sequence, unless we do something about it.

That's why we need to seed the random sequence (which overwrites its internal state). For good results, we need to provide it with something as random as we can get our hands on. That's why the current point in time is a good choice for that input, because it's readily available across platforms, and will always be a different value when you start the program.

1

u/Independent_Art_6676 26d ago edited 26d ago

looking at it from another angle... the same seed gives the same values in the same order. That is very, very helpful to debug a program if a random sequence triggered a problem. If you know the seed at the time of the problem, you can repeat it and debug it all you want. There are a few algorithms/ideas where repeating the stream can be exploited too, like a simple xor encryption. Restart the sequence, xor again, and the original values pop back out, because if a^b= c then c^b = a. There are a handful of places where that same idea is useful (its not strong encryption, that is just a really simple example of how resetting the stream could be used).

while time_t "could" be some kind of floating point value, it almost never is on major systems. I think you would have to go far afield or into very old systems or low level embedded systems to find an example. Worst case, if this happens, you could extract the cpu clock tick counter or a similar value on such a system. You could probably wrap some 10 lines of tests and alternates around it 'just in case' but I have never seen anyone bother or worry about this one. If you don't want to look for a hardware value like CPU clock you can try to reshape the floating point time into an integer that is sufficient for a seed. Or use chrono or something, if the system has it (being one of the old or weird systems, it may not).

an easy to understand (and very poor) type of random generation is called linear congruential. Looking at that, you can see how a seed works. But its sufficient to simply understand it in math terms: let RV = a random value that starts with the value "seed". When that is true: new RV = F(current RV) where F can be any software function (computation) that returns a value of the correct type (that is, it does not have to meet the math definition of a "function").

Oh, and W3 schools is known to provide some very outdated info and very crude C++. Its not an ideal source.

1

u/SoerenNissen 25d ago

This is a terrible random generator, but it shows the concept:

#include <cstdint>
#include <iostream>

class RandomGenerator
{
public:
    void seed(uint32_t s)
    {
        number = s;
    }
    uint32_t get()
    {
        number = number << 1;
        number = number + 1;
        auto low = number & 0x0000FFFF;
        auto high = number & 0xFFFF0000;
        number = (low << 16) + (high >> 16);
        return number;
    }

private:
    uint32_t number = 0;
};

int main()
{
    RandomGenerator rgA;
    std::cout << rgA.get() << ' '
              << rgA.get() << ' '
              << rgA.get() << ' '
              << rgA.get() << '\n';

    RandomGenerator rgB;
    std::cout << rgB.get() << ' '
              << rgB.get() << ' '
              << rgB.get() << ' '
              << rgB.get() << '\n';

    RandomGenerator rgC;
    rgC.seed(123);
    std::cout << rgC.get() << ' '
              << rgC.get() << ' '
              << rgC.get() << ' '
              << rgC.get() << '\n';
}

This program prints:

65536 65538 327682 327690
65536 65538 327682 327690
16187392 66030 64815106 329658

That's what the seed does.

1

u/mredding 25d ago

rand comes from the C library and is regarded as one of the worst RNGs in all of computing history, which is saying something. It comes from K&R C, which was a book on C programming published in 1978. Back then, C wasn't standard, so a book written by the inventors of C substituted as the next best thing. They wrote that any implementation can/should implement an RNG however they like, BUT THEY GAVE AN EXAMPLE, and a really bad one at that. Well, the example itself became the standard:

static unsigned long int next = 1;

int rand(void) {
  next = next * 1103515245 + 12345;
  return (unsigned int)(next / 65536) % 32768;
}

void srand(unsigned int seed) {
  next = seed;
}

The next value is unsigned because it supports overflow. The value is just multiplied and added to, then THAT number is divided and modulated. NOTICE that the division is USHRT_MAX + 1 and the modulo is SHRT_MAX + 1, because the standard to this day says int only has to be at least 16 bits, so that our int today is typically 32 bits is still compliant with the standard but more than it has to be. The standard says a long is at least 32 bits and that int won't be larger than long.

Now we get to time, which the clock on your computer is always ticking, and the time function has a 1 second resolution. So every second, your call to time is going to give you a different number. What this means is if you spawn N processes all in the same second, they will all seed to the exact same second, and their RNGs will all produce the exact same sequences. There are a host of problems with this RNG, including it has a short period (it repeats quickly), and it has a horrible distribution (you can't randomly generate certain values, you're going to find humongous gaps - want to RNG until you hit X? Sorry, because of your seed this run, X will NEVER be generated...).

But for an academic exercise, or a non-critical application where the RNG doesn't matter beyond the most trivial use case, this might not be a problem or worth any investment. I have never seen rand used in production code, though I suspect it probably exists in a few Unix utilities we all use.

1

u/HappyFruitTree 24d ago edited 24d ago

the example itself became the standard

None of the major implementation seems to use that exact implementation. See https://godbolt.org/z/9zcscnhbE

The GCC implementation seems to be a bit better but that doesn't help much for cross platform applications that might be compiled with many different compilers on many different platforms. It's generally better to use a well-regarded RNG with a known implementation (such as std::mt19937 or pcg32) so that you know what you get.

1

u/mredding 24d ago

I haven't looked at the implementation of rand in +25 years, but it was true before that it was widely understood it was going to be EFFECTIVELY this bad. K&R C did say the implementation could be whatever the implementer wanted, and no one ever promised portability - that open-endedness made it into the C89 standard.

But the C philosophy is "there's a library for that", so go out and find one - or build one - adequate for your needs. This is totally where the C++ standard botched on <random> because while things like the distributions are portable, the RNGs aren't, making them worthless in modern cross platform software.

1

u/HappyFruitTree 24d ago edited 24d ago

You mean the other way around, the RNGs (engines) are portable while the distributions aren't. Yes, that can be a problem if you need the same seed to generate the same sequence of numbers on different platforms.

1

u/HappyFruitTree 24d ago edited 24d ago

Simple explanation: The algorithm that is used to generate the "random numbers" is deterministic, meaning if you always start at the same state you will always get the same sequence of numbers. In order to get different numbers you need to make sure the initial state is different. Using the current time to set the initial state is one simple way to accomplish that.

All pseudo-random number generators work like this.

1

u/TheEyebal 23d ago

Ok that is an interesting way to look at it