Ada's random numbers are the same
I have the following problem with my function that needs to return a random number. When I want to generate a couple of numbers by calling this function, they are exactly the same. How can I fix the problem of returning the same number all the time when I call the function? I need this to be random in order to save the function.
Here is the code:
with Ada.Numerics.discrete_Random
function generate_random_number ( n: in Positive) return Integer is
subtype Rand_Range is Integer range 0 .. n;
package Rand_Int is new Ada.Numerics.Discrete_Random(Rand_Range);
use Rand_Int;
gen : Rand_Int.Generator;
ret_val: Rand_Range;
begin
Rand_Int.Reset(gen);
ret_val := Random(gen);
return ret_val;
end;
source to share
The random generator gen
does not have to be local to the function. Nowadays, you recreate it on every call generate_random_number
and initialize it, so it should come as no surprise that you always get the same result.
If you make gen
- for example, a global variable initialized once, then every time you use it, you get a new random number. (Yes, globals are bad: but see below)
Unfortunately, this does not reflect well in semantics function generate_random_number ( n: in Positive)
, which can limit the range of random numbers differently each time. The simplest solution is to force gen
return any real integer and use modular arithmetic to return a number in the correct range for each call. This will work, but you should be aware that it potentially presents cryptographic flaws beyond my analysis skills.
If so, you will need a different approach, such as creating a different random generator for each range you want; taking care of the seed (reset) in different ways, otherwise there might be cryptolocations such as correlations between different generators.
Now globals are bad structure for all the usual reasons in any language. So a much better way is to make it a resource by wrapping it in a package.
package RandGen is
function generate_random_number ( n: in Positive) return Positive;
end RandGen;
And that's all the client needs. The package is implemented as follows:
with Ada.Numerics.discrete_Random;
package body RandGen is
subtype Rand_Range is Positive;
package Rand_Int is new Ada.Numerics.Discrete_Random(Rand_Range);
gen : Rand_Int.Generator;
function generate_random_number ( n: in Positive) return Integer is
begin
return Rand_Int.Random(gen) mod n; -- or mod n+1 to include the end value
end generate_random_number;
-- package initialisation part
begin
Rand_Int.Reset(gen);
end RandGen;
EDIT: Jacob's suggestion of dropping out-of-range values ββis better, but ineffective, if n is much less than the generator range. One solution would be to create multiple generators and generate_random_number
select the one that covers 0 .. N at the lowest cost.
source to share
A procedure Random
with only one parameter should initiate a "time-dependent" random number generator. However, if it is called several times in a row, it is very likely that the "time" that the program uses will be the same each time, since the program will most likely run faster than the clock resolution. (Try your test with something like delay 0.5
between two use cases generate_random_number
, chances are you'll get two different results.)
Anyway, the intended use of a random number generator is to use Reset
it once on it to set the seed and then generate a sequence of random numbers starting at the seed. While doing this, you can also use the same seed each time to get a duplicate sequence of random numbers for testing; or you can use a time-dependent seed. However, resetting the random number generator every time you want a random number is not the normal or recommended way to use RNG. Therefore, you need to move the call Reset
from generate_random_number
. Unfortunately, this also means that you have to use the same generator even if the parametern
will be different on every call, which means you can use Float_Random
instead Discrete_Random
.
PS Having studied this carefully, I found this in G.2.5: "Two different calls to the temporary Reset procedure should Reset the generator in different states, provided that the calls are separated in time by at least one second and not more than fifty years." [Underline mine.] I'm sure when you tried it, the calls were for less than one second.
source to share
Assuming the upper limit can be determined at program start:
Package Random
(specification):
with Generic_Random;
with Upper_Limit_Function;
package Random is new Generic_Random (Upper_Limit => Upper_Limit_Function);
General package Generic_Random
(specification):
generic
Upper_Limit : Positive;
package Generic_Random is
subtype Values is Natural range 0 .. Upper_Limit;
function Value return Values;
end Generic_Random;
General package Generic_Random
(body):
with Ada.Numerics.Discrete_Random;
package body Generic_Random is
package Random is new Ada.Numerics.Discrete_Random (Values);
Source : Random.Generator;
function Value return Values is
begin
return Random.Random (Source);
end Value;
begin
Random.Reset (Source);
end Generic_Random;
source to share