6

I need to generate a random double between -0.10 and 0.25 inclusive of -0.10 and 0.25. I don't completely understand random number generators with java yet so I'm not quite sure how to do this. I know that the code below generates a number within that range, but I'm almost 100% certain it's not inclusive yet. How would I change it to be inclusive of the min and max of the range?

public double randDouble() {
    //formula: (Math.random() * (max - min) + min
    //in this case, min = -0.10 and max = 0.25
    return (Math.random() * 0.35) - 0.10;
}

My question is different than the one @javaguy is talking about because no where on that thread does someone say how to make this inclusive on both ends. And I've tested this code, but haven't seen an output of -.10 or 0.25 so unless my tests just weren't big enough, I can't see how it's inclusive of both ends.

Sirius
  • 73
  • 1
  • 4

7 Answers7

7

Since you want values between -0.10 and 0.25 both inclusive, i would suggest a different approach to control the granularity of your random values. Using Math.random() will return values between 0.0(inclusive) and 1.0(exclusive), so using your approach you will never get the value of 0.25. Instead use the Random() class and use integers to get both ends inclusive.

Random randomValue = new Random();
int randomInt = randomValue.nextInt(3501) - 1000; //Will get integer values between -1000 and 2500
Double rangedValue = Double(randomInt)/10000.0 // Get values between -0.10 and 0.25 

Alternatively you can more decimal places by increasing the magnitude of the values passed into randomValue.nextInt() and accordingly altering the value being devided by in the last line.

Sasang
  • 1,261
  • 9
  • 10
  • 1
    On one hand, this may be thought of as cheating. On the other hand, it may be thought of as a pragmatic/practical solution to the OP's problem. – Mike Nakis Apr 11 '17 at 19:00
5

Your plan to obtain random double values inclusive of the limits is ill-conceived, since there is no guarantee that you will ever receive a value which is equal to either limit.

That's due to the huge precision of double, which means that the possibility of obtaining any exact given random double is astronomically slim.

You can have that random number issued billions of times, and you may get thousands of values that are very close, extremely close to the limit, and yet none of them may ever happen to be equal to the limit.

Therefore, you cannot have any logic that depends on a random double number being issued which will be equal to the limit, because that random number may never be yielded.

So, the solution to your problem is very simple: stop worrying about inclusive vs. exclusive, because all you can ever hope for is exclusive. That should make things more simple for you.

Mike Nakis
  • 56,297
  • 11
  • 110
  • 142
  • 1
    I was about to answer on similar lines and i think this is the correct answer to the question – RajatJ Apr 11 '17 at 18:06
1

Your current formula includes the lower bound but excludes the upper bound. To fix this, you can add Double.MIN_VALUE to the upper bound, creating a new max. This slightly changes your bounds so that you actually want to exclude the new max. Yout old max is included in [min, newMax).

public double randDouble() {
    //formula: Math.random() * (max + Double.MIN_VALUE - min) + min
    //in this case, min = -0.10 and max = 0.25
    return (Math.random() * (0.35 + Double.MIN_VALUE)) - 0.10;
}
1

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.)

tevemadar
  • 12,389
  • 3
  • 21
  • 49
0

As i run in a similar problem getting some random float values in between a min and a max and found no solution ready to use, I would like to post mine. Thank You Sasang for providing me with the right idea!

The min and max values are inclusive but if the range or precision is big it's unlikely to happen. You could use the precision also as a parameter if you have different use cases for that function.

public class MyRandom {

    private static Random randomValue = new Random();

    public static double randomFloat(double min, double max) {
        double precision = 1000000D;
        double number = randomValue.nextInt((int) ((max - min) * precision + 1)) + min * precision;
        return number / precision;
    }
}

example: called with min = -0.10 and max 0.25 and precision 1000000 generated output like:

-0.116965
0.067249
0.246948
-0.180695
-0.033533
0.08214
-0.053864
0.216388
-0.158086
0.05963
0.168015
0.119533
Stefan
  • 438
  • 4
  • 9
-1

You can also use Streams but is [fromInclusive, toExclusive). Check the following sample, one using Streams and another one using Random:

Edited: (I wasn't careful reading the docs, all versions are fromInclusive-toExclusive)

public class MainClass {
public static void main(String[] args) {
    generateWithStreams(-0.10, 0.25);
    generateWithoutStreams(-0.10, 0.25);
}

private static void generateWithStreams(double fromInclusive, double toExclusive) {
    new Random()
            .doubles(fromInclusive, toExclusive)
            // limit the stream to 100 doubles
            .limit(100)
            .forEach(System.out::println);
}

private static void generateWithoutStreams(double fromInclusive, double toExcusive) {
    Random random = new Random();

    // generating 100 doubles
    for (int index = 0; index < 100; index++) {
        System.out.println(random.nextDouble() * (toExcusive - fromInclusive) + fromInclusive);
    }
}

}

andreim
  • 3,365
  • 23
  • 21
-2

The formula you used will give you random values inclusive of lower bound of range but exclusive of upper bound, because:

Math.random() returns a pseudorandom double greater than or equal to 0.0 and less than 1.0

BhalchandraSW
  • 714
  • 5
  • 13