If you can live with float
and with ~8 million steps, you can simply throw away almost half of the numbers (one less than half of the numbers):
private static Random rnd=new Random();
public static float next0to1() {
float f;
do {
f=rnd.nextFloat();
} while(f>0.5);
return f*2;
}
this function will generate random floats between 0 and 1, both ends included.
A test snippet like
long start=System.currentTimeMillis();
int tries=0;
while(next0to1()!=0)
tries++;
System.out.println(tries);
while(next0to1()!=1)
tries++;
System.out.println(tries);
System.out.println(System.currentTimeMillis()-start);
or a longer one with your actual numbers and some extra checks
long start=System.currentTimeMillis();
int tries=0;
float min=-0.1f;
float max=0.25f;
tries=0;
float current;
do {
tries++;
current=min+next0to1()*(max-min);
if(current<min)
throw new RuntimeException(current+"<"+min);
if(current>max)
throw new RuntimeException(current+">"+max);
} while(current!=min);
System.out.println(tries);
do {
tries++;
current=min+next0to1()*(max-min);
if(current<min)
throw new RuntimeException(current+"<"+min);
if(current>max)
throw new RuntimeException(current+">"+max);
} while(current!=max);
System.out.println(tries);
System.out.println(System.currentTimeMillis()-start);
will typically show a couple ten million tries to generate both a 0 and 1, completes in less than a second for me on a 5-year old laptop.
Side remark: it's normal that it usually takes more than 8 million tries: while nextFloat()
generates 24 bits, which is reduced to ~23 by throwing away almost half of the numbers, the generator itself works on 48 bits.
The best you can do with Random
is still nextInt()
as shown in Sasang's answer. The usable range is 2^30:
static double next0to1() {
return rnd.nextInt(0x40000001)/(double)0x40000000;
}
This one takes far more time (minutes) and tries (billions, tries
is better changed to long
for this one), to generate both 0 and 1.
Random.nextDouble(), or "cracking" random
nextDouble()
needs more precision than what the generator can produce in a single step and it puts together a 26 and 27-bit number instead:
public double nextDouble() {
return (((long)next(26) << 27) + next(27))
/ (double)(1L << 53);
}
where next()
is described as
seed = (seed * 0x5DEECE66DL + 0xBL) & ((1L << 48) - 1)
return (int)(seed >>> (48 - bits))
(but it can be actually found too, like https://github.com/openjdk-mirror/jdk7u-jdk/blob/master/src/share/classes/java/util/Random.java#L183)
which implies that in order to generate 0, a a=next(26)
and a consecutive b=next(27)
have to both return 0, so
seeda=00000000 00000000 00000000 00xxxxxx xxxxxxxx xxxxxxxx (binary, 48 bits)
seedb=00000000 00000000 00000000 000xxxxx xxxxxxxx xxxxxxxx (binary, 48 bits)
and from the update:
seedb = (seeda * 0x5DEECE66DL + 0xBL) & ((1L << 48) - 1)
it can be brute-forced in a moment (4 million possibilities) what seeda
has to be:
long mask = ((long) ((1 << 27) - 1)) << 21;
System.out.println(Long.toBinaryString(mask));
for (long i = 0; i < 1 << 22; i++)
if (((i * 0x5DEECE66DL + 0xBL) & mask) == 0)
System.out.println(i);
where the loop runs in the lower 22 bits (as we know that the rest is zero) and mask
is 11111111 11111111 11111111 11100000 00000000 00000000
for checking if the relevant 27 bits of the next seed are zeros.
So seeda=0
.
The next question is if there exists a previous seedx
to generate seeda
, so
seeda = (seedx * 0x5DEECE66DL + 0xBL) & ((1L << 48) - 1)
just this time we don't know the bits, so brute-forcing won't help. But this kind of equation is an actual thing, called a congruence relation, and is solvable.
0 "=" seedx * 0x5DEECE66DL + 0xBL (mod 2^48)
WolframAlpha needs it in the form of Ax "=" B (mod C), and in decimal, so the inputs are
25214903917x <- 0x5DEECE66D
281474976710656 <- 2^48
-11 <- 0xB, went to the other side
One possible solution is 107048004364969. Learning/knowing that Random
XOR-s the seed with the magic number, it can be tested:
double low=-0.1,
high=0.25;
Random rnd=new Random(0x5DEECE66DL ^ 107048004364969L);
System.out.println(low+(high-low)*rnd.nextDouble()==low);
will result in true
. So yes, Random.nextDouble()
can generate an exact 0.
The next part is 0.5:
seeda=10000000 00000000 00000000 00xxxxxx xxxxxxxx xxxxxxxx (binary, 48 bits)
seedb=00000000 00000000 00000000 000xxxxx xxxxxxxx xxxxxxxx (binary, 48 bits)
seeda
has its 48th digit set to 1.
Comes the brute-forcing loop:
long mask = ((long) ((1 << 27) - 1)) << 21;
System.out.println(Long.toBinaryString(mask));
for (long i = 0; i < 1 << 22; i++)
if ((((i + 0x800000000000L) * 0x5DEECE66DL + 0xBL) & mask) == 0)
System.out.println(i);
Ooops, there is no solution. 5DEECE66D has its lowest bit set (it's an odd number), so when we have exactly one bit set to 1 as in 0x80...., that will remain 1 after the multiplication - and of course this also applies if we try moving that single bit to the right).
TL;DR: Random.nextDouble()
(and consequently Math.random()
) will never generate an exact 0.5. (or 0.25, 0.125, etc.)