You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by br...@apache.org on 2009/04/06 03:25:34 UTC

svn commit: r762194 - in /commons/proper/math/trunk/src: java/org/apache/commons/math/random/RandomDataImpl.java test/org/apache/commons/math/random/RandomDataTest.java

Author: brentworden
Date: Mon Apr  6 01:25:34 2009
New Revision: 762194

URL: http://svn.apache.org/viewvc?rev=762194&view=rev
Log:
MATH-197.  added rejection method to poisson random variates to help with large lamda values.

Modified:
    commons/proper/math/trunk/src/java/org/apache/commons/math/random/RandomDataImpl.java
    commons/proper/math/trunk/src/test/org/apache/commons/math/random/RandomDataTest.java

Modified: commons/proper/math/trunk/src/java/org/apache/commons/math/random/RandomDataImpl.java
URL: http://svn.apache.org/viewvc/commons/proper/math/trunk/src/java/org/apache/commons/math/random/RandomDataImpl.java?rev=762194&r1=762193&r2=762194&view=diff
==============================================================================
--- commons/proper/math/trunk/src/java/org/apache/commons/math/random/RandomDataImpl.java (original)
+++ commons/proper/math/trunk/src/java/org/apache/commons/math/random/RandomDataImpl.java Mon Apr  6 01:25:34 2009
@@ -24,32 +24,36 @@
 import java.security.NoSuchProviderException;
 import java.util.Collection;
 
+import org.apache.commons.math.util.MathUtils;
+
 /**
  * Implements the {@link RandomData} interface using a {@link RandomGenerator}
- * instance to generate non-secure data and a 
- * {@link java.security.SecureRandom} instance to provide data for the
- * <code>nextSecureXxx</code> methods.  If no <code>RandomGenerator</code>
- * is provided in the constructor, the default is to use a generator based on
- * {@link java.util.Random}.   To plug in a different implementation, 
- * either implement <code>RandomGenerator</code> directly or extend
- * {@link AbstractRandomGenerator}.
+ * instance to generate non-secure data and a {@link java.security.SecureRandom}
+ * instance to provide data for the <code>nextSecureXxx</code> methods. If no
+ * <code>RandomGenerator</code> is provided in the constructor, the default is
+ * to use a generator based on {@link java.util.Random}. To plug in a different
+ * implementation, either implement <code>RandomGenerator</code> directly or
+ * extend {@link AbstractRandomGenerator}.
  * <p>
- * Supports reseeding the underlying pseudo-random number generator (PRNG). 
- * The <code>SecurityProvider</code> and <code>Algorithm</code>
- * used by the <code>SecureRandom</code> instance can also be reset.</p>
+ * Supports reseeding the underlying pseudo-random number generator (PRNG). The
+ * <code>SecurityProvider</code> and <code>Algorithm</code> used by the
+ * <code>SecureRandom</code> instance can also be reset.
+ * </p>
  * <p>
  * For details on the default PRNGs, see {@link java.util.Random} and
- * {@link java.security.SecureRandom}.</p>
+ * {@link java.security.SecureRandom}.
+ * </p>
  * <p>
- * <strong>Usage Notes</strong>: <ul>
+ * <strong>Usage Notes</strong>:
+ * <ul>
  * <li>
  * Instance variables are used to maintain <code>RandomGenerator</code> and
- * <code>SecureRandom</code> instances used in data generation. Therefore,
- * to generate a random sequence of values or strings, you should use just
+ * <code>SecureRandom</code> instances used in data generation. Therefore, to
+ * generate a random sequence of values or strings, you should use just
  * <strong>one</strong> <code>RandomDataImpl</code> instance repeatedly.</li>
  * <li>
- * The "secure" methods are *much* slower.  These should be used only when a
- * cryptographically secure random sequence is required.  A secure random
+ * The "secure" methods are *much* slower. These should be used only when a
+ * cryptographically secure random sequence is required. A secure random
  * sequence is a sequence of pseudo-random values which, in addition to being
  * well-dispersed (so no subsequence of values is an any more likely than other
  * subsequence of the the same length), also has the additional property that
@@ -57,562 +61,689 @@
  * it any easier to predict subsequent values.</li>
  * <li>
  * When a new <code>RandomDataImpl</code> is created, the underlying random
- * number generators are <strong>not</strong> intialized.  If you do not
- * explicitly seed the default non-secure generator, it is seeded with the current time
- * in milliseconds on first use.  The same holds for the secure generator.  
- * If you provide a <code>RandomGenerator</code> to the constructor, however,
- * this generator is not reseeded by the constructor nor is it reseeded on
- * first use. </li>
+ * number generators are <strong>not</strong> intialized. If you do not
+ * explicitly seed the default non-secure generator, it is seeded with the
+ * current time in milliseconds on first use. The same holds for the secure
+ * generator. If you provide a <code>RandomGenerator</code> to the constructor,
+ * however, this generator is not reseeded by the constructor nor is it reseeded
+ * on first use.</li>
  * <li>
- * The <code>reSeed</code> and <code>reSeedSecure</code> methods delegate
- * to the corresponding methods on the underlying <code>RandomGenerator</code>
- * and<code>SecureRandom</code> instances.  Therefore, 
- * <code>reSeed(long)</code> fully resets the initial state of the non-secure
- * random number generator (so that reseeding with a specific value always
- * results in the same subsequent random sequence); whereas reSeedSecure(long)
- * does <strong>not</strong> reinitialize the secure random number generator
- * (so secure sequences started with calls to reseedSecure(long) won't be
- * identical).</li>
+ * The <code>reSeed</code> and <code>reSeedSecure</code> methods delegate to the
+ * corresponding methods on the underlying <code>RandomGenerator</code> and
+ * <code>SecureRandom</code> instances. Therefore, <code>reSeed(long)</code>
+ * fully resets the initial state of the non-secure random number generator (so
+ * that reseeding with a specific value always results in the same subsequent
+ * random sequence); whereas reSeedSecure(long) does <strong>not</strong>
+ * reinitialize the secure random number generator (so secure sequences started
+ * with calls to reseedSecure(long) won't be identical).</li>
  * <li>
  * This implementation is not synchronized.
- * </ul></p>
- *
- * @version $Revision$ $Date$
+ * </ul>
+ * </p>
+ * 
+ * @version $Revision$ $Date: 2008-11-23 08:27:09 -0600 (Sun, 23 Nov
+ *          2008) $
  */
 public class RandomDataImpl implements RandomData, Serializable {
 
-    /** Serializable version identifier */
-    private static final long serialVersionUID = -626730818244969716L;
+	/** Serializable version identifier */
+	private static final long serialVersionUID = -626730818244969716L;
 
-    /** underlying random number generator */
-    private RandomGenerator rand = null;
+	/** underlying random number generator */
+	private RandomGenerator rand = null;
 
-    /** underlying secure random number generator */
-    private SecureRandom secRand = null;
+	/** underlying secure random number generator */
+	private SecureRandom secRand = null;
 
-    /**
-     * Construct a RandomDataImpl.
-     */
-    public RandomDataImpl() {
-    }
-    
-    /**
-     * Construct a RandomDataImpl using the supplied {@link RandomGenerator}
-     * as the source of (non-secure) random data.
-     * 
-     * @param rand  the source of (non-secure) random data
-     * @since 1.1
-     */
-    public RandomDataImpl(RandomGenerator rand) {
-        super();
-        this.rand = rand;
-    }
-
-    /**
-     * {@inheritDoc}<p>
-     * <strong>Algorithm Description:</strong> hex strings are generated
-     * using a 2-step process. <ol>
-     * <li>
-     * len/2+1 binary bytes are generated using the underlying Random</li>
-     * <li>
-     * Each binary byte is translated into 2 hex digits</li></ol></p>
-     * 
-     * @param len the desired string length.
-     * @return the random string.
-     */
-    public String nextHexString(int len) {
-        if (len <= 0) {
-            throw new IllegalArgumentException("length must be positive");
-        }
-
-        //Get a random number generator
-        RandomGenerator ran = getRan();
-
-        //Initialize output buffer
-        StringBuffer outBuffer = new StringBuffer();
-
-        //Get int(len/2)+1 random bytes
-        byte[] randomBytes = new byte[(len / 2) + 1];
-        ran.nextBytes(randomBytes);
-
-        //Convert each byte to 2 hex digits
-        for (int i = 0; i < randomBytes.length; i++) {
-            Integer c = Integer.valueOf(randomBytes[i]);
-
-            /* Add 128 to byte value to make interval 0-255 before
-             * doing hex conversion.
-             * This guarantees <= 2 hex digits from toHexString()
-             * toHexString would otherwise add 2^32 to negative arguments.
-             */
-             String hex = Integer.toHexString(c.intValue() + 128);
-
-             // Make sure we add 2 hex digits for each byte
-             if (hex.length() == 1)  {
-                 hex = "0" + hex;
-             }
-             outBuffer.append(hex);
-        }
-        return outBuffer.toString().substring(0, len);
-    }
-
-    /**
-     * Generate a random int value uniformly distributed between
-     * <code>lower</code> and <code>upper</code>, inclusive.
-     * 
-     * @param lower the lower bound.
-     * @param upper the upper bound.
-     * @return the random integer.
-     */
-    public int nextInt(int lower, int upper) {
-        if (lower >= upper) {
-            throw new IllegalArgumentException
-                ("upper bound must be > lower bound");
-        }
-        RandomGenerator rand = getRan();
-        double r = rand.nextDouble();
-        return (int)((r * upper) + ((1.0 - r) * lower) + r);
-    }
-
-    /**
-     * Generate a random long value uniformly distributed between
-     * <code>lower</code> and <code>upper</code>, inclusive.
-     * 
-     * @param lower the lower bound.
-     * @param upper the upper bound.
-     * @return the random integer.
-     */
-    public long nextLong(long lower, long upper) {
-        if (lower >= upper) {
-            throw new IllegalArgumentException
-                ("upper bound must be > lower bound");
-        }
-        RandomGenerator rand = getRan();
-        double r = rand.nextDouble();
-        return (long)((r * upper) + ((1.0 - r) * lower) + r);
-    }
-
-     /**
-     * {@inheritDoc}<p>
-     * <strong>Algorithm Description:</strong> hex strings are generated in
-     * 40-byte segments using a 3-step process. <ol>
-     * <li>
-     * 20 random bytes are generated using the underlying
-     * <code>SecureRandom</code>.</li>
-     * <li>
-     * SHA-1 hash is applied to yield a 20-byte binary digest.</li>
-     * <li>
-     * Each byte of the binary digest is converted to 2 hex digits.</li></ol>
-     * </p>
-     *
-     * @param len the length of the generated string
-     * @return the random string
-     */
-    public String nextSecureHexString(int len) {
-        if (len <= 0) {
-            throw new IllegalArgumentException("length must be positive");
-        }
-
-       // Get SecureRandom and setup Digest provider
-       SecureRandom secRan = getSecRan();
-       MessageDigest alg = null;
-       try {
-            alg = MessageDigest.getInstance("SHA-1");
-       } catch (NoSuchAlgorithmException ex) {
-           return null; // gulp FIXME? -- this *should* never fail.
-       }
-       alg.reset();
-
-       //Compute number of iterations required (40 bytes each)
-       int numIter = (len / 40) + 1;
-
-       StringBuffer outBuffer = new StringBuffer();
-       for (int iter = 1; iter < numIter + 1; iter++) {
-            byte[] randomBytes = new byte[40];
-            secRan.nextBytes(randomBytes);
-            alg.update(randomBytes);
-
-            //Compute hash -- will create 20-byte binary hash
-            byte hash[] = alg.digest();
-
-            //Loop over the hash, converting each byte to 2 hex digits
-            for (int i = 0; i < hash.length; i++) {
-                Integer c = Integer.valueOf(hash[i]);
-
-                /* Add 128 to byte value to make interval 0-255
-                 * This guarantees <= 2 hex digits from toHexString()
-                 * toHexString would otherwise add 2^32 to negative
-                 * arguments
-                 */
-                String hex = Integer.toHexString(c.intValue() + 128);
-
-               //Keep strings uniform length -- guarantees 40 bytes
-                if (hex.length() == 1) {
-                    hex = "0" + hex;
-                }
-               outBuffer.append(hex);
-            }
-        }
-        return outBuffer.toString().substring(0, len);
-    }
-
-    /**
-     * Generate a random int value uniformly distributed between
-     * <code>lower</code> and <code>upper</code>, inclusive.  This algorithm
-     * uses a secure random number generator.
-     * 
-     * @param lower the lower bound.
-     * @param upper the upper bound.
-     * @return the random integer.
-     */
-    public int nextSecureInt(int lower, int upper) {
-          if (lower >= upper) {
-              throw new IllegalArgumentException
-                ("lower bound must be < upper bound");
-          }
-          SecureRandom sec = getSecRan();
-          return lower + (int) (sec.nextDouble() * (upper - lower + 1));
-    }
-
-    /**
-     * Generate a random long value uniformly distributed between
-     * <code>lower</code> and <code>upper</code>, inclusive.  This algorithm
-     * uses a secure random number generator.
-     * 
-     * @param lower the lower bound.
-     * @param upper the upper bound.
-     * @return the random integer.
-     */
-    public long nextSecureLong(long lower, long upper) {
-        if (lower >= upper) {
-            throw new IllegalArgumentException
-            ("lower bound must be < upper bound");
-        }
-        SecureRandom sec = getSecRan();
-        return lower + (long) (sec.nextDouble() * (upper - lower + 1));
-    }
-
-    /**
-     * {@inheritDoc}
-     * <p>
-     * <strong>Algorithm Description</strong>:
-     * Uses simulation of a Poisson process using Uniform deviates, as
-     * described
-     * <a href="http://irmi.epfl.ch/cmos/Pmmi/interactive/rng7.htm">
-     * here.</a></p>
-     * <p>
-     * The Poisson process (and hence value returned) is bounded by 
-     * 1000 * mean.</p>
-     * 
-     * @param mean mean of the Poisson distribution.
-     * @return the random Poisson value.
-     */
-    public long nextPoisson(double mean) {
-        if (mean <= 0) {
-            throw new IllegalArgumentException("Poisson mean must be > 0");
-        }
-        double p = Math.exp(-mean);
-        long n = 0;
-        double r = 1.0d;
-        double rnd = 1.0d;
-        RandomGenerator rand = getRan();
-        while (n < 1000 * mean) {
-            rnd = rand.nextDouble();
-            r = r * rnd;
-            if (r >= p) {
-                n++;
-            } else {
-                return n;
-            }
-        }
-        return n;
-    }
-
-    /**
-     * Generate a random value from a Normal (a.k.a. Gaussian) distribution
-     * with the given mean, <code>mu</code> and the given standard deviation,
-     * <code>sigma</code>.
-     * 
-     * @param mu the mean of the distribution
-     * @param sigma the standard deviation of the distribution
-     * @return the random Normal value
-     */
-    public double nextGaussian(double mu, double sigma) {
-        if (sigma <= 0) {
-            throw new IllegalArgumentException("Gaussian std dev must be > 0");
-        }
-        RandomGenerator rand = getRan();
-        return sigma * rand.nextGaussian() + mu;
-    }
-
-    /**
-     * Returns a random value from an Exponential distribution with the given
-     * mean.
-     * <p>
-     * <strong>Algorithm Description</strong>:  Uses the
-     * <a href="http://www.jesus.ox.ac.uk/~clifford/a5/chap1/node5.html">
-     * Inversion Method</a> to generate exponentially distributed random values
-     * from uniform deviates.</p>
-     * 
-     * @param mean the mean of the distribution
-     * @return the random Exponential value
-     */
-    public double nextExponential(double mean)  {
-        if (mean < 0.0)  {
-            throw new IllegalArgumentException
-                ("Exponential mean must be >= 0");
-        }
-        RandomGenerator rand = getRan();
-        double unif = rand.nextDouble();
-        while (unif == 0.0d) {
-            unif = rand.nextDouble();
-        }
-        return -mean * Math.log(unif);
-    }
-
-    /**
-     * {@inheritDoc}<p>
-     * <strong>Algorithm Description</strong>: scales the output of
-     * Random.nextDouble(), but rejects 0 values (i.e., will generate another
-     * random double if Random.nextDouble() returns 0).
-     * This is necessary to provide a symmetric output interval
-     * (both endpoints excluded).</p>
-     * 
-     * @param lower the lower bound.
-     * @param upper the upper bound.
-     * @return a uniformly distributed random value from the interval (lower, upper)
-     */
-    public double nextUniform(double lower, double upper) {
-        if (lower >= upper) {
-            throw new IllegalArgumentException
-            ("lower bound must be < upper bound");
-        }
-        RandomGenerator rand = getRan();
-
-        // ensure nextDouble() isn't 0.0
-        double u = rand.nextDouble();
-        while(u <= 0.0){
-            u = rand.nextDouble();
-        }
-
-        return lower + u * (upper - lower);
-    }
-
-    /**
-     * Returns the RandomGenerator used to generate non-secure
-     * random data.
-     * <p>
-     * Creates and initializes a default generator if null.</p>
-     *
-     * @return the Random used to generate random data
-     * @since 1.1
-     */
-    private RandomGenerator getRan() {
-        if (rand == null) {
-            rand = new JDKRandomGenerator();
-            rand.setSeed(System.currentTimeMillis());
-        }
-        return rand;
-    }
-
-    /**
-     * Returns the SecureRandom used to generate secure random data.
-     * <p>
-     * Creates and initializes if null.</p>
-     *
-     * @return the SecureRandom used to generate secure random data
-     */
-    private SecureRandom getSecRan() {
-        if (secRand == null) {
-            secRand = new SecureRandom();
-            secRand.setSeed(System.currentTimeMillis());
-        }
-        return secRand;
-    }
-
-    /**
-     * Reseeds the random number generator with the supplied seed.
-     * <p>
-     * Will create and initialize if null.</p>
-     *
-     * @param seed the seed value to use
-     */
-    public void reSeed(long seed) {
-        if (rand == null) {
-            rand = new JDKRandomGenerator();
-        }
-        rand.setSeed(seed);
-    }
-
-    /**
-     * Reseeds the secure random number generator with the current time
-     * in milliseconds.
-     * <p>
-     * Will create and initialize if null.</p>
-     */
-    public void reSeedSecure() {
-        if (secRand == null) {
-            secRand = new SecureRandom();
-        }
-        secRand.setSeed(System.currentTimeMillis());
-    }
-
-    /**
-     * Reseeds the secure random number generator with the supplied seed.
-     * <p>
-     * Will create and initialize if null.</p>
-     *
-     * @param seed the seed value to use
-     */
-    public void reSeedSecure(long seed) {
-        if (secRand == null) {
-            secRand = new SecureRandom();
-        }
-        secRand.setSeed(seed);
-    }
-
-    /**
-     * Reseeds the random number generator with the current time
-     * in milliseconds.
-     */
-    public void reSeed() {
-        if (rand == null) {
-            rand = new JDKRandomGenerator();
-        }
-        rand.setSeed(System.currentTimeMillis());
-    }
-
-    /**
-     * Sets the PRNG algorithm for the underlying SecureRandom instance
-     * using the Security Provider API.  The Security Provider API is defined in
-     * <a href="http://java.sun.com/j2se/1.3/docs/guide/security/CryptoSpec.html#AppA">
-     * Java Cryptography Architecture API Specification & Reference.</a>
-     * <p>
-     * <strong>USAGE NOTE:</strong> This method carries <i>significant</i>
-     * overhead and may take several seconds to execute.
-     * </p>
-     *
-     * @param algorithm the name of the PRNG algorithm
-     * @param provider the name of the provider
-     * @throws NoSuchAlgorithmException if the specified algorithm
-     * is not available
-     * @throws NoSuchProviderException if the specified provider
-     * is not installed
-     */
-    public void setSecureAlgorithm(String algorithm, String provider)
-        throws NoSuchAlgorithmException, NoSuchProviderException {
-        secRand = SecureRandom.getInstance(algorithm, provider);
-    }
-
-    /**
-     * Generates an integer array of length <code>k</code> whose entries
-     * are selected randomly, without repetition, from the integers
-     * <code>0 through n-1</code> (inclusive).
-     * <p>
-     * Generated arrays represent permutations
-     * of <code>n</code> taken <code>k</code> at a time.</p>
-     * <p>
-     * <strong>Preconditions:</strong><ul>
-     * <li> <code>k <= n</code></li>
-     * <li> <code>n > 0</code> </li>
-     * </ul>
-     * If the preconditions are not met, an IllegalArgumentException is
-     * thrown.</p>
-     * <p>
-     * Uses a 2-cycle permutation shuffle. The shuffling process is described
-     * <a href="http://www.maths.abdn.ac.uk/~igc/tch/mx4002/notes/node83.html">
-     * here</a>.</p>
-     * 
-     * @param n domain of the permutation (must be positive)
-     * @param k size of the permutation (must satisfy 0 < k <= n).
-     * @return the random permutation as an int array
-     */
-    public int[] nextPermutation(int n, int k) {
-        if (k > n) {
-            throw new IllegalArgumentException
-                ("permutation k exceeds n");
-        }
-        if (k == 0) {
-            throw new IllegalArgumentException
-                ("permutation k must be > 0");
-        }
-
-        int[] index = getNatural(n);
-        shuffle(index, n - k);
-        int[] result = new int[k];
-        for (int i = 0; i < k; i++) {
-            result[i] = index[n - i - 1];
-        }
-
-        return result;
-    }
-
-    /**
-     * Uses a 2-cycle permutation shuffle to generate a random permutation.
-     * <strong>Algorithm Description</strong>: Uses a 2-cycle permutation
-     * shuffle to generate a random permutation of <code>c.size()</code> and
-     * then returns the elements whose indexes correspond to the elements of
-     * the generated permutation.
-     * This technique is described, and proven to generate random samples,
-     * <a href="http://www.maths.abdn.ac.uk/~igc/tch/mx4002/notes/node83.html">
-     * here</a>
-     * @param c Collection to sample from.
-     * @param k sample size.
-     * @return the random sample.
-     */
-    public Object[] nextSample(Collection<?> c, int k) {
-        int len = c.size();
-        if (k > len) {
-            throw new IllegalArgumentException
-                ("sample size exceeds collection size");
-        }
-        if (k == 0) {
-            throw new IllegalArgumentException
-                ("sample size must be > 0");
-        }
-
-       Object[] objects = c.toArray();
-       int[] index = nextPermutation(len, k);
-       Object[] result = new Object[k];
-       for (int i = 0; i < k; i++) {
-           result[i] = objects[index[i]];
-       }
-       return result;
-    }
-
-    //------------------------Private methods----------------------------------
-
-    /**
-     * Uses a 2-cycle permutation shuffle to randomly re-order the last elements
-     * of list.
-     *
-     * @param list list to be shuffled
-     * @param end element past which shuffling begins
-     */
-    private void shuffle(int[] list, int end) {
-        int target = 0;
-        for (int i = list.length - 1 ; i >= end; i--) {
-            if (i == 0) {
-                target = 0;
-            } else {
-                target = nextInt(0, i);
-            }
-            int temp = list[target];
-            list[target] = list[i];
-            list[i] = temp;
-        }
-    }
-
-    /**
-     * Returns an array representing n.
-     *
-     * @param n the natural number to represent
-     * @return array with entries = elements of n
-     */
-    private int[] getNatural(int n) {
-        int[] natural = new int[n];
-        for (int i = 0; i < n; i++) {
-            natural[i] = i;
-        }
-        return natural;
-    }
+	/**
+	 * Construct a RandomDataImpl.
+	 */
+	public RandomDataImpl() {
+	}
+
+	/**
+	 * Construct a RandomDataImpl using the supplied {@link RandomGenerator} as
+	 * the source of (non-secure) random data.
+	 * 
+	 * @param rand
+	 *            the source of (non-secure) random data
+	 * @since 1.1
+	 */
+	public RandomDataImpl(RandomGenerator rand) {
+		super();
+		this.rand = rand;
+	}
+
+	/**
+	 * {@inheritDoc}
+	 * <p>
+	 * <strong>Algorithm Description:</strong> hex strings are generated using a
+	 * 2-step process.
+	 * <ol>
+	 * <li>
+	 * len/2+1 binary bytes are generated using the underlying Random</li>
+	 * <li>
+	 * Each binary byte is translated into 2 hex digits</li>
+	 * </ol>
+	 * </p>
+	 * 
+	 * @param len
+	 *            the desired string length.
+	 * @return the random string.
+	 */
+	public String nextHexString(int len) {
+		if (len <= 0) {
+			throw new IllegalArgumentException("length must be positive");
+		}
+
+		// Get a random number generator
+		RandomGenerator ran = getRan();
+
+		// Initialize output buffer
+		StringBuffer outBuffer = new StringBuffer();
+
+		// Get int(len/2)+1 random bytes
+		byte[] randomBytes = new byte[(len / 2) + 1];
+		ran.nextBytes(randomBytes);
+
+		// Convert each byte to 2 hex digits
+		for (int i = 0; i < randomBytes.length; i++) {
+			Integer c = Integer.valueOf(randomBytes[i]);
+
+			/*
+			 * Add 128 to byte value to make interval 0-255 before doing hex
+			 * conversion. This guarantees <= 2 hex digits from toHexString()
+			 * toHexString would otherwise add 2^32 to negative arguments.
+			 */
+			String hex = Integer.toHexString(c.intValue() + 128);
+
+			// Make sure we add 2 hex digits for each byte
+			if (hex.length() == 1) {
+				hex = "0" + hex;
+			}
+			outBuffer.append(hex);
+		}
+		return outBuffer.toString().substring(0, len);
+	}
+
+	/**
+	 * Generate a random int value uniformly distributed between
+	 * <code>lower</code> and <code>upper</code>, inclusive.
+	 * 
+	 * @param lower
+	 *            the lower bound.
+	 * @param upper
+	 *            the upper bound.
+	 * @return the random integer.
+	 */
+	public int nextInt(int lower, int upper) {
+		if (lower >= upper) {
+			throw new IllegalArgumentException(
+					"upper bound must be > lower bound");
+		}
+		RandomGenerator rand = getRan();
+		double r = rand.nextDouble();
+		return (int) ((r * upper) + ((1.0 - r) * lower) + r);
+	}
+
+	/**
+	 * Generate a random long value uniformly distributed between
+	 * <code>lower</code> and <code>upper</code>, inclusive.
+	 * 
+	 * @param lower
+	 *            the lower bound.
+	 * @param upper
+	 *            the upper bound.
+	 * @return the random integer.
+	 */
+	public long nextLong(long lower, long upper) {
+		if (lower >= upper) {
+			throw new IllegalArgumentException(
+					"upper bound must be > lower bound");
+		}
+		RandomGenerator rand = getRan();
+		double r = rand.nextDouble();
+		return (long) ((r * upper) + ((1.0 - r) * lower) + r);
+	}
+
+	/**
+	 * {@inheritDoc}
+	 * <p>
+	 * <strong>Algorithm Description:</strong> hex strings are generated in
+	 * 40-byte segments using a 3-step process.
+	 * <ol>
+	 * <li>
+	 * 20 random bytes are generated using the underlying
+	 * <code>SecureRandom</code>.</li>
+	 * <li>
+	 * SHA-1 hash is applied to yield a 20-byte binary digest.</li>
+	 * <li>
+	 * Each byte of the binary digest is converted to 2 hex digits.</li>
+	 * </ol>
+	 * </p>
+	 * 
+	 * @param len
+	 *            the length of the generated string
+	 * @return the random string
+	 */
+	public String nextSecureHexString(int len) {
+		if (len <= 0) {
+			throw new IllegalArgumentException("length must be positive");
+		}
+
+		// Get SecureRandom and setup Digest provider
+		SecureRandom secRan = getSecRan();
+		MessageDigest alg = null;
+		try {
+			alg = MessageDigest.getInstance("SHA-1");
+		} catch (NoSuchAlgorithmException ex) {
+			return null; // gulp FIXME? -- this *should* never fail.
+		}
+		alg.reset();
+
+		// Compute number of iterations required (40 bytes each)
+		int numIter = (len / 40) + 1;
+
+		StringBuffer outBuffer = new StringBuffer();
+		for (int iter = 1; iter < numIter + 1; iter++) {
+			byte[] randomBytes = new byte[40];
+			secRan.nextBytes(randomBytes);
+			alg.update(randomBytes);
+
+			// Compute hash -- will create 20-byte binary hash
+			byte hash[] = alg.digest();
+
+			// Loop over the hash, converting each byte to 2 hex digits
+			for (int i = 0; i < hash.length; i++) {
+				Integer c = Integer.valueOf(hash[i]);
+
+				/*
+				 * Add 128 to byte value to make interval 0-255 This guarantees
+				 * <= 2 hex digits from toHexString() toHexString would
+				 * otherwise add 2^32 to negative arguments
+				 */
+				String hex = Integer.toHexString(c.intValue() + 128);
+
+				// Keep strings uniform length -- guarantees 40 bytes
+				if (hex.length() == 1) {
+					hex = "0" + hex;
+				}
+				outBuffer.append(hex);
+			}
+		}
+		return outBuffer.toString().substring(0, len);
+	}
+
+	/**
+	 * Generate a random int value uniformly distributed between
+	 * <code>lower</code> and <code>upper</code>, inclusive. This algorithm uses
+	 * a secure random number generator.
+	 * 
+	 * @param lower
+	 *            the lower bound.
+	 * @param upper
+	 *            the upper bound.
+	 * @return the random integer.
+	 */
+	public int nextSecureInt(int lower, int upper) {
+		if (lower >= upper) {
+			throw new IllegalArgumentException(
+					"lower bound must be < upper bound");
+		}
+		SecureRandom sec = getSecRan();
+		return lower + (int) (sec.nextDouble() * (upper - lower + 1));
+	}
+
+	/**
+	 * Generate a random long value uniformly distributed between
+	 * <code>lower</code> and <code>upper</code>, inclusive. This algorithm uses
+	 * a secure random number generator.
+	 * 
+	 * @param lower
+	 *            the lower bound.
+	 * @param upper
+	 *            the upper bound.
+	 * @return the random integer.
+	 */
+	public long nextSecureLong(long lower, long upper) {
+		if (lower >= upper) {
+			throw new IllegalArgumentException(
+					"lower bound must be < upper bound");
+		}
+		SecureRandom sec = getSecRan();
+		return lower + (long) (sec.nextDouble() * (upper - lower + 1));
+	}
+
+	/**
+	 * {@inheritDoc}
+	 * <p>
+	 * <strong>Algorithm Description</strong>: For small means, uses simulation
+	 * of a Poisson process using Uniform deviates, as described <a
+	 * href="http://irmi.epfl.ch/cmos/Pmmi/interactive/rng7.htm"> here.</a>
+	 * </p>
+	 * <p>
+	 * The Poisson process (and hence value returned) is bounded by 1000 * mean.
+	 * </p>
+	 * 
+	 * <p>
+	 * For large means, uses a reject method as described in <a
+	 * href="http://cg.scs.carleton.ca/~luc/rnbookindex.html">Non-Uniform Random
+	 * Variate Generation</a>
+	 * </p>
+	 * 
+	 * <p>
+	 * References:
+	 * <ul>
+	 * <li>Devroye, Luc. (1986). <i>Non-Uniform Random Variate Generation</i>.
+	 * New York, NY. Springer-Verlag</li>
+	 * </ul>
+	 * </p>
+	 * 
+	 * @param mean
+	 *            mean of the Poisson distribution.
+	 * @return the random Poisson value.
+	 */
+	public long nextPoisson(double mean) {
+		if (mean <= 0) {
+			throw new IllegalArgumentException("Poisson mean must be > 0");
+		}
+
+		RandomGenerator rand = getRan();
+
+		double pivot = 6.0;
+		if (mean < pivot) {
+			double p = Math.exp(-mean);
+			long n = 0;
+			double r = 1.0d;
+			double rnd = 1.0d;
+
+			while (n < 1000 * mean) {
+				rnd = rand.nextDouble();
+				r = r * rnd;
+				if (r >= p) {
+					n++;
+				} else {
+					return n;
+				}
+			}
+			return n;
+		} else {
+			double mu = Math.floor(mean);
+			double delta = Math.floor(pivot + (mu - pivot) / 2.0); // integer
+			// between 6
+			// and mean
+			double mu2delta = 2.0 * mu + delta;
+			double muDeltaHalf = mu + delta / 2.0;
+			double logMeanMu = Math.log(mean / mu);
+
+			double muFactorialLog = MathUtils.factorialLog((int) mu);
+
+			double c1 = Math.sqrt(Math.PI * mu / 2.0);
+			double c2 = c1
+					+ Math.sqrt(Math.PI * muDeltaHalf
+							/ (2.0 * Math.exp(1.0 / mu2delta)));
+			double c3 = c2 + 2.0;
+			double c4 = c3 + Math.exp(1.0 / 78.0);
+			double c = c4 + 2.0 / delta * mu2delta
+					* Math.exp(-delta / mu2delta * (1.0 + delta / 2.0));
+
+			double y = 0.0;
+			double x = 0.0;
+			double w = Double.POSITIVE_INFINITY;
+
+			boolean accept = false;
+			while (!accept) {
+				double u = nextUniform(0.0, c);
+				double e = nextExponential(mean);
+
+				if (u <= c1) {
+					double z = nextGaussian(0.0, 1.0);
+					y = -Math.abs(z) * Math.sqrt(mu) - 1.0;
+					x = Math.floor(y);
+					w = -z * z / 2.0 - e - x * logMeanMu;
+					if (x < -mu) {
+						w = Double.POSITIVE_INFINITY;
+					}
+				} else if (c1 < u && u <= c2) {
+					double z = nextGaussian(0.0, 1.0);
+					y = 1.0 + Math.abs(z) * Math.sqrt(muDeltaHalf);
+					x = Math.ceil(y);
+					w = (-y * y + 2.0 * y) / mu2delta - e - x * logMeanMu;
+					if (x > delta) {
+						w = Double.POSITIVE_INFINITY;
+					}
+				} else if (c2 < u && u <= c3) {
+					x = 0.0;
+					w = -e;
+				} else if (c3 < u && u <= c4) {
+					x = 1.0;
+					w = -e - logMeanMu;
+				} else if (c4 < u) {
+					double v = nextExponential(mean);
+					y = delta + v * 2.0 / delta * mu2delta;
+					x = Math.ceil(y);
+					w = -delta / mu2delta * (1.0 + y / 2.0) - e - x * logMeanMu;
+				}
+				accept = (w <= x * Math.log(mu)
+						- MathUtils.factorialLog((int) (mu + x))
+						/ muFactorialLog);
+			}
+			// cast to long is acceptable because both x and mu are whole
+			// numbers.
+			return (long) (x + mu);
+		}
+	}
+
+	/**
+	 * Generate a random value from a Normal (a.k.a. Gaussian) distribution with
+	 * the given mean, <code>mu</code> and the given standard deviation,
+	 * <code>sigma</code>.
+	 * 
+	 * @param mu
+	 *            the mean of the distribution
+	 * @param sigma
+	 *            the standard deviation of the distribution
+	 * @return the random Normal value
+	 */
+	public double nextGaussian(double mu, double sigma) {
+		if (sigma <= 0) {
+			throw new IllegalArgumentException("Gaussian std dev must be > 0");
+		}
+		RandomGenerator rand = getRan();
+		return sigma * rand.nextGaussian() + mu;
+	}
+
+	/**
+	 * Returns a random value from an Exponential distribution with the given
+	 * mean.
+	 * <p>
+	 * <strong>Algorithm Description</strong>: Uses the <a
+	 * href="http://www.jesus.ox.ac.uk/~clifford/a5/chap1/node5.html"> Inversion
+	 * Method</a> to generate exponentially distributed random values from
+	 * uniform deviates.
+	 * </p>
+	 * 
+	 * @param mean
+	 *            the mean of the distribution
+	 * @return the random Exponential value
+	 */
+	public double nextExponential(double mean) {
+		if (mean < 0.0) {
+			throw new IllegalArgumentException("Exponential mean must be >= 0");
+		}
+		RandomGenerator rand = getRan();
+		double unif = rand.nextDouble();
+		while (unif == 0.0d) {
+			unif = rand.nextDouble();
+		}
+		return -mean * Math.log(unif);
+	}
+
+	/**
+	 * {@inheritDoc}
+	 * <p>
+	 * <strong>Algorithm Description</strong>: scales the output of
+	 * Random.nextDouble(), but rejects 0 values (i.e., will generate another
+	 * random double if Random.nextDouble() returns 0). This is necessary to
+	 * provide a symmetric output interval (both endpoints excluded).
+	 * </p>
+	 * 
+	 * @param lower
+	 *            the lower bound.
+	 * @param upper
+	 *            the upper bound.
+	 * @return a uniformly distributed random value from the interval (lower,
+	 *         upper)
+	 */
+	public double nextUniform(double lower, double upper) {
+		if (lower >= upper) {
+			throw new IllegalArgumentException(
+					"lower bound must be < upper bound");
+		}
+		RandomGenerator rand = getRan();
+
+		// ensure nextDouble() isn't 0.0
+		double u = rand.nextDouble();
+		while (u <= 0.0) {
+			u = rand.nextDouble();
+		}
+
+		return lower + u * (upper - lower);
+	}
+
+	/**
+	 * Returns the RandomGenerator used to generate non-secure random data.
+	 * <p>
+	 * Creates and initializes a default generator if null.
+	 * </p>
+	 * 
+	 * @return the Random used to generate random data
+	 * @since 1.1
+	 */
+	private RandomGenerator getRan() {
+		if (rand == null) {
+			rand = new JDKRandomGenerator();
+			rand.setSeed(System.currentTimeMillis());
+		}
+		return rand;
+	}
+
+	/**
+	 * Returns the SecureRandom used to generate secure random data.
+	 * <p>
+	 * Creates and initializes if null.
+	 * </p>
+	 * 
+	 * @return the SecureRandom used to generate secure random data
+	 */
+	private SecureRandom getSecRan() {
+		if (secRand == null) {
+			secRand = new SecureRandom();
+			secRand.setSeed(System.currentTimeMillis());
+		}
+		return secRand;
+	}
+
+	/**
+	 * Reseeds the random number generator with the supplied seed.
+	 * <p>
+	 * Will create and initialize if null.
+	 * </p>
+	 * 
+	 * @param seed
+	 *            the seed value to use
+	 */
+	public void reSeed(long seed) {
+		if (rand == null) {
+			rand = new JDKRandomGenerator();
+		}
+		rand.setSeed(seed);
+	}
+
+	/**
+	 * Reseeds the secure random number generator with the current time in
+	 * milliseconds.
+	 * <p>
+	 * Will create and initialize if null.
+	 * </p>
+	 */
+	public void reSeedSecure() {
+		if (secRand == null) {
+			secRand = new SecureRandom();
+		}
+		secRand.setSeed(System.currentTimeMillis());
+	}
+
+	/**
+	 * Reseeds the secure random number generator with the supplied seed.
+	 * <p>
+	 * Will create and initialize if null.
+	 * </p>
+	 * 
+	 * @param seed
+	 *            the seed value to use
+	 */
+	public void reSeedSecure(long seed) {
+		if (secRand == null) {
+			secRand = new SecureRandom();
+		}
+		secRand.setSeed(seed);
+	}
+
+	/**
+	 * Reseeds the random number generator with the current time in
+	 * milliseconds.
+	 */
+	public void reSeed() {
+		if (rand == null) {
+			rand = new JDKRandomGenerator();
+		}
+		rand.setSeed(System.currentTimeMillis());
+	}
+
+	/**
+	 * Sets the PRNG algorithm for the underlying SecureRandom instance using
+	 * the Security Provider API. The Security Provider API is defined in <a
+	 * href =
+	 * "http://java.sun.com/j2se/1.3/docs/guide/security/CryptoSpec.html#AppA">
+	 * Java Cryptography Architecture API Specification & Reference.</a>
+	 * <p>
+	 * <strong>USAGE NOTE:</strong> This method carries <i>significant</i>
+	 * overhead and may take several seconds to execute.
+	 * </p>
+	 * 
+	 * @param algorithm
+	 *            the name of the PRNG algorithm
+	 * @param provider
+	 *            the name of the provider
+	 * @throws NoSuchAlgorithmException
+	 *             if the specified algorithm is not available
+	 * @throws NoSuchProviderException
+	 *             if the specified provider is not installed
+	 */
+	public void setSecureAlgorithm(String algorithm, String provider)
+			throws NoSuchAlgorithmException, NoSuchProviderException {
+		secRand = SecureRandom.getInstance(algorithm, provider);
+	}
+
+	/**
+	 * Generates an integer array of length <code>k</code> whose entries are
+	 * selected randomly, without repetition, from the integers
+	 * <code>0 through n-1</code> (inclusive).
+	 * <p>
+	 * Generated arrays represent permutations of <code>n</code> taken
+	 * <code>k</code> at a time.
+	 * </p>
+	 * <p>
+	 * <strong>Preconditions:</strong>
+	 * <ul>
+	 * <li> <code>k <= n</code></li>
+	 * <li> <code>n > 0</code></li>
+	 * </ul>
+	 * If the preconditions are not met, an IllegalArgumentException is thrown.
+	 * </p>
+	 * <p>
+	 * Uses a 2-cycle permutation shuffle. The shuffling process is described <a
+	 * href="http://www.maths.abdn.ac.uk/~igc/tch/mx4002/notes/node83.html">
+	 * here</a>.
+	 * </p>
+	 * 
+	 * @param n
+	 *            domain of the permutation (must be positive)
+	 * @param k
+	 *            size of the permutation (must satisfy 0 < k <= n).
+	 * @return the random permutation as an int array
+	 */
+	public int[] nextPermutation(int n, int k) {
+		if (k > n) {
+			throw new IllegalArgumentException("permutation k exceeds n");
+		}
+		if (k == 0) {
+			throw new IllegalArgumentException("permutation k must be > 0");
+		}
+
+		int[] index = getNatural(n);
+		shuffle(index, n - k);
+		int[] result = new int[k];
+		for (int i = 0; i < k; i++) {
+			result[i] = index[n - i - 1];
+		}
+
+		return result;
+	}
+
+	/**
+	 * Uses a 2-cycle permutation shuffle to generate a random permutation.
+	 * <strong>Algorithm Description</strong>: Uses a 2-cycle permutation
+	 * shuffle to generate a random permutation of <code>c.size()</code> and
+	 * then returns the elements whose indexes correspond to the elements of the
+	 * generated permutation. This technique is described, and proven to
+	 * generate random samples, <a
+	 * href="http://www.maths.abdn.ac.uk/~igc/tch/mx4002/notes/node83.html">
+	 * here</a>
+	 * 
+	 * @param c
+	 *            Collection to sample from.
+	 * @param k
+	 *            sample size.
+	 * @return the random sample.
+	 */
+	public Object[] nextSample(Collection<?> c, int k) {
+		int len = c.size();
+		if (k > len) {
+			throw new IllegalArgumentException(
+					"sample size exceeds collection size");
+		}
+		if (k == 0) {
+			throw new IllegalArgumentException("sample size must be > 0");
+		}
+
+		Object[] objects = c.toArray();
+		int[] index = nextPermutation(len, k);
+		Object[] result = new Object[k];
+		for (int i = 0; i < k; i++) {
+			result[i] = objects[index[i]];
+		}
+		return result;
+	}
+
+	// ------------------------Private methods----------------------------------
+
+	/**
+	 * Uses a 2-cycle permutation shuffle to randomly re-order the last elements
+	 * of list.
+	 * 
+	 * @param list
+	 *            list to be shuffled
+	 * @param end
+	 *            element past which shuffling begins
+	 */
+	private void shuffle(int[] list, int end) {
+		int target = 0;
+		for (int i = list.length - 1; i >= end; i--) {
+			if (i == 0) {
+				target = 0;
+			} else {
+				target = nextInt(0, i);
+			}
+			int temp = list[target];
+			list[target] = list[i];
+			list[i] = temp;
+		}
+	}
+
+	/**
+	 * Returns an array representing n.
+	 * 
+	 * @param n
+	 *            the natural number to represent
+	 * @return array with entries = elements of n
+	 */
+	private int[] getNatural(int n) {
+		int[] natural = new int[n];
+		for (int i = 0; i < n; i++) {
+			natural[i] = i;
+		}
+		return natural;
+	}
 }

Modified: commons/proper/math/trunk/src/test/org/apache/commons/math/random/RandomDataTest.java
URL: http://svn.apache.org/viewvc/commons/proper/math/trunk/src/test/org/apache/commons/math/random/RandomDataTest.java?rev=762194&r1=762193&r2=762194&view=diff
==============================================================================
--- commons/proper/math/trunk/src/test/org/apache/commons/math/random/RandomDataTest.java (original)
+++ commons/proper/math/trunk/src/test/org/apache/commons/math/random/RandomDataTest.java Mon Apr  6 01:25:34 2009
@@ -19,6 +19,7 @@
 import junit.framework.Test;
 import junit.framework.TestSuite;
 import java.util.HashSet;
+import java.util.Iterator;
 
 import org.apache.commons.math.RetryTestCase;
 import org.apache.commons.math.stat.Frequency;
@@ -27,589 +28,599 @@
 
 /**
  * Test cases for the RandomData class.
- *
- * @version $Revision$ $Date$
+ * 
+ * @version $Revision$ $Date: 2009-04-05 11:55:59 -0500 (Sun, 05 Apr
+ *          2009) $
  */
 
 public class RandomDataTest extends RetryTestCase {
 
-    public RandomDataTest(String name) {
-        super(name);
-        randomData = new RandomDataImpl();
-    }
-
-    protected long smallSampleSize = 1000;
-    protected double[] expected = {250,250,250,250};
-    protected int largeSampleSize = 10000;
-    private String[] hex = 
-        {"0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f"}; 
-    protected RandomDataImpl randomData = null; 
-    protected ChiSquareTestImpl testStatistic = new ChiSquareTestImpl();
-    
-    public static Test suite() {
-        TestSuite suite = new TestSuite(RandomDataTest.class);
-        suite.setName("RandomData Tests");
-        return suite;
-    }
-
-    public void testNextIntExtremeValues() {
-        int x = randomData.nextInt(Integer.MIN_VALUE, Integer.MAX_VALUE);
-        int y = randomData.nextInt(Integer.MIN_VALUE, Integer.MAX_VALUE);
-        assertFalse(x == y);
-    }
-
-    public void testNextLongExtremeValues() {
-        long x = randomData.nextLong(Long.MIN_VALUE, Long.MAX_VALUE);
-        long y = randomData.nextLong(Long.MIN_VALUE, Long.MAX_VALUE);
-        assertFalse(x == y);
-    }
-    
-    /** test dispersion and failure modes for nextInt() */
-    public void testNextInt() {
-        try {
-            randomData.nextInt(4,3);
-            fail("IllegalArgumentException expected");
-        } catch (IllegalArgumentException ex) {
-            // ignored
-        }
-        Frequency freq = new Frequency();
-        int value = 0;
-        for (int i=0;i<smallSampleSize;i++) {
-            value = randomData.nextInt(0,3);
-            assertTrue("nextInt range",(value >= 0) && (value <= 3));
-            freq.addValue(value);  
-        }
-        long[] observed = new long[4];
-        for (int i=0; i<4; i++) {
-            observed[i] = freq.getCount(i);
-        } 
-        
-        /* Use ChiSquare dist with df = 4-1 = 3, alpha = .001
-         * Change to 11.34 for alpha = .01
-         */
-        assertTrue("chi-square test -- will fail about 1 in 1000 times",
-            testStatistic.chiSquare(expected,observed) < 16.27);    
-    }
-    
-    /** test dispersion and failure modes for nextLong() */
-    public void testNextLong() {
-       try {
-            randomData.nextLong(4,3);
-            fail("IllegalArgumentException expected");
-        } catch (IllegalArgumentException ex) {
-            // ignored
-        }
-       Frequency freq = new Frequency();
-       long value = 0;
-        for (int i=0;i<smallSampleSize;i++) {
-            value = randomData.nextLong(0,3);
-            assertTrue("nextInt range",(value >= 0) && (value <= 3));
-            freq.addValue(value);  
-        }
-        long[] observed = new long[4];
-        for (int i=0; i<4; i++) {
-            observed[i] = freq.getCount(i);
-        } 
-        
-        /* Use ChiSquare dist with df = 4-1 = 3, alpha = .001
-         * Change to 11.34 for alpha = .01
-         */
-        assertTrue("chi-square test -- will fail about 1 in 1000 times",
-            testStatistic.chiSquare(expected,observed) < 16.27);    
-    }
-    
-    /** test dispersion and failure modes for nextSecureLong() */
-    public void testNextSecureLong() {
-        try {
-            randomData.nextSecureLong(4,3);
-            fail("IllegalArgumentException expected");
-        } catch (IllegalArgumentException ex) {
-            // ignored
-        }
-        Frequency freq = new Frequency();
-        long value = 0;
-        for (int i=0;i<smallSampleSize;i++) {
-            value = randomData.nextSecureLong(0,3);
-            assertTrue("nextInt range",(value >= 0) && (value <= 3));
-            freq.addValue(value);  
-        }
-        long[] observed = new long[4];
-        for (int i=0; i<4; i++) {
-            observed[i] = freq.getCount(i);
-        } 
-        
-        /* Use ChiSquare dist with df = 4-1 = 3, alpha = .001
-         * Change to 11.34 for alpha = .01
-         */
-        assertTrue("chi-square test -- will fail about 1 in 1000 times",
-            testStatistic.chiSquare(expected,observed) < 16.27);    
-    }
-    
-    /** test dispersion and failure modes for nextSecureInt() */
-    public void testNextSecureInt() {
-        try {
-            randomData.nextSecureInt(4,3);
-            fail("IllegalArgumentException expected");
-        } catch (IllegalArgumentException ex) {
-            // ignored
-        }
-        Frequency freq = new Frequency();
-        int value = 0;
-        for (int i=0;i<smallSampleSize;i++) {
-            value = randomData.nextSecureInt(0,3);
-            assertTrue("nextInt range",(value >= 0) && (value <= 3));
-            freq.addValue(value);  
-        }
-        long[] observed = new long[4];
-        for (int i=0; i<4; i++) {
-            observed[i] = freq.getCount(i);
-        } 
-        
-        /* Use ChiSquare dist with df = 4-1 = 3, alpha = .001
-         * Change to 11.34 for alpha = .01
-         */
-        assertTrue("chi-square test -- will fail about 1 in 1000 times",
-            testStatistic.chiSquare(expected,observed) < 16.27);    
-    }
-    
-    /** 
-     * Make sure that empirical distribution of random Poisson(4)'s 
-     * has P(X <= 5) close to actual cumulative Poisson probablity
-     * and that nextPoisson fails when mean is non-positive
-     * TODO: replace with statistical test, adding test stat to TestStatistic
-     */
-    public void testNextPoisson() {
-        try {
-            randomData.nextPoisson(0);
-            fail("zero mean -- expecting IllegalArgumentException");
-        } catch (IllegalArgumentException ex) {
-            // ignored
-        }
-        Frequency f = new Frequency();
-        for (int i = 0; i<largeSampleSize; i++) {
-            try {
-                f.addValue(randomData.nextPoisson(4.0d));
-            } catch (Exception ex) {
-                fail(ex.getMessage());
-            }
-        }
-        long cumFreq = f.getCount(0) + f.getCount(1) + f.getCount(2) + 
-                        f.getCount(3) + f.getCount(4) + f.getCount(5);
-        long sumFreq = f.getSumFreq();
-        double cumPct = 
-            Double.valueOf(cumFreq).doubleValue()/Double.valueOf(sumFreq).doubleValue();
-        assertEquals("cum Poisson(4)",cumPct,0.7851,0.2);
-        try {
-            randomData.nextPoisson(-1);
-            fail("negative mean supplied -- IllegalArgumentException expected");
-        } catch (IllegalArgumentException ex) {
-            // ignored
-        }
-        try {
-            randomData.nextPoisson(0);
-            fail("0 mean supplied -- IllegalArgumentException expected");
-        } catch (IllegalArgumentException ex) {
-            // ignored
-        }
-        
-    }
-    
-    /** test dispersion and failute modes for nextHex() */
-    public void testNextHex() {
-        try {
-            randomData.nextHexString(-1);
-            fail("negative length supplied -- IllegalArgumentException expected");
-        } catch (IllegalArgumentException ex) {
-            // ignored
-        }
-        try {
-            randomData.nextHexString(0);
-            fail("zero length supplied -- IllegalArgumentException expected");
-        } catch (IllegalArgumentException ex) {
-            // ignored
-        }
-        String hexString = randomData.nextHexString(3);
-        if (hexString.length() != 3) {
-                fail("incorrect length for generated string");
-        }
-        hexString = randomData.nextHexString(1);
-        if (hexString.length() != 1) {
-                fail("incorrect length for generated string");
-        }
-        try {
-            hexString = randomData.nextHexString(0);
-            fail("zero length requested -- expecting IllegalArgumentException");
-        } catch (IllegalArgumentException ex) {
-            // ignored
-        }
-        if (hexString.length() != 1) {
-                fail("incorrect length for generated string");
-        }      
-        Frequency f = new Frequency();
-        for (int i = 0; i < smallSampleSize; i++) {
-            hexString = randomData.nextHexString(100);
-            if (hexString.length() != 100) {
-                fail("incorrect length for generated string");
-            }
-            for (int j = 0; j < hexString.length(); j++) {
-                f.addValue(hexString.substring(j,j+1));
-            }
-        }
-        double[] expected = new double[16];
-        long[] observed = new long[16];
-        for (int i = 0; i < 16; i++) {
-            expected[i] = (double)smallSampleSize*100/16;
-            observed[i] = f.getCount(hex[i]);
-        }
-        /* Use ChiSquare dist with df = 16-1 = 15, alpha = .001
-         * Change to 30.58 for alpha = .01
-         */
-        assertTrue("chi-square test -- will fail about 1 in 1000 times",
-            testStatistic.chiSquare(expected,observed) < 37.70);    
-    }
-    
-    /** test dispersion and failute modes for nextHex() */
-    public void testNextSecureHex() {
-        try {
-            randomData.nextSecureHexString(-1);
-            fail("negative length -- IllegalArgumentException expected");
-        } catch (IllegalArgumentException ex) {
-            // ignored
-        }
-        try {
-            randomData.nextSecureHexString(0);
-            fail("zero length -- IllegalArgumentException expected");
-        } catch (IllegalArgumentException ex) {
-            // ignored
-        }
-        String hexString = randomData.nextSecureHexString(3);
-        if (hexString.length() != 3) {
-                fail("incorrect length for generated string");
-        }
-        hexString = randomData.nextSecureHexString(1);
-        if (hexString.length() != 1) {
-                fail("incorrect length for generated string");
-        }
-        try {
-            hexString = randomData.nextSecureHexString(0);
-            fail("zero length requested -- expecting IllegalArgumentException");
-        } catch (IllegalArgumentException ex) {
-            // ignored
-        }
-        if (hexString.length() != 1) {
-                fail("incorrect length for generated string");
-        }      
-        Frequency f = new Frequency();
-        for (int i = 0; i < smallSampleSize; i++) {
-            hexString = randomData.nextSecureHexString(100);
-            if (hexString.length() != 100) {
-                fail("incorrect length for generated string");
-            }
-            for (int j = 0; j < hexString.length(); j++) {
-                f.addValue(hexString.substring(j,j+1));
-            }
-        }
-        double[] expected = new double[16];
-        long[] observed = new long[16];
-        for (int i = 0; i < 16; i++) {
-            expected[i] = (double)smallSampleSize*100/16;
-            observed[i] = f.getCount(hex[i]);
-        }
-        /* Use ChiSquare dist with df = 16-1 = 15, alpha = .001
-         * Change to 30.58 for alpha = .01
-         */
-        assertTrue("chi-square test -- will fail about 1 in 1000 times",
-            testStatistic.chiSquare(expected,observed) < 37.70);    
-    }
-    
-    /** test failure modes and dispersion of nextUniform() */  
-    public void testNextUniform() {    
-        try {
-            randomData.nextUniform(4,3);
-            fail("IllegalArgumentException expected");
-        } catch (IllegalArgumentException ex) {
-            // ignored
-        }
-        try {
-            randomData.nextUniform(3,3);
-            fail("IllegalArgumentException expected");
-        } catch (IllegalArgumentException ex) {
-            // ignored
-        }
-        double[] expected = {500,500};
-        long[] observed = {0,0};
-        double lower = -1d;
-        double upper = 20d;
-        double midpoint = (lower + upper)/2d;
-        double result = 0;
-        for (int i = 0; i < 1000; i++) {
-            result = randomData.nextUniform(lower,upper);
-            if ((result == lower) || (result == upper)) {
-                fail("generated value equal to an endpoint: " + result);
-            } 
-            if (result < midpoint) {
-                observed[0]++;
-            } else {
-                observed[1]++;
-            }
-        }
-        /* Use ChiSquare dist with df = 2-1 = 1, alpha = .001
-         * Change to 6.64 for alpha = .01
-         */
-        assertTrue("chi-square test -- will fail about 1 in 1000 times",
-            testStatistic.chiSquare(expected,observed) < 10.83);  
-    }
-    
-    /** test exclusive endpoints of nextUniform **/
-    public void testNextUniformExclusiveEndpoints() {
-        for (int i = 0; i < 1000; i++) {
-            double u = randomData.nextUniform(0.99, 1);
-            assertTrue(u > 0.99 && u < 1);
-        }
-    }
-    
-    /** test failure modes and distribution of nextGaussian() */  
-    public void testNextGaussian() { 
-        try {
-            randomData.nextGaussian(0,0);
-            fail("zero sigma -- IllegalArgumentException expected");
-        } catch (IllegalArgumentException ex) {
-            // ignored
-        }
-        SummaryStatistics u = new SummaryStatistics();
-        for (int i = 0; i<largeSampleSize; i++) {
-            u.addValue(randomData.nextGaussian(0,1));
-        }
-        double xbar = u.getMean();
-        double s = u.getStandardDeviation();
-        double n = u.getN(); 
-        /* t-test at .001-level TODO: replace with externalized t-test, with
-         * test statistic defined in TestStatistic
-         */
-        assertTrue(Math.abs(xbar)/(s/Math.sqrt(n))< 3.29);
-    }
-    
-    /** test failure modes and distribution of nextExponential() */  
-    public void testNextExponential() {
-        try {
-            randomData.nextExponential(-1);
-            fail("negative mean -- expecting IllegalArgumentException");
-        } catch (IllegalArgumentException ex) {
-            // ignored
-        }
-        assertEquals("0 mean", 0,randomData.nextExponential(0),10E-8); 
-        long cumFreq = 0;
-        double v = 0;
-        for (int i = 0; i < largeSampleSize; i++) {
-            v = randomData.nextExponential(1);
-            assertTrue("exponential deviate postive", v > 0);
-            if (v < 2) cumFreq++;
-        }
-        /* TODO: Replace with a statistical test, with statistic added to
-         * TestStatistic.  Check below compares observed cumulative distribution
-         * evaluated at 2 with exponential CDF 
-         */
-        assertEquals("exponential cumulative distribution",
-            (double)cumFreq/(double)largeSampleSize,0.8646647167633873,.2);
-    } 
-    
-    /** test reseeding, algorithm/provider games */
-    public void testConfig() {
-        randomData.reSeed(1000);
-        double v = randomData.nextUniform(0,1);
-        randomData.reSeed();
-        assertTrue("different seeds", 
-            Math.abs(v - randomData.nextUniform(0,1)) > 10E-12);
-        randomData.reSeed(1000);
-        assertEquals("same seeds",v,randomData.nextUniform(0,1),10E-12);
-        randomData.reSeedSecure(1000);
-        String hex = randomData.nextSecureHexString(40);
-        randomData.reSeedSecure();
-        assertTrue("different seeds",
-            !hex.equals(randomData.nextSecureHexString(40)));
-        randomData.reSeedSecure(1000);
-        assertTrue("same seeds",
-            !hex.equals(randomData.nextSecureHexString(40))); 
-        
-        /* remove this test back soon,
-         * since it takes about 4 seconds 
-
-        try {
-            randomData.setSecureAlgorithm("SHA1PRNG","SUN");
-        } catch (NoSuchProviderException ex) {
-            ;
-        }
-        assertTrue("different seeds",
-            !hex.equals(randomData.nextSecureHexString(40)));
-        try {
-            randomData.setSecureAlgorithm("NOSUCHTHING","SUN");
-            fail("expecting NoSuchAlgorithmException");
-        } catch (NoSuchProviderException ex) {
-            ;
-        } catch (NoSuchAlgorithmException ex) {
-            ;
-        }
-        
-        try {
-            randomData.setSecureAlgorithm("SHA1PRNG","NOSUCHPROVIDER");
-            fail("expecting NoSuchProviderException");
-        } catch (NoSuchProviderException ex) {
-            ;
-        } 
-        */
-        
-        // test reseeding without first using the generators
-        RandomDataImpl rd = new RandomDataImpl();
-        rd.reSeed(100);
-        rd.nextLong(1,2);
-        RandomDataImpl rd2 = new RandomDataImpl();
-        rd2.reSeedSecure(2000);
-        rd2.nextSecureLong(1,2);
-        rd = new RandomDataImpl();
-        rd.reSeed();
-        rd.nextLong(1,2);
-        rd2 = new RandomDataImpl();
-        rd2.reSeedSecure();
-        rd2.nextSecureLong(1,2);
-    }
-    
-    /** tests for nextSample() sampling from Collection */
-    public void testNextSample() {
-       Object[][] c = {{"0","1"},{"0","2"},{"0","3"},{"0","4"},{"1","2"},
-                        {"1","3"},{"1","4"},{"2","3"},{"2","4"},{"3","4"}};
-       long[] observed = {0,0,0,0,0,0,0,0,0,0};
-       double[] expected = {100,100,100,100,100,100,100,100,100,100};
-       
-       HashSet<Object> cPop = new HashSet<Object>();  //{0,1,2,3,4}
-       for (int i = 0; i < 5; i++) {
-           cPop.add(Integer.toString(i));
-       }
-       
-       Object[] sets = new Object[10]; // 2-sets from 5
-       for (int i = 0; i < 10; i ++) {
-           HashSet<Object> hs = new HashSet<Object>();
-           hs.add(c[i][0]);
-           hs.add(c[i][1]);
-           sets[i] = hs;
-       }
-       
-       for (int i = 0; i < 1000; i ++) {
-           Object[] cSamp = randomData.nextSample(cPop,2);
-           observed[findSample(sets,cSamp)]++;
-       }
-       
-        /* Use ChiSquare dist with df = 10-1 = 9, alpha = .001
-         * Change to 21.67 for alpha = .01
-         */
-        assertTrue("chi-square test -- will fail about 1 in 1000 times",
-            testStatistic.chiSquare(expected,observed) < 27.88);  
-       
-       // Make sure sample of size = size of collection returns same collection
-       HashSet<Object> hs = new HashSet<Object>();
-       hs.add("one");
-       Object[] one = randomData.nextSample(hs,1);
-       String oneString = (String) one[0];
-       if ((one.length != 1) || !oneString.equals("one")){
-           fail("bad sample for set size = 1, sample size = 1");
-       }
-       
-       // Make sure we fail for sample size > collection size
-       try {
-           one = randomData.nextSample(hs,2);
-           fail("sample size > set size, expecting IllegalArgumentException");
-       } catch (IllegalArgumentException ex) {
-           // ignored
-       }
-       
-       // Make sure we fail for empty collection
-       try {
-           hs = new HashSet<Object>();
-           one = randomData.nextSample(hs,0);
-           fail("n = k = 0, expecting IllegalArgumentException");
-       } catch (IllegalArgumentException ex) {
-           // ignored
-       }
-    }
-
-    @SuppressWarnings("unchecked")
-    private int findSample(Object[] u, Object[] samp) {
-        for (int i = 0; i < u.length; i++) {
-            HashSet<Object> set = (HashSet<Object>) u[i];
-            HashSet<Object> sampSet = new HashSet<Object>();
-            for (int j = 0; j < samp.length; j++) {
-                sampSet.add(samp[j]);
-            }
-            if (set.equals(sampSet)) {                 
-               return i;
-           }
-        }
-        fail("sample not found:{" + samp[0] + "," + samp[1] + "}");
-        return -1;
-    }
-    
-    /** tests for nextPermutation */
-    public void testNextPermutation() {
-        int[][] p = {{0,1,2},{0,2,1},{1,0,2},{1,2,0},{2,0,1},{2,1,0}};
-        long[] observed = {0,0,0,0,0,0};
-        double[] expected = {100,100,100,100,100,100};
-        
-        for (int i = 0; i < 600; i++) {
-            int[] perm = randomData.nextPermutation(3,3);
-            observed[findPerm(p,perm)]++;
-        }  
-        
-        /* Use ChiSquare dist with df = 6-1 = 5, alpha = .001
-         * Change to 15.09 for alpha = .01
-         */
-        assertTrue("chi-square test -- will fail about 1 in 1000 times",
-                testStatistic.chiSquare(expected,observed) < 20.52); 
-        
-        // Check size = 1 boundary case
-        int[] perm = randomData.nextPermutation(1,1);
-        if ((perm.length != 1) || (perm[0] != 0)){
-            fail("bad permutation for n = 1, sample k = 1");
-            
-            // Make sure we fail for k size > n 
-            try {
-                perm = randomData.nextPermutation(2,3);
-                fail("permutation k > n, expecting IllegalArgumentException");
-            } catch (IllegalArgumentException ex) {
-                // ignored
-            }
-            
-            // Make sure we fail for n = 0
-            try {
-                perm = randomData.nextPermutation(0,0);
-                fail("permutation k = n = 0, expecting IllegalArgumentException");
-            } catch (IllegalArgumentException ex) {
-                // ignored
-            }  
-            
-            // Make sure we fail for k < n < 0
-            try {
-                perm = randomData.nextPermutation(-1,-3);
-                fail("permutation k < n < 0, expecting IllegalArgumentException");
-            } catch (IllegalArgumentException ex) {
-                // ignored
-            }  
-            
-        }       
-    }
-    
-    private int findPerm(int[][] p, int[] samp) {
-        for (int i = 0; i < p.length; i++) {
-            boolean good = true;
-            for (int j = 0; j < samp.length; j++) {
-                if (samp[j] != p[i][j]) {
-                    good = false;
-                }
-            }
-            if (good)  {
-                return i;
-            }
-        }        
-        fail("permutation not found");
-        return -1;
-    }   
-}
+	public RandomDataTest(String name) {
+		super(name);
+		randomData = new RandomDataImpl();
+	}
+
+	protected long smallSampleSize = 1000;
+	protected double[] expected = { 250, 250, 250, 250 };
+	protected int largeSampleSize = 10000;
+	private String[] hex = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
+			"a", "b", "c", "d", "e", "f" };
+	protected RandomDataImpl randomData = null;
+	protected ChiSquareTestImpl testStatistic = new ChiSquareTestImpl();
+
+	public static Test suite() {
+		TestSuite suite = new TestSuite(RandomDataTest.class);
+		suite.setName("RandomData Tests");
+		return suite;
+	}
+
+	public void testNextIntExtremeValues() {
+		int x = randomData.nextInt(Integer.MIN_VALUE, Integer.MAX_VALUE);
+		int y = randomData.nextInt(Integer.MIN_VALUE, Integer.MAX_VALUE);
+		assertFalse(x == y);
+	}
+
+	public void testNextLongExtremeValues() {
+		long x = randomData.nextLong(Long.MIN_VALUE, Long.MAX_VALUE);
+		long y = randomData.nextLong(Long.MIN_VALUE, Long.MAX_VALUE);
+		assertFalse(x == y);
+	}
+
+	/** test dispersion and failure modes for nextInt() */
+	public void testNextInt() {
+		try {
+			randomData.nextInt(4, 3);
+			fail("IllegalArgumentException expected");
+		} catch (IllegalArgumentException ex) {
+			// ignored
+		}
+		Frequency freq = new Frequency();
+		int value = 0;
+		for (int i = 0; i < smallSampleSize; i++) {
+			value = randomData.nextInt(0, 3);
+			assertTrue("nextInt range", (value >= 0) && (value <= 3));
+			freq.addValue(value);
+		}
+		long[] observed = new long[4];
+		for (int i = 0; i < 4; i++) {
+			observed[i] = freq.getCount(i);
+		}
+
+		/*
+		 * Use ChiSquare dist with df = 4-1 = 3, alpha = .001 Change to 11.34
+		 * for alpha = .01
+		 */
+		assertTrue("chi-square test -- will fail about 1 in 1000 times",
+				testStatistic.chiSquare(expected, observed) < 16.27);
+	}
+
+	/** test dispersion and failure modes for nextLong() */
+	public void testNextLong() {
+		try {
+			randomData.nextLong(4, 3);
+			fail("IllegalArgumentException expected");
+		} catch (IllegalArgumentException ex) {
+			// ignored
+		}
+		Frequency freq = new Frequency();
+		long value = 0;
+		for (int i = 0; i < smallSampleSize; i++) {
+			value = randomData.nextLong(0, 3);
+			assertTrue("nextInt range", (value >= 0) && (value <= 3));
+			freq.addValue(value);
+		}
+		long[] observed = new long[4];
+		for (int i = 0; i < 4; i++) {
+			observed[i] = freq.getCount(i);
+		}
+
+		/*
+		 * Use ChiSquare dist with df = 4-1 = 3, alpha = .001 Change to 11.34
+		 * for alpha = .01
+		 */
+		assertTrue("chi-square test -- will fail about 1 in 1000 times",
+				testStatistic.chiSquare(expected, observed) < 16.27);
+	}
+
+	/** test dispersion and failure modes for nextSecureLong() */
+	public void testNextSecureLong() {
+		try {
+			randomData.nextSecureLong(4, 3);
+			fail("IllegalArgumentException expected");
+		} catch (IllegalArgumentException ex) {
+			// ignored
+		}
+		Frequency freq = new Frequency();
+		long value = 0;
+		for (int i = 0; i < smallSampleSize; i++) {
+			value = randomData.nextSecureLong(0, 3);
+			assertTrue("nextInt range", (value >= 0) && (value <= 3));
+			freq.addValue(value);
+		}
+		long[] observed = new long[4];
+		for (int i = 0; i < 4; i++) {
+			observed[i] = freq.getCount(i);
+		}
+
+		/*
+		 * Use ChiSquare dist with df = 4-1 = 3, alpha = .001 Change to 11.34
+		 * for alpha = .01
+		 */
+		assertTrue("chi-square test -- will fail about 1 in 1000 times",
+				testStatistic.chiSquare(expected, observed) < 16.27);
+	}
+
+	/** test dispersion and failure modes for nextSecureInt() */
+	public void testNextSecureInt() {
+		try {
+			randomData.nextSecureInt(4, 3);
+			fail("IllegalArgumentException expected");
+		} catch (IllegalArgumentException ex) {
+			// ignored
+		}
+		Frequency freq = new Frequency();
+		int value = 0;
+		for (int i = 0; i < smallSampleSize; i++) {
+			value = randomData.nextSecureInt(0, 3);
+			assertTrue("nextInt range", (value >= 0) && (value <= 3));
+			freq.addValue(value);
+		}
+		long[] observed = new long[4];
+		for (int i = 0; i < 4; i++) {
+			observed[i] = freq.getCount(i);
+		}
+
+		/*
+		 * Use ChiSquare dist with df = 4-1 = 3, alpha = .001 Change to 11.34
+		 * for alpha = .01
+		 */
+		assertTrue("chi-square test -- will fail about 1 in 1000 times",
+				testStatistic.chiSquare(expected, observed) < 16.27);
+	}
+
+	/**
+	 * Make sure that empirical distribution of random Poisson(4)'s has P(X <=
+	 * 5) close to actual cumulative Poisson probablity and that nextPoisson
+	 * fails when mean is non-positive TODO: replace with statistical test,
+	 * adding test stat to TestStatistic
+	 */
+	public void testNextPoisson() {
+		try {
+			randomData.nextPoisson(0);
+			fail("zero mean -- expecting IllegalArgumentException");
+		} catch (IllegalArgumentException ex) {
+			// ignored
+		}
+		Frequency f = new Frequency();
+		for (int i = 0; i < largeSampleSize; i++) {
+			try {
+				f.addValue(randomData.nextPoisson(4.0d));
+			} catch (Exception ex) {
+				fail(ex.getMessage());
+			}
+		}
+		long cumFreq = f.getCount(0) + f.getCount(1) + f.getCount(2)
+				+ f.getCount(3) + f.getCount(4) + f.getCount(5);
+		long sumFreq = f.getSumFreq();
+		double cumPct = Double.valueOf(cumFreq).doubleValue()
+				/ Double.valueOf(sumFreq).doubleValue();
+		assertEquals("cum Poisson(4)", cumPct, 0.7851, 0.2);
+		try {
+			randomData.nextPoisson(-1);
+			fail("negative mean supplied -- IllegalArgumentException expected");
+		} catch (IllegalArgumentException ex) {
+			// ignored
+		}
+		try {
+			randomData.nextPoisson(0);
+			fail("0 mean supplied -- IllegalArgumentException expected");
+		} catch (IllegalArgumentException ex) {
+			// ignored
+		}
+
+	}
+
+	public void testNextPoissonLargeMean() {
+		for (int i = 0; i < 1000; i++) {
+			long n = randomData.nextPoisson(1500.0);
+			assertTrue(0 <= n);
+		}
+	}
+
+	/** test dispersion and failute modes for nextHex() */
+	public void testNextHex() {
+		try {
+			randomData.nextHexString(-1);
+			fail("negative length supplied -- IllegalArgumentException expected");
+		} catch (IllegalArgumentException ex) {
+			// ignored
+		}
+		try {
+			randomData.nextHexString(0);
+			fail("zero length supplied -- IllegalArgumentException expected");
+		} catch (IllegalArgumentException ex) {
+			// ignored
+		}
+		String hexString = randomData.nextHexString(3);
+		if (hexString.length() != 3) {
+			fail("incorrect length for generated string");
+		}
+		hexString = randomData.nextHexString(1);
+		if (hexString.length() != 1) {
+			fail("incorrect length for generated string");
+		}
+		try {
+			hexString = randomData.nextHexString(0);
+			fail("zero length requested -- expecting IllegalArgumentException");
+		} catch (IllegalArgumentException ex) {
+			// ignored
+		}
+		if (hexString.length() != 1) {
+			fail("incorrect length for generated string");
+		}
+		Frequency f = new Frequency();
+		for (int i = 0; i < smallSampleSize; i++) {
+			hexString = randomData.nextHexString(100);
+			if (hexString.length() != 100) {
+				fail("incorrect length for generated string");
+			}
+			for (int j = 0; j < hexString.length(); j++) {
+				f.addValue(hexString.substring(j, j + 1));
+			}
+		}
+		double[] expected = new double[16];
+		long[] observed = new long[16];
+		for (int i = 0; i < 16; i++) {
+			expected[i] = (double) smallSampleSize * 100 / 16;
+			observed[i] = f.getCount(hex[i]);
+		}
+		/*
+		 * Use ChiSquare dist with df = 16-1 = 15, alpha = .001 Change to 30.58
+		 * for alpha = .01
+		 */
+		assertTrue("chi-square test -- will fail about 1 in 1000 times",
+				testStatistic.chiSquare(expected, observed) < 37.70);
+	}
+
+	/** test dispersion and failute modes for nextHex() */
+	public void testNextSecureHex() {
+		try {
+			randomData.nextSecureHexString(-1);
+			fail("negative length -- IllegalArgumentException expected");
+		} catch (IllegalArgumentException ex) {
+			// ignored
+		}
+		try {
+			randomData.nextSecureHexString(0);
+			fail("zero length -- IllegalArgumentException expected");
+		} catch (IllegalArgumentException ex) {
+			// ignored
+		}
+		String hexString = randomData.nextSecureHexString(3);
+		if (hexString.length() != 3) {
+			fail("incorrect length for generated string");
+		}
+		hexString = randomData.nextSecureHexString(1);
+		if (hexString.length() != 1) {
+			fail("incorrect length for generated string");
+		}
+		try {
+			hexString = randomData.nextSecureHexString(0);
+			fail("zero length requested -- expecting IllegalArgumentException");
+		} catch (IllegalArgumentException ex) {
+			// ignored
+		}
+		if (hexString.length() != 1) {
+			fail("incorrect length for generated string");
+		}
+		Frequency f = new Frequency();
+		for (int i = 0; i < smallSampleSize; i++) {
+			hexString = randomData.nextSecureHexString(100);
+			if (hexString.length() != 100) {
+				fail("incorrect length for generated string");
+			}
+			for (int j = 0; j < hexString.length(); j++) {
+				f.addValue(hexString.substring(j, j + 1));
+			}
+		}
+		double[] expected = new double[16];
+		long[] observed = new long[16];
+		for (int i = 0; i < 16; i++) {
+			expected[i] = (double) smallSampleSize * 100 / 16;
+			observed[i] = f.getCount(hex[i]);
+		}
+		/*
+		 * Use ChiSquare dist with df = 16-1 = 15, alpha = .001 Change to 30.58
+		 * for alpha = .01
+		 */
+		assertTrue("chi-square test -- will fail about 1 in 1000 times",
+				testStatistic.chiSquare(expected, observed) < 37.70);
+	}
+
+	/** test failure modes and dispersion of nextUniform() */
+	public void testNextUniform() {
+		try {
+			randomData.nextUniform(4, 3);
+			fail("IllegalArgumentException expected");
+		} catch (IllegalArgumentException ex) {
+			// ignored
+		}
+		try {
+			randomData.nextUniform(3, 3);
+			fail("IllegalArgumentException expected");
+		} catch (IllegalArgumentException ex) {
+			// ignored
+		}
+		double[] expected = { 500, 500 };
+		long[] observed = { 0, 0 };
+		double lower = -1d;
+		double upper = 20d;
+		double midpoint = (lower + upper) / 2d;
+		double result = 0;
+		for (int i = 0; i < 1000; i++) {
+			result = randomData.nextUniform(lower, upper);
+			if ((result == lower) || (result == upper)) {
+				fail("generated value equal to an endpoint: " + result);
+			}
+			if (result < midpoint) {
+				observed[0]++;
+			} else {
+				observed[1]++;
+			}
+		}
+		/*
+		 * Use ChiSquare dist with df = 2-1 = 1, alpha = .001 Change to 6.64 for
+		 * alpha = .01
+		 */
+		assertTrue("chi-square test -- will fail about 1 in 1000 times",
+				testStatistic.chiSquare(expected, observed) < 10.83);
+	}
+
+	/** test exclusive endpoints of nextUniform **/
+	public void testNextUniformExclusiveEndpoints() {
+		for (int i = 0; i < 1000; i++) {
+			double u = randomData.nextUniform(0.99, 1);
+			assertTrue(u > 0.99 && u < 1);
+		}
+	}
+
+	/** test failure modes and distribution of nextGaussian() */
+	public void testNextGaussian() {
+		try {
+			randomData.nextGaussian(0, 0);
+			fail("zero sigma -- IllegalArgumentException expected");
+		} catch (IllegalArgumentException ex) {
+			// ignored
+		}
+		SummaryStatistics u = new SummaryStatistics();
+		for (int i = 0; i < largeSampleSize; i++) {
+			u.addValue(randomData.nextGaussian(0, 1));
+		}
+		double xbar = u.getMean();
+		double s = u.getStandardDeviation();
+		double n = u.getN();
+		/*
+		 * t-test at .001-level TODO: replace with externalized t-test, with
+		 * test statistic defined in TestStatistic
+		 */
+		assertTrue(Math.abs(xbar) / (s / Math.sqrt(n)) < 3.29);
+	}
+
+	/** test failure modes and distribution of nextExponential() */
+	public void testNextExponential() {
+		try {
+			randomData.nextExponential(-1);
+			fail("negative mean -- expecting IllegalArgumentException");
+		} catch (IllegalArgumentException ex) {
+			// ignored
+		}
+		assertEquals("0 mean", 0, randomData.nextExponential(0), 10E-8);
+		long cumFreq = 0;
+		double v = 0;
+		for (int i = 0; i < largeSampleSize; i++) {
+			v = randomData.nextExponential(1);
+			assertTrue("exponential deviate postive", v > 0);
+			if (v < 2)
+				cumFreq++;
+		}
+		/*
+		 * TODO: Replace with a statistical test, with statistic added to
+		 * TestStatistic. Check below compares observed cumulative distribution
+		 * evaluated at 2 with exponential CDF
+		 */
+		assertEquals("exponential cumulative distribution", (double) cumFreq
+				/ (double) largeSampleSize, 0.8646647167633873, .2);
+	}
 
+	/** test reseeding, algorithm/provider games */
+	public void testConfig() {
+		randomData.reSeed(1000);
+		double v = randomData.nextUniform(0, 1);
+		randomData.reSeed();
+		assertTrue("different seeds", Math
+				.abs(v - randomData.nextUniform(0, 1)) > 10E-12);
+		randomData.reSeed(1000);
+		assertEquals("same seeds", v, randomData.nextUniform(0, 1), 10E-12);
+		randomData.reSeedSecure(1000);
+		String hex = randomData.nextSecureHexString(40);
+		randomData.reSeedSecure();
+		assertTrue("different seeds", !hex.equals(randomData
+				.nextSecureHexString(40)));
+		randomData.reSeedSecure(1000);
+		assertTrue("same seeds", !hex
+				.equals(randomData.nextSecureHexString(40)));
+
+		/*
+		 * remove this test back soon, since it takes about 4 seconds
+		 * 
+		 * try { randomData.setSecureAlgorithm("SHA1PRNG","SUN"); } catch
+		 * (NoSuchProviderException ex) { ; } assertTrue("different seeds",
+		 * !hex.equals(randomData.nextSecureHexString(40))); try {
+		 * randomData.setSecureAlgorithm("NOSUCHTHING","SUN");
+		 * fail("expecting NoSuchAlgorithmException"); } catch
+		 * (NoSuchProviderException ex) { ; } catch (NoSuchAlgorithmException
+		 * ex) { ; }
+		 * 
+		 * try { randomData.setSecureAlgorithm("SHA1PRNG","NOSUCHPROVIDER");
+		 * fail("expecting NoSuchProviderException"); } catch
+		 * (NoSuchProviderException ex) { ; }
+		 */
+
+		// test reseeding without first using the generators
+		RandomDataImpl rd = new RandomDataImpl();
+		rd.reSeed(100);
+		rd.nextLong(1, 2);
+		RandomDataImpl rd2 = new RandomDataImpl();
+		rd2.reSeedSecure(2000);
+		rd2.nextSecureLong(1, 2);
+		rd = new RandomDataImpl();
+		rd.reSeed();
+		rd.nextLong(1, 2);
+		rd2 = new RandomDataImpl();
+		rd2.reSeedSecure();
+		rd2.nextSecureLong(1, 2);
+	}
+
+	/** tests for nextSample() sampling from Collection */
+	public void testNextSample() {
+		Object[][] c = { { "0", "1" }, { "0", "2" }, { "0", "3" },
+				{ "0", "4" }, { "1", "2" }, { "1", "3" }, { "1", "4" },
+				{ "2", "3" }, { "2", "4" }, { "3", "4" } };
+		long[] observed = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
+		double[] expected = { 100, 100, 100, 100, 100, 100, 100, 100, 100, 100 };
+
+		HashSet<Object> cPop = new HashSet<Object>(); // {0,1,2,3,4}
+		for (int i = 0; i < 5; i++) {
+			cPop.add(Integer.toString(i));
+		}
+
+		Object[] sets = new Object[10]; // 2-sets from 5
+		for (int i = 0; i < 10; i++) {
+			HashSet<Object> hs = new HashSet<Object>();
+			hs.add(c[i][0]);
+			hs.add(c[i][1]);
+			sets[i] = hs;
+		}
+
+		for (int i = 0; i < 1000; i++) {
+			Object[] cSamp = randomData.nextSample(cPop, 2);
+			observed[findSample(sets, cSamp)]++;
+		}
+
+		/*
+		 * Use ChiSquare dist with df = 10-1 = 9, alpha = .001 Change to 21.67
+		 * for alpha = .01
+		 */
+		assertTrue("chi-square test -- will fail about 1 in 1000 times",
+				testStatistic.chiSquare(expected, observed) < 27.88);
+
+		// Make sure sample of size = size of collection returns same collection
+		HashSet<Object> hs = new HashSet<Object>();
+		hs.add("one");
+		Object[] one = randomData.nextSample(hs, 1);
+		String oneString = (String) one[0];
+		if ((one.length != 1) || !oneString.equals("one")) {
+			fail("bad sample for set size = 1, sample size = 1");
+		}
+
+		// Make sure we fail for sample size > collection size
+		try {
+			one = randomData.nextSample(hs, 2);
+			fail("sample size > set size, expecting IllegalArgumentException");
+		} catch (IllegalArgumentException ex) {
+			// ignored
+		}
+
+		// Make sure we fail for empty collection
+		try {
+			hs = new HashSet<Object>();
+			one = randomData.nextSample(hs, 0);
+			fail("n = k = 0, expecting IllegalArgumentException");
+		} catch (IllegalArgumentException ex) {
+			// ignored
+		}
+	}
+
+	@SuppressWarnings("unchecked")
+	private int findSample(Object[] u, Object[] samp) {
+		for (int i = 0; i < u.length; i++) {
+			HashSet<Object> set = (HashSet<Object>) u[i];
+			HashSet<Object> sampSet = new HashSet<Object>();
+			for (int j = 0; j < samp.length; j++) {
+				sampSet.add(samp[j]);
+			}
+			if (set.equals(sampSet)) {
+				return i;
+			}
+		}
+		fail("sample not found:{" + samp[0] + "," + samp[1] + "}");
+		return -1;
+	}
+
+	/** tests for nextPermutation */
+	public void testNextPermutation() {
+		int[][] p = { { 0, 1, 2 }, { 0, 2, 1 }, { 1, 0, 2 }, { 1, 2, 0 },
+				{ 2, 0, 1 }, { 2, 1, 0 } };
+		long[] observed = { 0, 0, 0, 0, 0, 0 };
+		double[] expected = { 100, 100, 100, 100, 100, 100 };
+
+		for (int i = 0; i < 600; i++) {
+			int[] perm = randomData.nextPermutation(3, 3);
+			observed[findPerm(p, perm)]++;
+		}
+
+		/*
+		 * Use ChiSquare dist with df = 6-1 = 5, alpha = .001 Change to 15.09
+		 * for alpha = .01
+		 */
+		assertTrue("chi-square test -- will fail about 1 in 1000 times",
+				testStatistic.chiSquare(expected, observed) < 20.52);
+
+		// Check size = 1 boundary case
+		int[] perm = randomData.nextPermutation(1, 1);
+		if ((perm.length != 1) || (perm[0] != 0)) {
+			fail("bad permutation for n = 1, sample k = 1");
+
+			// Make sure we fail for k size > n
+			try {
+				perm = randomData.nextPermutation(2, 3);
+				fail("permutation k > n, expecting IllegalArgumentException");
+			} catch (IllegalArgumentException ex) {
+				// ignored
+			}
+
+			// Make sure we fail for n = 0
+			try {
+				perm = randomData.nextPermutation(0, 0);
+				fail("permutation k = n = 0, expecting IllegalArgumentException");
+			} catch (IllegalArgumentException ex) {
+				// ignored
+			}
+
+			// Make sure we fail for k < n < 0
+			try {
+				perm = randomData.nextPermutation(-1, -3);
+				fail("permutation k < n < 0, expecting IllegalArgumentException");
+			} catch (IllegalArgumentException ex) {
+				// ignored
+			}
+
+		}
+	}
+
+	private int findPerm(int[][] p, int[] samp) {
+		for (int i = 0; i < p.length; i++) {
+			boolean good = true;
+			for (int j = 0; j < samp.length; j++) {
+				if (samp[j] != p[i][j]) {
+					good = false;
+				}
+			}
+			if (good) {
+				return i;
+			}
+		}
+		fail("permutation not found");
+		return -1;
+	}
+}