1

This is a homework question, but it's a small part of a much bigger project. One of the constraints is that we are not allowed to use STL for any reason.

I've attempted to roll up my own rand() function using ctime and an incrementing modifier. I figured that even though this doesn't have a consistent seed, the function should output semi-random numbers so long as it's not fed the same modifier more than once per second.

//notcstdlib.cpp
//<ctime> <cmath>
int rand(int mod)
{
    time_t seed;
    return std::abs(seed * mod);
}

but this sample code

//main.cpp
#include "notcstdlib.h"
#include <iostream>

int main(int argc, char** argv)
{
    int f;
    for(int i = 1; i <= 10; i++)
    {
        f = rand(i);
        std::cout << "random num= " << f << "\n";
        std::cout << "rand % 10 = " << f%10 << "\n";
    }
    return 0;
}

Always returns 7 as the first value and only even numbers between 0 and 8 for every other number.

//Output 1              //Output 2              //Output 3
random num= 134514987 | random num= 134514987 | random num= 134514987
rand % 10 = 7         | rand % 10 = 7         | rand % 10 = 7
random num= 13261304  | random num= 24238584  | random num= 27941368
rand % 10 = 4         | rand % 10 = 4         | rand % 10 = 8
random num= 19891956  | random num= 36357876  | random num= 41912052
rand % 10 = 6         | rand % 10 = 6         | rand % 10 = 2
random num= 26522608  | random num= 48477168  | random num= 55882736
rand % 10 = 8         | rand % 10 = 8         | rand % 10 = 6
random num= 33153260  | random num= 60596460  | random num= 69853420
rand % 10 = 0         | rand % 10 = 0         | rand % 10 = 0
random num= 39783912  | random num= 72715752  | random num= 83824104
rand % 10 = 2         | rand % 10 = 2         | rand % 10 = 4
random num= 46414564  | random num= 84835044  | random num= 97794788
rand % 10 = 4         | rand % 10 = 4         | rand % 10 = 8
random num= 53045216  | random num= 96954336  | random num= 111765472
rand % 10 = 6         | rand % 10 = 6         | rand % 10 = 2
random num= 59675868  | random num= 109073628 | random num= 125736156
rand % 10 = 8         | rand % 10 = 8         | rand % 10 = 6
random num= 66306520  | random num= 121192920 | random num= 139706840
rand % 10 = 0         | rand % 10 = 0         | rand % 10 = 0

Obviously I'm missing some important aspect of rand() and I'm not implementing it. Is there a better way to tackle this issue?

anthony
  • 233
  • 3
  • 11
  • Is the assignment about writing your own pseudo-random number generator? – Nikos C. Nov 09 '17 at 00:00
  • Take a look here: https://en.wikipedia.org/wiki/Linear-feedback_shift_register – DimChtz Nov 09 '17 at 00:02
  • See https://stackoverflow.com/questions/3062746/special-simple-random-number-generator – Nikos C. Nov 09 '17 at 00:06
  • @Nikos C - No, the assignment is about simulating the spread and impact of a disease. Random numbers are needed to decide how each individual reacts. – anthony Nov 09 '17 at 00:07
  • They why are you trying to implement your own random number generator? – Nikos C. Nov 09 '17 at 00:11
  • With `STL` your professor probably (wrongly) referred to the subset of containers on the standard library that are derived from the original Standard Template Library and not to the standard library as a whole. `rand()` should be clear. Otherwise use of `std::abs()` should also be banned. – xvan Nov 09 '17 at 00:13
  • @xvan - That would certainly speed things up if you are correct. If it helps at all, for the class's first assignment part of the problem involved finding the greatest common divisor between two numbers. Half of the class got points docked for using a pre-built gcd() function. Now that I go back and look for it again, I can't find any mention of gcd() belonging to the STL. Verbatim, the instructions just say "Must not use STL, with the exception of Strings" – anthony Nov 09 '17 at 00:42

2 Answers2

2

You never initialize your seed, try

//notcstdlib.cpp
<ctime>
int rand(int mod)
{
    static time_t seed = time(NULL);
    return std::abs(seed * mod);
}
xvan
  • 4,554
  • 1
  • 22
  • 37
2

You should probably ask your teacher whether std::rand() is also excluded and whether you really need to implement your own pseudo-random number generator. Or better, ask if you're allowed to use <random> so that you can use the Mercene Twister engine of C++, which is a very good pseudo-random number generator.

If you really need to roll your own, the simplest replacement for std::rand() is an LCG (Linear Congruential Generator):

#define MY_RAND_MAX = 2147483647
static unsigned long my_rand_state = 1;

void my_srand(unsigned long seed)
{
    my_rand_state = seed;
}

long my_rand()
{
    my_rand_state = (my_rand_state * 1103515245 + 12345) % 2147483648;
    return my_rand_state;
}

You can then use my_srand(), my_rand() and MY_RAND_MAX like you would std::srand(), std::rand() and RAND_MAX respectively:

// Seed it with the current time.
my_srand(std::time(nullptr));

// Print 1000 random numbers between 0 and MY_RAND_MAX.
for (int i = 0; i < 1000; ++i) {
    std::cout << my_rand() << ' ';
}
std::cout << '\n';

This generates low quality random numbers (they have bad distribution.) Note that std::rand() also has bad distribution. If you would like high quality random numbers (meaning good distribution), you should be using the C++ <random> algorithms, using the std::mt19937 engine (which is the Mercene Twister algorithm, which has very good distribution and a huge period.)

In general, just avoid rand(). See Stephan T. Lavavej's 30min talk "rand() Considered Harmful", a must-watch for anyone who still uses rand() in their code.

Nikos C.
  • 50,738
  • 9
  • 71
  • 96
  • I will attempt to get a more detailed answer from the professor. In the meantime, I suppose I'll occupy myself with the bulk of the assignment. As for quality, the distribution of the numbers doesn't matter too much for this implementation. I'll play around with this and see what I can do with it. Thanks. – anthony Nov 09 '17 at 01:14
  • @anthony I'm sure you heard the phrase "garbage in, garbage out" before :-) Meaning, if the random numbers are really bad, you can draw bad conclusions from the results of the simulation. Of course, if the only point of this is learning how to do a simulation rather than drawing conclusions from the results, then it's fine. – Nikos C. Nov 09 '17 at 01:18
  • I like this solution. It's pretty simple and it allows me to preset a seed and rerun previous sets of numbers. If I could bother you for one more explanation though, I tried putting in different LCG numbers and I got odd results. I tried ((my_rand_state * 134775813 + 1) % 4294967296) and even though my_rand_state is an unsigned long, it sometimes spat out negative numbers. I even changed my_rand() to explicitly return unsigned long, but nothing changed. – anthony Nov 10 '17 at 05:26
  • @anthony Don't do that. The arithmetic is unsigned, to avoid signed integer overflow (undefined behavior.) 4294967296 is not a value an `unsigned` can ever hold (4294967295 is the highest), so `% 4294967296` has no effect. The `% 2147483648` is there to limit the range of the result from 0 to the highest number a `signed long` can hold, which is 2147483647. So don't change that. Also don't change the constants. `1103515245 + 12345` is chosen because of https://stackoverflow.com/questions/8569113/why-1103515245-is-used-in-rand so if you change it, your numbers are going to be much worse. – Nikos C. Nov 10 '17 at 07:35
  • @anthony Also see this (fun) talk by Stephan T. Lavavej: https://channel9.msdn.com/Events/GoingNative/2013/rand-Considered-Harmful – Nikos C. Nov 10 '17 at 08:05
  • Okay, I understand. I was just picking numbers off of Wikipedia's "Linear Congruential Generator" page. It didn't even occur to me that I might exceed what the variables are capable of. Thanks for your help. – anthony Nov 10 '17 at 09:43