4

I have a line in my code much like below:

float rand = (float) Math.random();

Math.random() returns a double that is >=0.0 and <1.0. Unfortunately, the cast above may set rand to 1.0f if the double is too close to 1.0.

Is there a way to cast a double to a float in such a way that when no exact equal value can be found, it always rounds down to the next float value instead of to the nearest float value?

I'm not looking for advice on RNGs, nor work-arounds such as following up with if(rand == 1.0f) rand = 0.0f;. In my case, that solution is a satisfactory way to fix my problem, and it has already been implemented. I am just interest in finding out "proper" solution to this kind of number conversion.

Numeron
  • 8,723
  • 3
  • 21
  • 46
  • Is there a reason you can't use [nextFloat()](https://docs.oracle.com/javase/8/docs/api/java/util/Random.html#nextFloat--)? – VGR Nov 26 '14 at 16:54

2 Answers2

1

The option I'd suggest would be to use double rather than float.

The only disadvantages I've ever found generally only come into play when you have a large number of them, in that the storage requirements are higher, and this doesn't appear to be the case here.

If you must use float, then the solution you've already tried is probably the best one available. It's a fundamental problem that loss of precision when converting double to float may give you 1.0f.

So coerce the float value to be in your desired range.

However, you don't have to pepper your code with if statements for this, simply provide a float random function that does the grunt work for you:

float frandom() {
    float ret = 1.0f;
    while (ret == 1.0f)
        ret = (float) Math.random();
    return ret;
}

or, as per your sample:

float frandom() {
    float ret = (float) Math.random();
    if (ret == 1.0f)
        ret = 0.0f;
    return ret;
}

then call it with:

float rand = frandom();

This would be the point where it would be nice for Java (and others) to have the Javascript ability to "inject" code into a type so that you could implement frandom() into the static Math arena. That way you could just use:

float rand = Math.frandom();

But, alas, this is not yet possible (as far as I know, short of recompiling the Java support libraries, which seems a bit of an overkill).


By the way, if you're looking for the maximum float value less than 1, it's 0.99999994 (an exponent multiplier of 2-1 with all mantissa bits set to 1), so you could use that instead of 0.0f in the frandom() function above.

This is gleaned from a handy little tool I wrote some time ago, one that's proven invaluable when fiddling about with IEEE754 floating point values.

paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
  • Yep, that is basically what I already have. The question I want to know is how to convert a double into a float by rounding down to the next value instead of to the nearest available. – Numeron Nov 25 '14 at 05:58
  • 1
    Should `0.1` be `1.0`? – user253751 Nov 25 '14 at 06:07
  • The maximum float less than `1.0f` is `0.99999994f`, but the maximum double that rounds to less than `1.0f` is around `0.99999997`, give or take a ten of a millionth. It comes down to what distribution is expected of the numbers generated. Using 0.99999994 as limit will make the last value half as probable as it would have been if the distribution was more uniform. – Pascal Cuoq Nov 25 '14 at 06:09
  • @Pascal, if you're the type of person actually _concerned_ about minor distribution effects at the top or bottom end of a range, you'd _already_ be using a different method :-) – paxdiablo Nov 25 '14 at 06:11
  • @immibis, yes, it should have been. Fixed, and thanks. – paxdiablo Nov 25 '14 at 06:12
1

If you only want to round when rand==1.0f, then I second @paxdiablo's answer. However, it sounds like you're okay with always rounding, and simply want to always round down. If so:

float rand = Math.nextDown((float)Math.random());

from the javadoc:

nextDown(float f) Returns the floating-point value adjacent to f in the direction of negative infinity.

In response to your comment - good point. To avoid that problem, you could simply wrap the statement in a call to Math.abs(), which will have no affect except when the result of nextDown() is negative.

float rand = Math.abs(Math.nextDown((float)Math.random()));
drew moore
  • 31,565
  • 17
  • 75
  • 112
  • 1
    This is a good useful find! However it would be bad if the Math.random() call returns 0.0, as it would push it below 0 – Numeron Nov 25 '14 at 06:04
  • I think if I use something like 'if(doubleRand != floatRand) floatRand = Math.nextDown(floatRand);' - so it only drops a value when there is no equal equivilant. – Numeron Nov 25 '14 at 06:08
  • @Numeron - sure, and that'd probably be a better way to go. I was just having fun trying to do it without any `if` statements :) – drew moore Nov 25 '14 at 06:10
  • 3
    This is the ugliest solution of the two that have been proposed, and it is disappointing that it was chosen as the accepted answer. This solution has aliasing issues around powers of two (some values cannot be obtained, specifically values of the form `0x1.fffffepXX`, whereas powers of two are over-represented). – Pascal Cuoq Nov 25 '14 at 06:13
  • @drewmoore, at some point, a long chain of function calls will probably be _slower_ than an `if`. Kudos for conserving screen real estate though, in one dimension anyway :-) – paxdiablo Nov 25 '14 at 06:14
  • 1
    @paxdiablo I agree - see my comment above, I'm quite sure an `if` would be the better solution here, I was just having some fun answering the question as it was asked (i.e. sans `if`s) pascal, proposing a solution and answering a *very specific* question are two very different things - this was the latter. – drew moore Nov 25 '14 at 06:16
  • 1
    @PascalCuoq the other answer doesn't really answer the question though, I said I wasn't interested in work-arounds for my specific problem - rather I wanted to how to find the float value nearest to but not above a double. – Numeron Nov 25 '14 at 06:16