Your approach, unfortunately, doesn't work. Indeed, while random:uniform/1
accepts any positive integer as its argument, it does not deliver a random integer uniformly distributed between 1 and N for very large values of N (in spite of what documentation claims).
The reason is that random:uniform/1
is actually truncating the product of its argument by the value of random:uniform/0
(and adding 1 for [1-N] range instead of [0-(N-1)]).
See source code: https://github.com/erlang/otp/blob/maint/lib/stdlib/src/random.erl#L112
And floats are IEEE 754 doubles with 53 bits mantissa, which means that get_id/1
will not return all possible values for input from 17 to 20 (with 16 or more digits).
random:uniform/0,1
is known as a poor random generator, mostly suitable if you want to generate reproductible pseudo-random series (a given seed value will always generate the same series). For this reason, I would suggest using crypto:rand_uniform/2.
A simple solution would be to compute 10^(N-1)
using integer arithmetics (to avoid the 53 bits mantissa issue) and then call crypto:rand_uniform/2
. You can perform this with a naive recursive implementation (pow1/1
below), or use binary exponentiation (pow2/1
below).
-define(BASE, 10).
-spec pow1(non_neg_integer()) -> pos_integer().
pow1(N) when N >= 0 ->
pow1(N, 1).
pow1(0, Acc) -> Acc;
pow1(N, Acc) ->
pow1(N - 1, Acc * ?BASE).
-spec pow2(non_neg_integer()) -> pos_integer().
pow2(N) when N >= 0 ->
pow2(?BASE, N, 1).
pow2(_X, 0, Acc) ->
Acc;
pow2(X, N, Acc) when N rem 2 =:= 0 ->
pow2(X * X, N div 2, Acc);
pow2(X, N, Acc) ->
pow2(X * X, N div 2, Acc * X).
Your function could simply be written as:
-spec get_id2(pos_integer()) -> non_neg_integer().
get_id2(N) ->
1 + crypto:rand_uniform(0, pow2(N - 1)).
Alternatively, you could use a combination of uniform random variables, one per digit (while the sum of two random uniform variables is generally not a uniform random variable, it is if combined like this) or for several digits in the case of the binary exponentiation.
With the naive exponentiation:
-spec get_id3(pos_integer()) -> pos_integer().
get_id3(N) when N > 0 ->
get_id3(N - 1, 0).
get_id3(0, Acc) -> 1 + Acc;
get_id3(N, Acc) ->
Acc1 = crypto:rand_uniform(0, ?BASE) + (Acc * ?BASE),
get_id3(N - 1, Acc1).
With the binary exponentiation:
-spec get_id4(pos_integer()) -> pos_integer().
get_id4(N) when N > 0 ->
get_id4(?BASE, N - 1, 0).
get_id4(_X, 0, Acc) ->
1 + Acc;
get_id4(X, N, Acc) when N rem 2 =:= 0 ->
get_id4(X * X, N div 2, Acc);
get_id4(X, N, Acc) ->
Acc1 = crypto:rand_uniform(0, X) + (Acc * X),
get_id4(X * X, N div 2, Acc1).