Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generate random float, both bounds inclusive

Tags:

java

random

I need to generate random real numbers in the range [-0.5, 0.5], both bounds inclusive.

I found various ways to generate similar ranges, like

-0.5 + Math.random()

But the upper bound is always exclusive, I need it inclusive as well. 0.5 must be inside the range.

like image 517
hsnsd Avatar asked May 10 '19 08:05

hsnsd


Video Answer


3 Answers

One way to achieve this would be to create random int from -500 to 500 and then divide it by 1000.

int max = 500;
int min = -500;
int randomInt = rand.nextInt((max - min) + 1) + min;
float randomNum = randomInt / 1000.00f;
System.out.println(randomNum);

You can change the precision by adding and removing zeros from the integer boundaries and the divisor. (eG: create integers from -5 to +5 and divide by 10 for less precision)

A disadvantage of that solution is that it does not use the maximum precision provided by float/double data types.

like image 96
OH GOD SPIDERS Avatar answered Nov 25 '22 00:11

OH GOD SPIDERS


I haven't seen any answer that uses bit-fiddling inside the IEEE-754 Double representation, so here's one.

Based on the observation that a rollover to a next binary exponent is the same as adding 1 to the binary representation (actually this is by design):

Double.longBitsToDouble(0x3ff0000000000000L) // 1.0
Double.longBitsToDouble(0x3ffFFFFFFFFFFFFFL) // 1.9999999999999998
Double.longBitsToDouble(0x4000000000000000L) // 2.0

I came up with this:

long   l = ThreadLocalRandom.current().nextLong(0x0010000000000001L);
double r = Double.longBitsToDouble(l + 0x3ff0000000000000L) - 1.5;

This technique works only with ranges that span a binary number (1, 2, 4, 8, 0.5, 0.25, etc) but for those ranges this approach is possibly the most efficient and accurate. This example is tuned for a span of 1. For ranges that do not span a binary range, you can still use this technique to get a different span. Apply the technique to get a number in the range [0, 1] and scale the result to the desired span. This has negligible accuracy loss, and the resulting accuracy is actually identical to that of Random.nextDouble(double, double).

For other spans, execute this code to find the offset:

double span = 0.125;

if (!(span > 0.0) || (Double.doubleToLongBits(span) & 0x000FFFFFFFFFFFFFL) != 0)
    throw new IllegalArgumentException("'span' is not a binary number: " + span);
if (span * 2 >= Double.MAX_VALUE)
    throw new IllegalArgumentException("'span' is too large: " + span);

System.out.println("Offset: 0x" + Long.toHexString(Double.doubleToLongBits(span)));

When you plug this offset into the second line of the actual code, you get a value in the range [span, 2*span]. Subtract the span to get a value starting at 0.

like image 22
Mark Jeronimus Avatar answered Nov 25 '22 02:11

Mark Jeronimus


You can adjust the upper bound by the minimal value (epsilon) larger than the maxium value you expect. To find the epsilon, start with any positive value and make it as small as it can get:

double min = -0.5;
double max = 0.5;

double epsilon = 1;
while (max + epsilon / 2 > max) {
    epsilon /= 2;
}

Random random = ThreadLocalRandom.current();
DoubleStream randomDoubles = random.doubles(min, max + epsilon);

Edit: alternative suggested by @DodgyCodeException (results in same epsilon as above):

double min = -0.5;
double max = 0.5;

double maxPlusEpsilon = Double.longBitsToDouble(Double.doubleToLongBits(max) + 1L)

Random random = ThreadLocalRandom.current();
DoubleStream randomDoubles = random.doubles(min, maxPlusEpsilon);
like image 23
Peter Walser Avatar answered Nov 25 '22 01:11

Peter Walser