-3

So as the title describes I'm trying to learn PRNG but it hasn't gone too well thusfar. I am writing a program that takes an entered number of choices, rolls a dice and makes the choice for you. I do this using rand() and an array of strings with a length that is dependant on a variable read in before the declaration of the array.

The two problems showing up are that if I try to choose between for example 12 choices, the program will crash. As I read in the length of the array before I declare it I don't understand how this could happen. Also the PRNG will always return the number 2 which is not the desired function of a function that should return a random number between min and max. I will share my code below:

#include <iostream>
#include <cstdlib> // for rand() and srand()
#include <ctime> // for time()
#include <string>

using namespace std;

int callPRNG(int min, int max)
{
    srand(static_cast<unsigned int>(time(0))); // set initial seed value to system clock

    static const double fraction = 1.0 / (static_cast<double>(RAND_MAX) + 1.0);  // static used for efficiency, so we only calculate this value once
    // evenly distribute the random number across our range
    return static_cast<int>(rand() * fraction * (max - min + 1) + min);
}


int main()
{
    int max=1;
    int min=0;

    cout << "Please enter the amount of choices you want to choose from: ";
    cin >> max;
    string choices[max-1];
    int choiceMade=0;

    {
    int i=0;
        while(i<=max-1)
        {
            cout << "Please enter choice " << i+1 << ": ";
            cin >> choices[i];
            i++;
        }
        choiceMade=callPRNG(min, max-1);
        cout << "I have decided: " << choices[choiceMade-1];
    }

    return 0;
}

As extra information, I'm using code::blocks with C++11 enabled on a windows 10 OS. I hope someone can help me with this as I don't quite understand arrays and PRNG well enough to find the error by myself. Thanks :)

edit: It seems, as I can now use higher values for max without crashing the program, that the answer scales with the value of max.

ReznoV
  • 37
  • 4
  • What error did it crash with? And you really need to learn to debug. – Carcigenicate Jan 01 '17 at 01:41
  • 2
    `srand` is something you should call *once,* before you start generating a random sequence. If you call it before each random number, you risk getting the same number each time if the seed value hasn't changed (such as if you use the current time and things happen very quickly). – paxdiablo Jan 01 '17 at 01:42
  • Special thanks to the person who editted my post to include the code instead of the ugly pastebin link. I couldn't get it done but it does look way better like this. – ReznoV Jan 01 '17 at 01:43
  • See [`srand()` — why call it only once?](http://stackoverflow.com/questions/7343833/srand-why-call-it-only-once/) Whether that's your main problem isn't clear to me yet, but you're in danger of violating it if you call your function more than once. – Jonathan Leffler Jan 01 '17 at 01:44
  • @paxdiablo I reran the program to test multiple times. So the time should have changed meanwhile. – ReznoV Jan 01 '17 at 01:45
  • @Carcigenicate it says "RandomChoiceMaker.exe doesn't work anymore.. A problem occured which caused the program to fail. The program will be closed and a notice will be given when there's a fix available. – ReznoV Jan 01 '17 at 01:46
  • I think you need an array of `max` choices (taking indexes `0` .. `max-1`). So `string choices[max];` would be more nearly correct. You seem to be using a G++ extension, too — a variable-length array of strings. You'd more normally use a vector of strings in C++: `vector choices(max);` or thereabouts. – Jonathan Leffler Jan 01 '17 at 01:48
  • @JonathanLeffler as I have not nailed my understanding about basic arrays yet I haven't yet bothered to look into vector strings. I have seen samples of those but they always seem to come down at pointers which is an area I haven't yet discovered. using max instead of max-1 did fix the crashing part however so thanks alot for that. – ReznoV Jan 01 '17 at 01:57
  • 1
    An array defined as `string choices[max-1]` has `max-1` elements, numbered `0` through `max-2`. Your program exhibits undefined behavior by way of accessing the non-existent `choices[max-1]`. That happens in the reading loop, before `callPRNG` is ever called. – Igor Tandetnik Jan 01 '17 at 02:04
  • Apart from adding `#include `, the only other change needed to use the vector of strings is to use `vector choices(max);` — the rest of the code remains unchanged. You may do better avoiding the floating point arithmetic in `callPRNG()`. I'm getting persistent values for the choice working on macOS Sierra 10.12.2 too — but `rand()` is moderately awful on the platform. – Jonathan Leffler Jan 01 '17 at 02:05
  • [Why is “using namespace std” considered bad practice?](http://stackoverflow.com/questions/1452721) – Ryan Bemrose Jan 01 '17 at 02:14
  • @Galik: No; although [`srand()` — why call it only once](https://stackoverflow.com/questions/7343833/srand-why-call-it-only-once) would be relevant if the function containing the call to `srand()` was called more than once, that is not the primary problem with this code — the function is only called once. – Jonathan Leffler Jan 01 '17 at 02:32

2 Answers2

1

The problem with crashing is because you're not allocating enough space for the array of choices (you need the size to be max, not max - 1). You're using a G++ extension to C++ — namely variable length arrays — which are part of standard C but not standard C++. You should use a vector of strings.

The non-random random behaviour is more a reflection on the quality of the rand() PRNG — it is often not very good. I was getting similar behaviour on macOS Sierra 10.12.2 with G++ 6.3.0. I worked around it by avoiding the use of floating point arithmetic and using modulus operations.

The use of choiceMade-1 was suspect too; given a choice of 0, it tries to access non-existent element -1 of the array of choices.

These changes yield the following code, which still has debug code in place:

#include <iostream>
#include <cstdlib>
#include <ctime>
#include <string>
#include <vector>

using namespace std;

int callPRNG(int min, int max)
{
    cout << __func__ << ": " << min << " .. " << max << "\n";

    unsigned int seed = static_cast < unsigned int > (time(0));
    cout << __func__ << ": " << seed << "\n";

    srand(seed);

    int range = max - min + 1;
    int max_valid = RAND_MAX - RAND_MAX % range;

    int rand_val;
    while ((rand_val = rand()) > max_valid)
        ;

    int rv = rand_val % range;
    cout << __func__ << ", rand_val = " << rand_val << ", ret_val = " << rv << "\n";

    return rv;
}

int main()
{
    int max = 1;
    int min = 0;

    cout << "Please enter the amount of choices you want to choose from: ";
    cin >> max;
    vector < string > choices(max);
    int choiceMade = 0;

    int i = 0;
    while (i <= max - 1)
    {
        cout << "Please enter choice " << i + 1 << ": ";
        cin >> choices[i];
        i++;
    }
    choiceMade = callPRNG(min, max - 1);
    cout << "I have decided: " << choices[choiceMade] << endl;

    return 0;
}

Sample outputs:

Please enter the amount of choices you want to choose from: 12
Please enter choice 1: a
Please enter choice 2: b
Please enter choice 3: c
Please enter choice 4: d
Please enter choice 5: e
Please enter choice 6: f
Please enter choice 7: g
Please enter choice 8: h
Please enter choice 9: i
Please enter choice 10: j
Please enter choice 11: k
Please enter choice 12: l
callPRNG: 0 .. 11
callPRNG: 1483236759
callPRNG, rand_val = 770034137, ret_val = 5
I have decided: f

Over a series of runs, with filtering out all except the 'I have decided' lines, I got outputs:

I have decided: g
I have decided: i
I have decided: d
I have decided: f
I have decided: a
I have decided: c
I have decided: l
I have decided: f
I have decided: a
I have decided: f
I have decided: h
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • Thanks this code has proven to work perfectly so far. I think I see what you've done with the PRNG and will start looking into the using of vector strings instead of variable length arrays in the future. I appreciate the time you spend helping me. – ReznoV Jan 01 '17 at 12:45
  • Great answer. Thanks for clarifying the extension that was used. I couldn't understand how it was compiling. – wally Jan 01 '17 at 14:44
0

Jonathan Leffler's answer was complete and correct. But I thought you might also be interested in a shorter program that uses <random>:

#include <vector>
#include <iostream>
#include <string>
#include <random>

size_t callRNG(size_t min, size_t max)
{
    std::random_device r{};
    std::uniform_int_distribution<size_t> ud{min,max};
    return ud(r);
}

int main()
{
    const auto min{0};
    std::vector<std::string> choices{};
    std::string line{};
    while(std::cout << "Please enter choice: ", std::getline(std::cin, line) && !line.empty()) {
        choices.push_back(line);
    }
    const auto max{choices.size() - 1};
    if(!choices.empty()) {
        std::cout << "I have decided: " << choices[callRNG(min, max)] << '\n';
    }
}
wally
  • 10,717
  • 5
  • 39
  • 72