2

I've been using java's SplittableRandom ever since I heard about it, due to its speed, and not being in need of multithreading. However, although it has almost every method from Random class, it doesn't come with nextFloat(). Why's that?

Now, the real question is, how would I then go about creating that nextFloat method? Seeing the double is generated as follows: (from JDK 8)

final double internalNextDouble(final double n, final double n2) {
    double longBitsToDouble = (this.nextLong() >>> 11) * 1.1102230246251565E-16;
    if (n < n2) {
        longBitsToDouble = longBitsToDouble * (n2 - n) + n;
        if (longBitsToDouble >= n2) {
            longBitsToDouble = Double.longBitsToDouble(Double.doubleToLongBits(n2) - 1L);
        }
    }
    return longBitsToDouble;
}

.. I was hoping that I could just turn it to a 32-bit number generation with the following;

final float internalNextFloat(final float min, final float max) {
    float intBitsToFloat = (this.nextInt() >>> 11) * 1.1102230246251565E-16f;
    if (min < max) {
        intBitsToFloat = intBitsToFloat * (max - min) + min;
        if (intBitsToFloat >= max) {
            intBitsToFloat = Float.intBitsToFloat(Float.floatToIntBits(max) - 1);
        }
    }
    return intBitsToFloat;
}

However, this returns 0.000000. I can only assume it overflows somewhere, in which case I'm pretty sure the problem lies at the following line:

(this.nextInt() >>> 11) * 1.1102230246251565E-16f;

So, not being experienced with shifting (and using epsilon I guess), how could I achieve what I want?

5gon12eder
  • 24,280
  • 5
  • 45
  • 92
jetp250
  • 63
  • 5
  • Also, wouldn't a modulus operator be faster when scaling the number range, at least when it comes to big numbers? Is there a reason it's done that way? – jetp250 Mar 19 '17 at 16:01
  • 1
    See http://stackoverflow.com/questions/10984974/why-do-people-say-there-is-modulo-bias-when-using-a-random-number-generator/10984975#10984975 – kennytm Mar 19 '17 at 16:12
  • Why don't you use http://docs.oracle.com/javase/8/docs/api/java/util/Random.html#nextFloat-- ? –  Mar 19 '17 at 16:15
  • Thanks @kennytm. I'll try to use it still, as it clearly speeds up generating bigger numbers. – jetp250 Mar 19 '17 at 16:28
  • As to why the function doesn't exist, it's probably because `float` is not one of the types that the new Java 8 features were targeting for support (e.g. there is no `FloatStream` or `FloatConsumer` etc.). The first-class citizens for the new features are `int`, `long`, and `double` (and sometimes `boolean`). – kbolino Mar 19 '17 at 16:32
  • Interesting, thanks @kbolino! Do you know if there was a specific reason for not including `float` in this list? – jetp250 Mar 19 '17 at 16:49
  • @jetp250 closest thing to an answer I could find is from [State of the Lambda](http://cr.openjdk.java.net/~briangoetz/lambda/lambda-state-final.html) (from JSR 335/Project Lambda), which says "the other primitive types can be accomodated through conversions"; to me, that means the answer is "to avoid API bloat and reduce maintenance burden", but this particular case might be a reasonable exception: it's just one function and it's not just a specialization, the behavior is different as the accepted answer demonstrates – kbolino Mar 23 '17 at 12:00

2 Answers2

2

Without having thought about the mathematics of this too deeply, it seems to me that you could just use the nextDouble method to generate a double within the desired range and then cast the result to float.

5gon12eder
  • 24,280
  • 5
  • 45
  • 92
  • Yes, but why? Sure, that's easier, but it's also ever so slightly slower, since it has to convert a 64-bit double to 32-bit float. And that way I'd never learn anything new :) – jetp250 Mar 19 '17 at 16:24
  • 1
    @jetp250 I appreciate your desire to learn very much. Questions on Stack Overflow are usually answered under the assumption that you have production code to get working, though. And in that case, rolling your own low-level mathematic routines is normally not a good idea. It might be worth pointing out in your questions if your case is different. I wouldn't be concerned about the performance, though. I doubt that you'll be even able to measure it in your application and if such micro optimizations are a concern, then Java might be the wrong technology to begin with. – 5gon12eder Mar 19 '17 at 16:41
  • Thank you @5gon12eder! About the performance; yes, that's something I'm always too concerned with, everything has to work as smoothly as possible. Not the best trait, I admit. However, I'll have to use java in this case, the entire library I'm using only exists in java, and I doubt it'll ever be ported to any other language. However, I indeed am interested in taking a look at some of the C languages! – jetp250 Mar 19 '17 at 16:52
1

You need to first understand the meaning behind this line:

double longBitsToDouble = (this.nextLong() >>> 11) * 1.1102230246251565E-16;
  1. this.nextLong() returns a 64 long.
  2. >>> 11 turn the long to unsigned and removes the last 11 bits, so now we get a 53-bit random value. This is also the precision of double.
  3. * 1.1102230246251565E-16. This is equivalent to 1 / 9007199254740992.0, or 2-53.

So longBitsToDouble is a randomly uniform double from 0 (inclusive) to 1 (exclusive).

Compared with a float, its precision is 24 bits, while this.nextInt() generates a 32-bit random value, so the corresponding line should be written as

float intBitsToFloat = (this.nextInt() >>> 8) * 5.960464477539063E-8f;

(Instead of the decimal representation 5.960464477539063E-8f you could also use hexadecimal float, which may be clearer to readers:

float intBitsToFloat = (this.nextInt() >>> 8) * 0x1.0p-24;

)

Community
  • 1
  • 1
kennytm
  • 510,854
  • 105
  • 1,084
  • 1,005