There was a post last week on cf-talk about random number generation with ColdFusion, and in particular, the use of the
randomize() function. The ColdFusion documentation indicates that you should call
randomize() before calling
rand() in order to ensure highly random numbers (though the same applies to
randRange()). The problems are:
randomize()function only takes in an int. The best way to generate a unique number is by calling
getTickCount()which returns a long. The two functions cannot be used together.
- If you don’t use a unique number to seed the random number generator, then your results are anything but random. Each time you call
randRange()without reseeding the random number generator, you will get predictable results. In other words, the algorithm for generating random numbers is, ironically enough, predictable, so the only way to get (pseudo) random numbers is by using a unique, dynamic seed. The current time in milliseconds is a perfect choice (and is what gets used by default).
As Sam Neff pointed out on cf-talk, it seems better not to call
randomize() in order to allow the ColdFusion server to seed the random number generator with the current time in milliseconds — a long value that cannot otherwise be passed in — and I tend to agree. The problem with this, however, is synchronization since two requests made at the exact same time will cause the same “random” number to be generated. Fortunately, there’s a good solution.
Sam also suggested creating an instance of java.util.Random, storing it in the application scope, and using it to generate your random numbers. It automatically gets seeded with the current time in milliseconds, and will keep returning highly random numbers each time you call it. If you create a new instance of it on each request, you will get fairly random numbers since it will get re-seeded with the current time in milliseconds, however by storing it in your application scope, you can seed it once, then get highly random numbers by calling one of its
next methods to get the next pseudorandom, uniformly distributed value from its internal sequence. This sounds like a pretty good solution to me. It also eliminates the possibility of creating two random number generators at the exact same time. If two random number generators using the current time in milliseconds as seeds are instantiated simultaneous, the “random” numbers they generate will be identical. Oops.
In order to make the solution described above a little easier, I threw together a Randomize component which does the following:
- Creates a single instance of java.util.Random at the time it’s instantiated and continues to use it for generating additional random numbers. By putting the Random component in some persistent scope, you can ensure that the same instance of Random is being used, and hence your numbers will be highly random.
- Allows you to seed the random number generator, and happily accepts ints or longs, which means you can pass in the result of calling
getTickCount(). (Just don’t try to pass in a decimal of any kind — it won’t like that.)
- Allows you to easily set bounds for your random numbers using the
setMax()functions. These can be changed without having to create a new instance of the Randomize component.
- Provides a
nextBoolean()function for getting back random booleans.
If none of this made much sense to you, the bottom line is that if you need to generate a lot of highly random numbers, create an instance of this component, store it in a persistent scope, and call
next() on it whenever you need a random number.
The code is here:
<cfcomponent output="no"><cfset my = structNew()/><cfset my.rnd = createObject("java", "java.util.Random").init()/><cfset my.min = 0/><cfset my.max = 0/><cfset my.cnt = 0/><!-- Set the minimum and maximum. ---><cffunction name="setBounds" access="public" returnType="void" output="no"><cfargument name="min" type="numeric" required="true"/><cfargument name="max" type="numeric" required="true"/><cfset my.min = arguments.min/><cfset my.max = arguments.max/></cffunction><!--- Set the maximum. The minimum is automatically 0. ---><cffunction name="setMax" access="public" returnType="void" output="no"><cfargument name="max" type="numeric" required="true"/><cfset my.max = arguments.max/></cffunction><!--- Sets the seed. Accepts ints and longs (but no decimals). ---><cffunction name="setSeed" access="public" returnType="void" output="no"><cfargument name="seed" type="string" required="true"/><cfset var l = createObject("java", "java.lang.Long").init(arguments.seed)/><cfset my.rnd.setSeed(l.longValue())/></cffunction><!--- Returns the next highly random number. ---><cffunction name="next" access="public" returnType="numeric" output="no"><cfif my.min + my.max eq 0><cfreturn my.rnd.nextInt()/><cfelse><cfreturn (my.rnd.nextInt(javaCast("int",((my.max+1) - my.min))) + my.min)/></cfif></cffunction><!--- Returns a random boolean. ---><cffunction name="nextBoolean" access="public" returnType="boolean" output="no"><cfreturn my.rnd.nextBoolean()/></cffunction></cfcomponent>