javafloating-pointdouble

Precision issues when converting double to float


JDK has a random method in ThreadLocalRandom:

nextDouble(startInclusive, endExclusive)

But no float version. Here I want to float version, this is my implementation:

public static float nextFloat(float startInclusive, float endExclusive) {
    return (float) nextDouble(startInclusive, endExclusive);
}

I know float number has precision issues when cast double to float. So, is it possible the result float value < startInclusive or >= endExclusive? And how to solve it?


Solution

  • Indeed your current implementation might output a float that is equal to endExclusive. Suppose next double returns d, which is less than endExclusive, but the closest value to d representable by a float could still be endExclusive.

    In modern versions of the JDK, there exists nextFloat that takes a lower bound and upper bound. If you are on an older version, you can just take a look at how nextFloat in newer versions are implemented, and do the same thing yourself.

    Using the code from RandomSupport.java in the main branch of OpenJDK, you can write such a helper:

    public static float nextFloat(Random random, float origin, float bound) {
        if (!(Float.NEGATIVE_INFINITY < origin && origin < bound &&
            bound < Float.POSITIVE_INFINITY)) {
            throw new IllegalArgumentException("Invalid range");
        }
        float r = random.nextFloat();
        if (bound - origin < Float.POSITIVE_INFINITY) {
            r = r * (bound - origin) + origin;
        } else {
            float halfOrigin = 0.5f * origin;
            r = (r * (0.5f * bound - halfOrigin) + halfOrigin) * 2.0f;
        }
        if (r >= bound)
            r = Math.nextDown(bound);
        return r;
    }
    

    This uses the parameterless nextFloat, which existed for a long time. It returns a random float between 0 and 1 inclusive, and the code simply linearly maps that 0~1 range to the range we want. It is also possible for this process to generate a number greater than or equal to the upper bound, in which case the JDK seems to just use the greatest float that is smaller than bound (Math.nextDown).