Tuesday, December 9, 2008

Generating A Random Number Within A Range in Java

The task should be simple. If you search for tips, examples, and tutorials on generating random numbers in Java you will find allot of helpful articles. The problem I ran across was that I needed to generate a random number within a range of user specified numbers. Well, this too is simple as you can use the nextInt(n) method of the Random class to accomplish this, right? Well, I must admit, in most cases you can. In my case, however, I couldn't. See, I needed a random number that could potentially be negative as well as positive. So back to searching and you will find that nextInt(n) should also address my need. It should work something like this:


int randNum = random.nextInt( hiNum - loNum + 1 ) + loNum;

Well, this produce the desired result. I can set hiNum and loNum both to positive numbers and get a pretty even distribution of the range. I can also set hiNum and loNum both to negative numbers and get a pretty even distribution of the range. And most importantly I can set loNum to a negative number and hiNum to a positive number and once again get a pretty even distribution of the range. Seems like the perfect solution, right?


Not for me. The problem with this solution is that nextInt(n) accepts an integer as its parameter. An integer seems to work pretty well until we attempt to set the integer to a value larger than Integer.MAX_VALUE. Why would we ever do this you ask? It is simple, the integer value passed to nextInt(n) represents the range of values you want to generate a random number within. The nextInt(n) method was intended to return a positive random integer. What this means is that as soon as we start adding numbers on the negative side of the field to our integer value range we end up with an unsigned integer. In other words the range of -2 to 2 is 5. The range will include -2, -1, 0, 1, and 2. So the nextInt(n) method keeps working until we hit a range of Integer.MAX_VALUE + 1. Once we do that range gets corrupted for all practical purposes because it becomes a negative value which then results in nextInt(n) throwing an exception.


This can be seen by simply setting loNum and hiNum as follows:


int loNum = Integer.MIN_VALUE / 2;
int hiNum = Integer.MAX_VALUE / 2;

Everything would be fine is we add 1 to loNum or subtract 1 from hiNum but as long as we set them as above, expect nextInt(n) to fail.


I searched for quite some time looking for the solution and all solutions that I found made the same mistake in not taking into consideration the maximum size of an integer. The one thing that I did establish very early is that nextInt(n) will not work for my needs as range needs to be a long. So, here is what I came up with:


long range = ( loNum <> 0 ) ? ( ( (long)hiNum - (long)loNum ) + 1) : ( (long)hiNum - (long)loNum );
int randNum = (int)(long)((random.nextDouble() * (long)(range + 1)) + ( ( loNum <> 0 ? (loNum - 1) : loNum )) );

So, I first calculate a long value and set it as the range. If the range is to include the number 0 I have to add 1 to the range to account for it. This is why we see the loNum <> 0 ? ... : .... Once I have calculated my range using a long value I can call the nextDouble() method from the Random class which will return a number between 0.0 and 1.0. I can then take the random double and multiply it by my range. We add 1 to take into account integer rounding that will occur and result in our hiNum not being achieved due to rounding down. I then need to add loNum to the result so that we get the correct range within an integer value. Again, I have to take into account that 0 may or may not be included in the range of integers and if it isn't I need to subtract 1 from the result.


The most important thing I found when putting this together is the types that are involved. What I initially came up with wasn't working because I failed to case loNum and hiNum to long when I was calculating range resulting in range (although a long) being assigned the bad integer value. Once I worked out my casting issues all seemed to come together. The above now allows me to set loNum to Integer.MIN_VALUE and hiNum to Integer.MAX_VALUE and get expected and acceptable results.