You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by ah...@apache.org on 2019/07/31 21:47:23 UTC

[commons-rng] 05/08: RNG-110: Provide factory constructors for unreleased samplers.

This is an automated email from the ASF dual-hosted git repository.

aherbert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-rng.git

commit b3a439bab6a71828b9d3426b37c895492de9a2ca
Author: Alex Herbert <ah...@apache.org>
AuthorDate: Fri Jul 19 22:08:50 2019 +0100

    RNG-110: Provide factory constructors for unreleased samplers.
    
    These samplers have no requirement to maintain an instance constructor.
    The constructor has been made private.
---
 .../AliasMethodDiscreteSamplerPerformance.java     |    4 +-
 .../distribution/DiscreteSamplersPerformance.java  |   12 +-
 .../distribution/GeometricSamplersPerformance.java |    2 +-
 .../distribution/PoissonSamplersPerformance.java   |    2 +-
 .../distribution/AliasMethodDiscreteSampler.java   |   18 +-
 .../sampling/distribution/GeometricSampler.java    |   52 +-
 .../distribution/GuideTableDiscreteSampler.java    |  158 +--
 .../distribution/KempSmallMeanPoissonSampler.java  |   62 +-
 .../distribution/LargeMeanPoissonSampler.java      |    4 +-
 .../MarsagliaTsangWangDiscreteSampler.java         | 1063 ++++++++++----------
 .../AliasMethodDiscreteSamplerTest.java            |   14 +-
 .../distribution/DiscreteSamplersList.java         |   20 +-
 .../distribution/GeometricSamplerTest.java         |   18 +-
 .../GuideTableDiscreteSamplerTest.java             |   12 +-
 .../KempSmallMeanPoissonSamplerTest.java           |   16 +-
 .../MarsagliaTsangWangDiscreteSamplerTest.java     |   58 +-
 src/main/resources/pmd/pmd-ruleset.xml             |    3 +-
 17 files changed, 778 insertions(+), 740 deletions(-)

diff --git a/commons-rng-examples/examples-jmh/src/main/java/org/apache/commons/rng/examples/jmh/distribution/AliasMethodDiscreteSamplerPerformance.java b/commons-rng-examples/examples-jmh/src/main/java/org/apache/commons/rng/examples/jmh/distribution/AliasMethodDiscreteSamplerPerformance.java
index 408e1ba..32d8307 100644
--- a/commons-rng-examples/examples-jmh/src/main/java/org/apache/commons/rng/examples/jmh/distribution/AliasMethodDiscreteSamplerPerformance.java
+++ b/commons-rng-examples/examples-jmh/src/main/java/org/apache/commons/rng/examples/jmh/distribution/AliasMethodDiscreteSamplerPerformance.java
@@ -96,7 +96,7 @@ public class AliasMethodDiscreteSamplerPerformance {
         public void setup() {
             probabilities = createProbabilities(size);
             UniformRandomProvider rng = RandomSource.create(RandomSource.SPLIT_MIX_64);
-            sampler = AliasMethodDiscreteSampler.create(rng, probabilities, alpha);
+            sampler = AliasMethodDiscreteSampler.of(rng, probabilities, alpha);
         }
 
         /**
@@ -155,6 +155,6 @@ public class AliasMethodDiscreteSamplerPerformance {
     @Benchmark
     public Object createSampler(DistributionData dist) {
         // For the construction the RNG can be null
-        return AliasMethodDiscreteSampler.create(null, dist.getProbabilities(), dist.getAlpha());
+        return AliasMethodDiscreteSampler.of(null, dist.getProbabilities(), dist.getAlpha());
     }
 }
diff --git a/commons-rng-examples/examples-jmh/src/main/java/org/apache/commons/rng/examples/jmh/distribution/DiscreteSamplersPerformance.java b/commons-rng-examples/examples-jmh/src/main/java/org/apache/commons/rng/examples/jmh/distribution/DiscreteSamplersPerformance.java
index c8cb81d..03bae18 100644
--- a/commons-rng-examples/examples-jmh/src/main/java/org/apache/commons/rng/examples/jmh/distribution/DiscreteSamplersPerformance.java
+++ b/commons-rng-examples/examples-jmh/src/main/java/org/apache/commons/rng/examples/jmh/distribution/DiscreteSamplersPerformance.java
@@ -119,17 +119,17 @@ public class DiscreteSamplersPerformance {
                 // Note: Use with a fractional part to the mean includes a small mean sample
                 sampler = new LargeMeanPoissonSampler(rng, 41.7);
             } else if ("GeometricSampler".equals(samplerType)) {
-                sampler = new GeometricSampler(rng, 0.21);
+                sampler = GeometricSampler.of(rng, 0.21);
             } else if ("MarsagliaTsangWangDiscreteSampler".equals(samplerType)) {
-                sampler = MarsagliaTsangWangDiscreteSampler.createDiscreteDistribution(rng, DISCRETE_PROBABILITIES);
+                sampler = MarsagliaTsangWangDiscreteSampler.Enumerated.of(rng, DISCRETE_PROBABILITIES);
             } else if ("MarsagliaTsangWangPoissonSampler".equals(samplerType)) {
-                sampler = MarsagliaTsangWangDiscreteSampler.createPoissonDistribution(rng, 8.9);
+                sampler = MarsagliaTsangWangDiscreteSampler.Poisson.of(rng, 8.9);
             } else if ("MarsagliaTsangWangBinomialSampler".equals(samplerType)) {
-                sampler = MarsagliaTsangWangDiscreteSampler.createBinomialDistribution(rng, 20, 0.33);
+                sampler = MarsagliaTsangWangDiscreteSampler.Binomial.of(rng, 20, 0.33);
             } else if ("GuideTableDiscreteSampler".equals(samplerType)) {
-                sampler = new GuideTableDiscreteSampler(rng, DISCRETE_PROBABILITIES);
+                sampler = GuideTableDiscreteSampler.of(rng, DISCRETE_PROBABILITIES);
             } else if ("AliasMethodDiscreteSampler".equals(samplerType)) {
-                sampler = AliasMethodDiscreteSampler.create(rng, DISCRETE_PROBABILITIES);
+                sampler = AliasMethodDiscreteSampler.of(rng, DISCRETE_PROBABILITIES);
             }
         }
     }
diff --git a/commons-rng-examples/examples-jmh/src/main/java/org/apache/commons/rng/examples/jmh/distribution/GeometricSamplersPerformance.java b/commons-rng-examples/examples-jmh/src/main/java/org/apache/commons/rng/examples/jmh/distribution/GeometricSamplersPerformance.java
index b87ef8f..7fc3e57 100644
--- a/commons-rng-examples/examples-jmh/src/main/java/org/apache/commons/rng/examples/jmh/distribution/GeometricSamplersPerformance.java
+++ b/commons-rng-examples/examples-jmh/src/main/java/org/apache/commons/rng/examples/jmh/distribution/GeometricSamplersPerformance.java
@@ -95,7 +95,7 @@ public class GeometricSamplersPerformance {
             final RandomSource randomSource = RandomSource.valueOf(randomSourceName);
             final UniformRandomProvider rng = RandomSource.create(randomSource);
             if ("GeometricSampler".equals(samplerType)) {
-                sampler = new GeometricSampler(rng, probabilityOfSuccess);
+                sampler = GeometricSampler.of(rng, probabilityOfSuccess);
             } else {
                 final DiscreteInverseCumulativeProbabilityFunction geometricFunction =
                     new GeometricDiscreteInverseCumulativeProbabilityFunction(probabilityOfSuccess);
diff --git a/commons-rng-examples/examples-jmh/src/main/java/org/apache/commons/rng/examples/jmh/distribution/PoissonSamplersPerformance.java b/commons-rng-examples/examples-jmh/src/main/java/org/apache/commons/rng/examples/jmh/distribution/PoissonSamplersPerformance.java
index fbce71e..2ef6f8b 100644
--- a/commons-rng-examples/examples-jmh/src/main/java/org/apache/commons/rng/examples/jmh/distribution/PoissonSamplersPerformance.java
+++ b/commons-rng-examples/examples-jmh/src/main/java/org/apache/commons/rng/examples/jmh/distribution/PoissonSamplersPerformance.java
@@ -185,7 +185,7 @@ public class PoissonSamplersPerformance {
                 factory = new DiscreteSamplerFactory() {
                     @Override
                     public DiscreteSampler create() {
-                        return new KempSmallMeanPoissonSampler(generator, mean);
+                        return KempSmallMeanPoissonSampler.of(generator, mean);
                     }
                 };
             } else if ("BoundedKempSmallMeanPoissonSampler".equals(samplerType)) {
diff --git a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/AliasMethodDiscreteSampler.java b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/AliasMethodDiscreteSampler.java
index 2ad3593..2fa1ef7 100644
--- a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/AliasMethodDiscreteSampler.java
+++ b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/AliasMethodDiscreteSampler.java
@@ -52,7 +52,7 @@ import java.util.Arrays;
  * approximately 12 bytes of storage per input probability, that is {@code n * 12} for size
  * {@code n}. Zero-padding only requires 4 bytes of storage per padded value as the probability is
  * known to be zero. A table can be padded to a power of 2 using the utility function
- * {@link #create(UniformRandomProvider, double[], int)} to construct the sampler.</p>
+ * {@link #of(UniformRandomProvider, double[], int)} to construct the sampler.</p>
  *
  * <p>An optimisation is performed for small table sizes that are a power of 2. In this case the
  * sampling uses 1 or 2 calls from {@link UniformRandomProvider#nextInt()} to generate up to
@@ -293,7 +293,7 @@ public class AliasMethodDiscreteSampler
      * power-of-two. Padding is bounded by the upper limit on the size of an array.</p>
      *
      * <p>To avoid zero-padding use the
-     * {@link #create(UniformRandomProvider, double[], int)} method with a negative
+     * {@link #of(UniformRandomProvider, double[], int)} method with a negative
      * {@code alpha} factor.</p>
      *
      * @param rng Generator of uniformly distributed random numbers.
@@ -302,11 +302,11 @@ public class AliasMethodDiscreteSampler
      * @throws IllegalArgumentException if {@code probabilities} is null or empty, a
      * probability is negative, infinite or {@code NaN}, or the sum of all
      * probabilities is not strictly positive.
-     * @see #create(UniformRandomProvider, double[], int)
+     * @see #of(UniformRandomProvider, double[], int)
      */
-    public static AliasMethodDiscreteSampler create(final UniformRandomProvider rng,
-                                                    final double[] probabilities) {
-        return create(rng, probabilities, DEFAULT_ALPHA);
+    public static SharedStateDiscreteSampler of(final UniformRandomProvider rng,
+                                                final double[] probabilities) {
+        return of(rng, probabilities, DEFAULT_ALPHA);
     }
 
     /**
@@ -348,9 +348,9 @@ public class AliasMethodDiscreteSampler
      * probability is negative, infinite or {@code NaN}, or the sum of all
      * probabilities is not strictly positive.
      */
-    public static AliasMethodDiscreteSampler create(final UniformRandomProvider rng,
-                                                    final double[] probabilities,
-                                                    int alpha) {
+    public static SharedStateDiscreteSampler of(final UniformRandomProvider rng,
+                                                final double[] probabilities,
+                                                int alpha) {
         // The Alias method balances N categories with counts around the mean into N sections,
         // each allocated 'mean' observations.
         //
diff --git a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/GeometricSampler.java b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/GeometricSampler.java
index 25a4703..7cbca6b 100644
--- a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/GeometricSampler.java
+++ b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/GeometricSampler.java
@@ -45,10 +45,7 @@ import org.apache.commons.rng.UniformRandomProvider;
  *
  * @since 1.3
  */
-public class GeometricSampler implements SharedStateDiscreteSampler {
-    /** The appropriate geometric sampler for the parameters. */
-    private final SharedStateDiscreteSampler delegate;
-
+public final class GeometricSampler {
     /**
      * Sample from the geometric distribution when the probability of success is 1.
      */
@@ -132,48 +129,29 @@ public class GeometricSampler implements SharedStateDiscreteSampler {
         }
     }
 
+    /** Class contains only static methods. */
+    private GeometricSampler() {}
+
     /**
-     * Creates a new geometric distribution sampler. The samples will be provided in the set
-     * {@code k=[0, 1, 2, ...]} where {@code k} indicates the number of failures before the first
-     * success.
+     * Creates a new geometric distribution sampler. The samples will be provided in
+     * the set {@code k=[0, 1, 2, ...]} where {@code k} indicates the number of
+     * failures before the first success.
      *
-     * @param rng Generator of uniformly distributed random numbers
-     * @param probabilityOfSuccess The probability of success
-     * @throws IllegalArgumentException if {@code probabilityOfSuccess} is not in the range
-     * {@code [0 < probabilityOfSuccess <= 1]})
+     * @param rng Generator of uniformly distributed random numbers.
+     * @param probabilityOfSuccess The probability of success.
+     * @return the sampler
+     * @throws IllegalArgumentException if {@code probabilityOfSuccess} is not in
+     * the range {@code [0 < probabilityOfSuccess <= 1]})
      */
-    public GeometricSampler(UniformRandomProvider rng, double probabilityOfSuccess) {
+    public static SharedStateDiscreteSampler of(UniformRandomProvider rng,
+                                                double probabilityOfSuccess) {
         if (probabilityOfSuccess <= 0 || probabilityOfSuccess > 1) {
             throw new IllegalArgumentException(
                 "Probability of success (p) must be in the range [0 < p <= 1]: " +
                     probabilityOfSuccess);
         }
-        delegate = probabilityOfSuccess == 1 ?
+        return probabilityOfSuccess == 1 ?
             GeometricP1Sampler.INSTANCE :
             new GeometricExponentialSampler(rng, probabilityOfSuccess);
     }
-
-    /**
-     * Create a sample from a geometric distribution.
-     *
-     * <p>The sample will take the values in the set {@code [0, 1, 2, ...]}, equivalent to the
-     * number of failures before the first success.
-     */
-    @Override
-    public int sample() {
-        return delegate.sample();
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public String toString() {
-        return delegate.toString();
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public SharedStateDiscreteSampler withUniformRandomProvider(UniformRandomProvider rng) {
-        // Direct return of the optimised sampler
-        return delegate.withUniformRandomProvider(rng);
-    }
 }
diff --git a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/GuideTableDiscreteSampler.java b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/GuideTableDiscreteSampler.java
index 0ca3315..3ad4218 100644
--- a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/GuideTableDiscreteSampler.java
+++ b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/GuideTableDiscreteSampler.java
@@ -19,27 +19,27 @@ package org.apache.commons.rng.sampling.distribution;
 import org.apache.commons.rng.UniformRandomProvider;
 
 /**
- * Compute a sample from a discrete probability distribution. The cumulative probability
- * distribution is searched using a guide table to set an initial start point. This implementation
- * is based on:
+ * Compute a sample from {@code n} values each with an associated probability. If all unique items
+ * are assigned the same probability it is more efficient to use the {@link DiscreteUniformSampler}.
  *
- * <ul>
- *  <li>
- *   <blockquote>
- *    Devroye, Luc (1986). Non-Uniform Random Variate Generation.
- *    New York: Springer-Verlag. Chapter 3.2.4 "The method of guide tables" p. 96.
- *   </blockquote>
- *  </li>
- * </ul>
+ * <p>The cumulative probability distribution is searched using a guide table to set an
+ * initial start point. This implementation is based on:</p>
+ *
+ * <blockquote>
+ *  Devroye, Luc (1986). Non-Uniform Random Variate Generation.
+ *  New York: Springer-Verlag. Chapter 3.2.4 "The method of guide tables" p. 96.
+ * </blockquote>
  *
  * <p>The size of the guide table can be controlled using a parameter. A larger guide table
  * will improve performance at the cost of storage space.</p>
  *
  * <p>Sampling uses {@link UniformRandomProvider#nextDouble()}.</p>
  *
+ * @see <a href="http://en.wikipedia.org/wiki/Probability_distribution#Discrete_probability_distribution">
+ * Discrete probability distribution (Wikipedia)</a>
  * @since 1.3
  */
-public class GuideTableDiscreteSampler
+public final class GuideTableDiscreteSampler
     implements SharedStateDiscreteSampler {
     /** The default value for {@code alpha}. */
     private static final double DEFAULT_ALPHA = 1.0;
@@ -63,38 +63,93 @@ public class GuideTableDiscreteSampler
     private final int[] guideTable;
 
     /**
-     * Create a new instance using the default guide table size.
+     * @param rng Generator of uniformly distributed random numbers.
+     * @param cumulativeProbabilities The cumulative probability table ({@code f(x)}).
+     * @param guideTable The inverse cumulative probability guide table.
+     */
+    private GuideTableDiscreteSampler(UniformRandomProvider rng,
+                                      double[] cumulativeProbabilities,
+                                      int[] guideTable) {
+        this.rng = rng;
+        this.cumulativeProbabilities = cumulativeProbabilities;
+        this.guideTable = guideTable;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int sample() {
+        // Compute a probability
+        final double u = rng.nextDouble();
+
+        // Initialise the search using the guide table to find an initial guess.
+        // The table provides an upper bound on the sample (x+1) for a known
+        // cumulative probability (f(x)).
+        int x = guideTable[getGuideTableIndex(u, guideTable.length)];
+        // Search down.
+        // In the edge case where u is 1.0 then 'x' will be 1 outside the range of the
+        // cumulative probability table and this will decrement to a valid range.
+        // In the case where 'u' is mapped to the same guide table index as a lower
+        // cumulative probability f(x) (due to rounding down) then this will not decrement
+        // and return the exclusive upper bound (x+1).
+        while (x != 0 && u <= cumulativeProbabilities[x - 1]) {
+            x--;
+        }
+        return x;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        return "Guide table deviate [" + rng.toString() + "]";
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public SharedStateDiscreteSampler withUniformRandomProvider(UniformRandomProvider rng) {
+        return new GuideTableDiscreteSampler(rng, cumulativeProbabilities, guideTable);
+    }
+
+    /**
+     * Create a new sampler for an enumerated distribution using the given {@code probabilities}.
+     * The samples corresponding to each probability are assumed to be a natural sequence
+     * starting at zero.
+     *
+     * <p>The size of the guide table is {@code probabilities.length}.</p>
      *
      * @param rng Generator of uniformly distributed random numbers.
      * @param probabilities The probabilities.
+     * @return the sampler
      * @throws IllegalArgumentException if {@code probabilities} is null or empty, a
      * probability is negative, infinite or {@code NaN}, or the sum of all
      * probabilities is not strictly positive.
      */
-    public GuideTableDiscreteSampler(UniformRandomProvider rng,
-                                     double[] probabilities) {
-        this(rng, probabilities, DEFAULT_ALPHA);
+    public static SharedStateDiscreteSampler of(UniformRandomProvider rng,
+                                                double[] probabilities) {
+        return of(rng, probabilities, DEFAULT_ALPHA);
     }
 
     /**
-     * Create a new instance.
+     * Create a new sampler for an enumerated distribution using the given {@code probabilities}.
+     * The samples corresponding to each probability are assumed to be a natural sequence
+     * starting at zero.
      *
-     * <p>The size of the guide table is {@code alpha * probabilities.length}.
+     * <p>The size of the guide table is {@code alpha * probabilities.length}.</p>
      *
      * @param rng Generator of uniformly distributed random numbers.
      * @param probabilities The probabilities.
      * @param alpha The alpha factor used to set the guide table size.
+     * @return the sampler
      * @throws IllegalArgumentException if {@code probabilities} is null or empty, a
      * probability is negative, infinite or {@code NaN}, the sum of all
      * probabilities is not strictly positive, or {@code alpha} is not strictly positive.
      */
-    public GuideTableDiscreteSampler(UniformRandomProvider rng,
-                                     double[] probabilities,
-                                     double alpha) {
+    public static SharedStateDiscreteSampler of(UniformRandomProvider rng,
+                                                double[] probabilities,
+                                                double alpha) {
         validateParameters(probabilities, alpha);
 
         final int size = probabilities.length;
-        cumulativeProbabilities = new double[size];
+        final double[] cumulativeProbabilities = new double[size];
 
         double sumProb = 0;
         int count = 0;
@@ -110,12 +165,10 @@ public class GuideTableDiscreteSampler
             throw new IllegalArgumentException("Invalid sum of probabilities: " + sumProb);
         }
 
-        this.rng = rng;
-
         // Note: The guide table is at least length 1. Compute the size avoiding overflow
         // in case (alpha * size) is too large.
         final int guideTableSize = (int) Math.ceil(alpha * size);
-        guideTable = new int[Math.max(guideTableSize, guideTableSize + 1)];
+        final int[] guideTable = new int[Math.max(guideTableSize, guideTableSize + 1)];
 
         // Compute and store cumulative probability.
         for (int x = 0; x < size; x++) {
@@ -123,7 +176,8 @@ public class GuideTableDiscreteSampler
             cumulativeProbabilities[x] = (norm < 1) ? norm : 1.0;
 
             // Set the guide table value as an exclusive upper bound (x + 1)
-            guideTable[getGuideTableIndex(cumulativeProbabilities[x])] = x + 1;
+            final int index = getGuideTableIndex(cumulativeProbabilities[x], guideTable.length);
+            guideTable[index] = x + 1;
         }
 
         // Edge case for round-off
@@ -139,17 +193,8 @@ public class GuideTableDiscreteSampler
         for (int i = 1; i < guideTable.length; i++) {
             guideTable[i] = Math.max(guideTable[i - 1], guideTable[i]);
         }
-    }
 
-    /**
-     * @param rng Generator of uniformly distributed random numbers.
-     * @param source Source to copy.
-     */
-    private GuideTableDiscreteSampler(UniformRandomProvider rng,
-                                      GuideTableDiscreteSampler source) {
-        this.rng = rng;
-        cumulativeProbabilities = source.cumulativeProbabilities;
-        guideTable = source.guideTable;
+        return new GuideTableDiscreteSampler(rng, cumulativeProbabilities, guideTable);
     }
 
     /**
@@ -171,48 +216,15 @@ public class GuideTableDiscreteSampler
 
     /**
      * Gets the guide table index for the probability. This is obtained using
-     * {@code p * (guideTable.length - 1)} so is inside the length of the table.
+     * {@code p * (tableLength - 1)} so is inside the length of the table.
      *
      * @param p Cumulative probability.
+     * @param tableLength Table length.
      * @return the guide table index.
      */
-    private int getGuideTableIndex(double p) {
+    private static int getGuideTableIndex(double p, int tableLength) {
         // Note: This is only ever called when p is in the range of the cumulative
         // probability table. So assume 0 <= p <= 1.
-        return (int) (p * (guideTable.length - 1));
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public int sample() {
-        // Compute a probability
-        final double u = rng.nextDouble();
-
-        // Initialise the search using the guide table to find an initial guess.
-        // The table provides an upper bound on the sample (x+1) for a known
-        // cumulative probability (f(x)).
-        int x = guideTable[getGuideTableIndex(u)];
-        // Search down.
-        // In the edge case where u is 1.0 then 'x' will be 1 outside the range of the
-        // cumulative probability table and this will decrement to a valid range.
-        // In the case where 'u' is mapped to the same guide table index as a lower
-        // cumulative probability f(x) (due to rounding down) then this will not decrement
-        // and return the exclusive upper bound (x+1).
-        while (x != 0 && u <= cumulativeProbabilities[x - 1]) {
-            x--;
-        }
-        return x;
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public String toString() {
-        return "Guide table deviate [" + rng.toString() + "]";
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public SharedStateDiscreteSampler withUniformRandomProvider(UniformRandomProvider rng) {
-        return new GuideTableDiscreteSampler(rng, this);
+        return (int) (p * (tableLength - 1));
     }
 }
diff --git a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/KempSmallMeanPoissonSampler.java b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/KempSmallMeanPoissonSampler.java
index 7c4a09c..353651d 100644
--- a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/KempSmallMeanPoissonSampler.java
+++ b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/KempSmallMeanPoissonSampler.java
@@ -41,11 +41,11 @@ import org.apache.commons.rng.UniformRandomProvider;
  * <p>Sampling uses 1 call to {@link UniformRandomProvider#nextDouble()}. This method provides
  * an alternative to the {@link SmallMeanPoissonSampler} for slow generators of {@code double}.</p>
  *
- * @since 1.3
  * @see <a href="https://www.jstor.org/stable/2346348">Kemp, A.W. (1981) JRSS Vol. 30, pp.
  * 249-253</a>
+ * @since 1.3
  */
-public class KempSmallMeanPoissonSampler
+public final class KempSmallMeanPoissonSampler
     implements SharedStateDiscreteSampler {
     /** Underlying source of randomness. */
     private final UniformRandomProvider rng;
@@ -61,35 +61,15 @@ public class KempSmallMeanPoissonSampler
 
     /**
      * @param rng Generator of uniformly distributed random numbers.
+     * @param p0 Probability of the Poisson sample {@code p(x=0)}.
      * @param mean Mean.
-     * @throws IllegalArgumentException if {@code mean <= 0} or
-     * {@code Math.exp(-mean) == 0}.
-     */
-    public KempSmallMeanPoissonSampler(UniformRandomProvider rng,
-                                       double mean) {
-        if (mean <= 0) {
-            throw new IllegalArgumentException("Mean is not strictly positive: " + mean);
-        }
-
-        p0 = Math.exp(-mean);
-        if (p0 > 0) {
-            this.rng = rng;
-            this.mean = mean;
-        } else {
-            // This catches the edge case of a NaN mean
-            throw new IllegalArgumentException("No probability for mean " + mean);
-        }
-    }
-
-    /**
-     * @param rng Generator of uniformly distributed random numbers.
-     * @param source Source to copy.
      */
     private KempSmallMeanPoissonSampler(UniformRandomProvider rng,
-                                        KempSmallMeanPoissonSampler source) {
+                                        double p0,
+                                        double mean) {
         this.rng = rng;
-        p0 = source.p0;
-        mean = source.mean;
+        this.p0 = p0;
+        this.mean = mean;
     }
 
     /** {@inheritDoc} */
@@ -129,6 +109,32 @@ public class KempSmallMeanPoissonSampler
     /** {@inheritDoc} */
     @Override
     public SharedStateDiscreteSampler withUniformRandomProvider(UniformRandomProvider rng) {
-        return new KempSmallMeanPoissonSampler(rng, this);
+        return new KempSmallMeanPoissonSampler(rng, p0, mean);
+    }
+
+    /**
+     * Creates a new sampler for the Poisson distribution.
+     *
+     * @param rng Generator of uniformly distributed random numbers.
+     * @param mean Mean of the distribution.
+     * @return the sampler
+     * @throws IllegalArgumentException if {@code mean <= 0} or
+     * {@code Math.exp(-mean) == 0}.
+     */
+    public static SharedStateDiscreteSampler of(UniformRandomProvider rng,
+                                                double mean) {
+        if (mean <= 0) {
+            throw new IllegalArgumentException("Mean is not strictly positive: " + mean);
+        }
+
+        final double p0 = Math.exp(-mean);
+
+        // Probability must be positive. As mean increases then p(0) decreases.
+        if (p0 > 0) {
+            return new KempSmallMeanPoissonSampler(rng, p0, mean);
+        }
+
+        // This catches the edge case of a NaN mean
+        throw new IllegalArgumentException("No probability for mean: " + mean);
     }
 }
diff --git a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/LargeMeanPoissonSampler.java b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/LargeMeanPoissonSampler.java
index 2a68e32..bc71e5b 100644
--- a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/LargeMeanPoissonSampler.java
+++ b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/LargeMeanPoissonSampler.java
@@ -155,7 +155,7 @@ public class LargeMeanPoissonSampler
         final double lambdaFractional = mean - lambda;
         smallMeanPoissonSampler = (lambdaFractional < Double.MIN_VALUE) ?
             NO_SMALL_MEAN_POISSON_SAMPLER : // Not used.
-            new KempSmallMeanPoissonSampler(rng, lambdaFractional);
+            KempSmallMeanPoissonSampler.of(rng, lambdaFractional);
     }
 
     /**
@@ -196,7 +196,7 @@ public class LargeMeanPoissonSampler
         // The algorithm requires a Poisson sample from the remaining lambda fraction.
         smallMeanPoissonSampler = (lambdaFractional < Double.MIN_VALUE) ?
             NO_SMALL_MEAN_POISSON_SAMPLER : // Not used.
-            new KempSmallMeanPoissonSampler(rng, lambdaFractional);
+            KempSmallMeanPoissonSampler.of(rng, lambdaFractional);
     }
 
     /**
diff --git a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/MarsagliaTsangWangDiscreteSampler.java b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/MarsagliaTsangWangDiscreteSampler.java
index 32bc12e..97b9228 100644
--- a/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/MarsagliaTsangWangDiscreteSampler.java
+++ b/commons-rng-sampling/src/main/java/org/apache/commons/rng/sampling/distribution/MarsagliaTsangWangDiscreteSampler.java
@@ -40,17 +40,16 @@ import org.apache.commons.rng.UniformRandomProvider;
  * <p>The sampler supports the following distributions:</p>
  *
  * <ul>
- *  <li>Any discrete probability distribution (probabilities must be provided)
+ *  <li>Enumerated distribution (probabilities must be provided for each sample)
  *  <li>Poisson distribution up to {@code mean = 1024}
  *  <li>Binomial distribution up to {@code trials = 65535}
  * </ul>
  *
- * @since 1.3
  * @see <a href="http://dx.doi.org/10.18637/jss.v011.i03">Margsglia, et al (2004) JSS Vol.
  * 11, Issue 3</a>
+ * @since 1.3
  */
-public abstract class MarsagliaTsangWangDiscreteSampler
-    implements SharedStateDiscreteSampler {
+public final class MarsagliaTsangWangDiscreteSampler {
     /** The value 2<sup>8</sup> as an {@code int}. */
     private static final int INT_8 = 1 << 8;
     /** The value 2<sup>16</sup> as an {@code int}. */
@@ -60,34 +59,6 @@ public abstract class MarsagliaTsangWangDiscreteSampler
     /** The value 2<sup>31</sup> as a {@code double}. */
     private static final double DOUBLE_31 = 1L << 31;
 
-    /** The general name of any discrete probability distribution. */
-    private static final String DISCRETE_NAME = "discrete";
-    /** The name of the Poisson distribution. */
-    private static final String POISSON_NAME = "Poisson";
-    /** The name of the Binomial distribution. */
-    private static final String BINOMIAL_NAME = "Binomial";
-
-    /**
-     * Upper bound on the mean for the Poisson distribution.
-     *
-     * <p>The original source code provided in Marsaglia, et al (2004) has no explicit
-     * limit but the code fails at mean >= 1941 as the transform to compute p(x=mode)
-     * produces infinity. Use a conservative limit of 1024.</p>
-     */
-    private static final double MAX_POISSON_MEAN = 1024;
-    /**
-     * The threshold for the mean of the Poisson distribution to switch the method used
-     * to compute the probabilities. This is taken from the example software provided by
-     * Marsaglia, et al (2004).
-     */
-    private static final double POISSON_MEAN_THRESHOLD = 21.4;
-
-    /** Underlying source of randomness. */
-    protected final UniformRandomProvider rng;
-
-    /** The name of the distribution. */
-    private final String distributionName;
-
     // =========================================================================
     // Implementation note:
     //
@@ -109,15 +80,55 @@ public abstract class MarsagliaTsangWangDiscreteSampler
     // when provided via an array of probabilities and the Poisson and Binomial
     // distributions for a restricted set of parameters. The restrictions are
     // imposed by the requirement to compute the entire probability distribution
-    // from the controlling parameter(s) using a recursive method.
+    // from the controlling parameter(s) using a recursive method. Factory
+    // constructors return a SharedStateDiscreteSampler instance. Each distribution
+    // type is contained in an inner class.
     // =========================================================================
 
     /**
+     * The base class for Marsaglia-Tsang-Wang samplers.
+     */
+    private abstract static class AbstractMarsagliaTsangWangDiscreteSampler
+            implements SharedStateDiscreteSampler {
+        /** Underlying source of randomness. */
+        protected final UniformRandomProvider rng;
+
+        /** The name of the distribution. */
+        private final String distributionName;
+
+        /**
+         * @param rng Generator of uniformly distributed random numbers.
+         * @param distributionName Distribution name.
+         */
+        AbstractMarsagliaTsangWangDiscreteSampler(UniformRandomProvider rng,
+                                                  String distributionName) {
+            this.rng = rng;
+            this.distributionName = distributionName;
+        }
+
+        /**
+         * @param rng Generator of uniformly distributed random numbers.
+         * @param source Source to copy.
+         */
+        AbstractMarsagliaTsangWangDiscreteSampler(UniformRandomProvider rng,
+                                                  AbstractMarsagliaTsangWangDiscreteSampler source) {
+            this.rng = rng;
+            this.distributionName = source.distributionName;
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public String toString() {
+            return "Marsaglia Tsang Wang " + distributionName + " deviate [" + rng.toString() + "]";
+        }
+    }
+
+    /**
      * An implementation for the sample algorithm based on the decomposition of the
      * index in the range {@code [0,2^30)} into 5 base-64 digits with 8-bit backing storage.
      */
     private static class MarsagliaTsangWangBase64Int8DiscreteSampler
-        extends MarsagliaTsangWangDiscreteSampler {
+        extends AbstractMarsagliaTsangWangDiscreteSampler {
         /** The mask to convert a {@code byte} to an unsigned 8-bit integer. */
         private static final int MASK = 0xff;
 
@@ -257,7 +268,7 @@ public abstract class MarsagliaTsangWangDiscreteSampler
      * index in the range {@code [0,2^30)} into 5 base-64 digits with 16-bit backing storage.
      */
     private static class MarsagliaTsangWangBase64Int16DiscreteSampler
-        extends MarsagliaTsangWangDiscreteSampler {
+        extends AbstractMarsagliaTsangWangDiscreteSampler {
         /** The mask to convert a {@code byte} to an unsigned 16-bit integer. */
         private static final int MASK = 0xffff;
 
@@ -397,7 +408,7 @@ public abstract class MarsagliaTsangWangDiscreteSampler
      * index in the range {@code [0,2^30)} into 5 base-64 digits with 32-bit backing storage.
      */
     private static class MarsagliaTsangWangBase64Int32DiscreteSampler
-        extends MarsagliaTsangWangDiscreteSampler {
+        extends AbstractMarsagliaTsangWangDiscreteSampler {
         /** Limit for look-up table 1. */
         private final int t1;
         /** Limit for look-up table 2. */
@@ -528,108 +539,10 @@ public abstract class MarsagliaTsangWangDiscreteSampler
         }
     }
 
-    /**
-     * Return a fixed result for the Binomial distribution. This is a special class to handle
-     * an edge case of probability of success equal to 0 or 1.
-     */
-    private static class MarsagliaTsangWangFixedResultBinomialSampler
-        extends MarsagliaTsangWangDiscreteSampler {
-        /** The result. */
-        private final int result;
-
-        /**
-         * @param result Result.
-         */
-        MarsagliaTsangWangFixedResultBinomialSampler(int result) {
-            super(null, BINOMIAL_NAME);
-            this.result = result;
-        }
-
-        @Override
-        public int sample() {
-            return result;
-        }
-
-        @Override
-        public String toString() {
-            return BINOMIAL_NAME + " deviate";
-        }
-
-        @Override
-        public SharedStateDiscreteSampler withUniformRandomProvider(UniformRandomProvider rng) {
-            // No shared state
-            return this;
-        }
-    }
-
-    /**
-     * Return an inversion result for the Binomial distribution. This assumes the
-     * following:
-     *
-     * <pre>
-     * Binomial(n, p) = 1 - Binomial(n, 1 - p)
-     * </pre>
-     */
-    private static class MarsagliaTsangWangInversionBinomialSampler
-        extends MarsagliaTsangWangDiscreteSampler {
-        /** The number of trials. */
-        private final int trials;
-        /** The Binomial distribution sampler. */
-        private final SharedStateDiscreteSampler sampler;
-
-        /**
-         * @param trials Number of trials.
-         * @param sampler Binomial distribution sampler.
-         */
-        MarsagliaTsangWangInversionBinomialSampler(int trials,
-                                                   SharedStateDiscreteSampler sampler) {
-            super(null, BINOMIAL_NAME);
-            this.trials = trials;
-            this.sampler = sampler;
-        }
-
-        @Override
-        public int sample() {
-            return trials - sampler.sample();
-        }
-
-        @Override
-        public String toString() {
-            return sampler.toString();
-        }
-
-        @Override
-        public SharedStateDiscreteSampler withUniformRandomProvider(UniformRandomProvider rng) {
-            return new MarsagliaTsangWangInversionBinomialSampler(this.trials,
-                this.sampler.withUniformRandomProvider(rng));
-        }
-    }
-
-    /**
-     * @param rng Generator of uniformly distributed random numbers.
-     * @param distributionName Distribution name.
-     */
-    MarsagliaTsangWangDiscreteSampler(UniformRandomProvider rng,
-                                      String distributionName) {
-        this.rng = rng;
-        this.distributionName = distributionName;
-    }
 
-    /**
-     * @param rng Generator of uniformly distributed random numbers.
-     * @param source Source to copy.
-     */
-    MarsagliaTsangWangDiscreteSampler(UniformRandomProvider rng,
-                                      MarsagliaTsangWangDiscreteSampler source) {
-        this.rng = rng;
-        this.distributionName = source.distributionName;
-    }
 
-    /** {@inheritDoc} */
-    @Override
-    public String toString() {
-        return "Marsaglia Tsang Wang " + distributionName + " deviate [" + rng.toString() + "]";
-    }
+    /** Class contains only static methods. */
+    private MarsagliaTsangWangDiscreteSampler() {}
 
     /**
      * Gets the k<sup>th</sup> base 64 digit of {@code m}.
@@ -643,6 +556,17 @@ public abstract class MarsagliaTsangWangDiscreteSampler
     }
 
     /**
+     * Convert the probability to an integer in the range [0,2^30]. This is the numerator of
+     * a fraction with assumed denominator 2<sup>30</sup>.
+     *
+     * @param p Probability.
+     * @return the fraction numerator
+     */
+    private static int toUnsignedInt30(double p) {
+        return (int) (p * INT_30 + 0.5);
+    }
+
+    /**
      * Create a new instance for probabilities {@code p(i)} where the sample value {@code x} is
      * {@code i + offset}.
      *
@@ -655,10 +579,10 @@ public abstract class MarsagliaTsangWangDiscreteSampler
      * @param offset The offset (must be positive).
      * @return Sampler.
      */
-    private static MarsagliaTsangWangDiscreteSampler createSampler(UniformRandomProvider rng,
-                                                                   String distributionName,
-                                                                   int[] prob,
-                                                                   int offset) {
+    private static SharedStateDiscreteSampler createSampler(UniformRandomProvider rng,
+                                                            String distributionName,
+                                                            int[] prob,
+                                                            int offset) {
         // Note: No argument checks for private method.
 
         // Choose implementation based on the maximum index
@@ -673,426 +597,545 @@ public abstract class MarsagliaTsangWangDiscreteSampler
     }
 
     // =========================================================================
-    // The following factory methods are the public API to construct a sampler for:
-    // - Any discrete probability distribution (from provided double[] probabilities)
+    // The following public classes provide factory methods to construct a sampler for:
+    // - Enumerated probability distribution (from provided double[] probabilities)
     // - Poisson distribution for mean <= 1024
     // - Binomial distribution for trials <= 65535
     // =========================================================================
 
     /**
-     * Creates a sampler for a given probability distribution.
-     *
-     * <p>The probabilities will be normalised using their sum. The only requirement
-     * is the sum is positive.</p>
-     *
-     * <p>The sum of the probabilities is normalised to 2<sup>30</sup>. Note that
-     * probabilities are adjusted to the nearest 1<sup>-30</sup> due to round-off during
-     * the normalisation conversion. Consequently any probability less than 2<sup>-31</sup>
-     * will not be observed in samples.</p>
-     *
-     * @param rng Generator of uniformly distributed random numbers.
-     * @param probabilities The list of probabilities.
-     * @return Sampler.
-     * @throws IllegalArgumentException if {@code probabilities} is null or empty, a
-     * probability is negative, infinite or {@code NaN}, or the sum of all
-     * probabilities is not strictly positive.
+     * Create a sampler for an enumerated distribution of {@code n} values each with an
+     * associated probability.
+     * The samples corresponding to each probability are assumed to be a natural sequence
+     * starting at zero.
      */
-    public static MarsagliaTsangWangDiscreteSampler createDiscreteDistribution(UniformRandomProvider rng,
-                                                                               double[] probabilities) {
-        return createSampler(rng, DISCRETE_NAME, normaliseProbabilities(probabilities), 0);
-    }
+    public static final class Enumerated {
+        /** The name of the enumerated probability distribution. */
+        private static final String ENUMERATED_NAME = "Enumerated";
 
-    /**
-     * Normalise the probabilities to integers that sum to 2<sup>30</sup>.
-     *
-     * @param probabilities The list of probabilities.
-     * @return the normalised probabilities.
-     * @throws IllegalArgumentException if {@code probabilities} is null or empty, a
-     * probability is negative, infinite or {@code NaN}, or the sum of all
-     * probabilities is not strictly positive.
-     */
-    private static int[] normaliseProbabilities(double[] probabilities) {
-        final double sumProb = InternalUtils.validateProbabilities(probabilities);
-
-        // Compute the normalisation: 2^30 / sum
-        final double normalisation = INT_30 / sumProb;
-        final int[] prob = new int[probabilities.length];
-        int sum = 0;
-        int max = 0;
-        int mode = 0;
-        for (int i = 0; i < prob.length; i++) {
-            // Add 0.5 for rounding
-            final int p = (int) (probabilities[i] * normalisation + 0.5);
-            sum += p;
-            // Find the mode (maximum probability)
-            if (max < p) {
-                max = p;
-                mode = i;
-            }
-            prob[i] = p;
-        }
+        /** Class contains only static methods. */
+        private Enumerated() {}
 
-        // The sum must be >= 2^30.
-        // Here just compensate the difference onto the highest probability.
-        prob[mode] += INT_30 - sum;
+        /**
+         * Creates a sampler for an enumerated distribution of {@code n} values each with an
+         * associated probability.
+         *
+         * <p>The probabilities will be normalised using their sum. The only requirement
+         * is the sum is positive.</p>
+         *
+         * <p>The sum of the probabilities is normalised to 2<sup>30</sup>. Note that
+         * probabilities are adjusted to the nearest 1<sup>-30</sup> due to round-off during
+         * the normalisation conversion. Consequently any probability less than 2<sup>-31</sup>
+         * will not be observed in samples.</p>
+         *
+         * @param rng Generator of uniformly distributed random numbers.
+         * @param probabilities The list of probabilities.
+         * @return Sampler.
+         * @throws IllegalArgumentException if {@code probabilities} is null or empty, a
+         * probability is negative, infinite or {@code NaN}, or the sum of all
+         * probabilities is not strictly positive.
+         */
+        public static SharedStateDiscreteSampler of(UniformRandomProvider rng,
+                                                    double[] probabilities) {
+            return createSampler(rng, ENUMERATED_NAME, normaliseProbabilities(probabilities), 0);
+        }
 
-        return prob;
-    }
+        /**
+         * Normalise the probabilities to integers that sum to 2<sup>30</sup>.
+         *
+         * @param probabilities The list of probabilities.
+         * @return the normalised probabilities.
+         * @throws IllegalArgumentException if {@code probabilities} is null or empty, a
+         * probability is negative, infinite or {@code NaN}, or the sum of all
+         * probabilities is not strictly positive.
+         */
+        private static int[] normaliseProbabilities(double[] probabilities) {
+            final double sumProb = InternalUtils.validateProbabilities(probabilities);
+
+            // Compute the normalisation: 2^30 / sum
+            final double normalisation = INT_30 / sumProb;
+            final int[] prob = new int[probabilities.length];
+            int sum = 0;
+            int max = 0;
+            int mode = 0;
+            for (int i = 0; i < prob.length; i++) {
+                // Add 0.5 for rounding
+                final int p = (int) (probabilities[i] * normalisation + 0.5);
+                sum += p;
+                // Find the mode (maximum probability)
+                if (max < p) {
+                    max = p;
+                    mode = i;
+                }
+                prob[i] = p;
+            }
 
-    /**
-     * Creates a sampler for the Poisson distribution.
-     *
-     * <p>Any probability less than 2<sup>-31</sup> will not be observed in samples.</p>
-     *
-     * <p>Storage requirements depend on the tabulated probability values. Example storage
-     * requirements are listed below.</p>
-     *
-     * <pre>
-     * mean      table size     kB
-     * 0.25      882            0.88
-     * 0.5       1135           1.14
-     * 1         1200           1.20
-     * 2         1451           1.45
-     * 4         1955           1.96
-     * 8         2961           2.96
-     * 16        4410           4.41
-     * 32        6115           6.11
-     * 64        8499           8.50
-     * 128       11528          11.53
-     * 256       15935          31.87
-     * 512       20912          41.82
-     * 1024      30614          61.23
-     * </pre>
-     *
-     * <p>Note: Storage changes to 2 bytes per index between {@code mean=128} and {@code mean=256}.</p>
-     *
-     * @param rng Generator of uniformly distributed random numbers.
-     * @param mean Mean.
-     * @return Sampler.
-     * @throws IllegalArgumentException if {@code mean <= 0} or {@code mean > 1024}.
-     */
-    public static MarsagliaTsangWangDiscreteSampler createPoissonDistribution(UniformRandomProvider rng,
-                                                                              double mean) {
-        validatePoissonDistributionParameters(mean);
-
-        // Create the distribution either from X=0 or from X=mode when the mean is high.
-        return mean < POISSON_MEAN_THRESHOLD ?
-            createPoissonDistributionFromX0(rng, mean) :
-            createPoissonDistributionFromXMode(rng, mean);
-    }
+            // The sum must be >= 2^30.
+            // Here just compensate the difference onto the highest probability.
+            prob[mode] += INT_30 - sum;
 
-    /**
-     * Validate the Poisson distribution parameters.
-     *
-     * @param mean Mean.
-     * @throws IllegalArgumentException if {@code mean <= 0} or {@code mean > 1024}.
-     */
-    private static void validatePoissonDistributionParameters(double mean) {
-        if (mean <= 0) {
-            throw new IllegalArgumentException("mean is not strictly positive: " + mean);
-        }
-        if (mean > MAX_POISSON_MEAN) {
-            throw new IllegalArgumentException("mean " + mean + " > " + MAX_POISSON_MEAN);
+            return prob;
         }
     }
 
     /**
-     * Creates the Poisson distribution by computing probabilities recursively from {@code X=0}.
-     *
-     * @param rng Generator of uniformly distributed random numbers.
-     * @param mean Mean.
-     * @return Sampler.
+     * Create a sampler for the Poisson distribution.
      */
-    private static MarsagliaTsangWangDiscreteSampler createPoissonDistributionFromX0(
-            UniformRandomProvider rng, double mean) {
-        final double p0 = Math.exp(-mean);
-
-        // Recursive update of Poisson probability until the value is too small
-        // p(x + 1) = p(x) * mean / (x + 1)
-        double p = p0;
-        int i;
-        for (i = 1; p * DOUBLE_31 >= 1; i++) {
-            p *= mean / i;
-        }
+    public static final class Poisson {
+        /** The name of the Poisson distribution. */
+        private static final String POISSON_NAME = "Poisson";
 
-        // Probabilities are 30-bit integers, assumed denominator 2^30
-        final int size = i - 1;
-        final int[] prob = new int[size];
-
-        p = p0;
-        prob[0] = toUnsignedInt30(p);
-        // The sum must exceed 2^30. In edges cases this is false due to round-off.
-        int sum = prob[0];
-        for (i = 1; i < prob.length; i++) {
-            p *= mean / i;
-            prob[i] = toUnsignedInt30(p);
-            sum += prob[i];
-        }
+        /**
+         * Upper bound on the mean for the Poisson distribution.
+         *
+         * <p>The original source code provided in Marsaglia, et al (2004) has no explicit
+         * limit but the code fails at mean >= 1941 as the transform to compute p(x=mode)
+         * produces infinity. Use a conservative limit of 1024.</p>
+         */
 
-        // If the sum is < 2^30 add the remaining sum to the mode (floor(mean)).
-        prob[(int) mean] += Math.max(0, INT_30 - sum);
+        private static final double MAX_MEAN = 1024;
+        /**
+         * The threshold for the mean of the Poisson distribution to switch the method used
+         * to compute the probabilities. This is taken from the example software provided by
+         * Marsaglia, et al (2004).
+         */
+        private static final double MEAN_THRESHOLD = 21.4;
 
-        // Note: offset = 0
-        return createSampler(rng, POISSON_NAME, prob, 0);
-    }
+        /** Class contains only static methods. */
+        private Poisson() {}
 
-    /**
-     * Creates the Poisson distribution by computing probabilities recursively upward and downward
-     * from {@code X=mode}, the location of the largest p-value.
-     *
-     * @param rng Generator of uniformly distributed random numbers.
-     * @param mean Mean.
-     * @return Sampler.
-     */
-    private static MarsagliaTsangWangDiscreteSampler createPoissonDistributionFromXMode(
-            UniformRandomProvider rng, double mean) {
-        // If mean >= 21.4, generate from largest p-value up, then largest down.
-        // The largest p-value will be at the mode (floor(mean)).
-
-        // Find p(x=mode)
-        final int mode = (int) mean;
-        // This transform is stable until mean >= 1941 where p will result in Infinity
-        // before the divisor i is large enough to start reducing the product (i.e. i > c).
-        final double c = mean * Math.exp(-mean / mode);
-        double p = 1.0;
-        int i;
-        for (i = 1; i <= mode; i++) {
-            p *= c / i;
+        /**
+         * Creates a sampler for the Poisson distribution.
+         *
+         * <p>Any probability less than 2<sup>-31</sup> will not be observed in samples.</p>
+         *
+         * <p>Storage requirements depend on the tabulated probability values. Example storage
+         * requirements are listed below.</p>
+         *
+         * <pre>
+         * mean      table size     kB
+         * 0.25      882            0.88
+         * 0.5       1135           1.14
+         * 1         1200           1.20
+         * 2         1451           1.45
+         * 4         1955           1.96
+         * 8         2961           2.96
+         * 16        4410           4.41
+         * 32        6115           6.11
+         * 64        8499           8.50
+         * 128       11528          11.53
+         * 256       15935          31.87
+         * 512       20912          41.82
+         * 1024      30614          61.23
+         * </pre>
+         *
+         * <p>Note: Storage changes to 2 bytes per index between {@code mean=128} and {@code mean=256}.</p>
+         *
+         * @param rng Generator of uniformly distributed random numbers.
+         * @param mean Mean.
+         * @return Sampler.
+         * @throws IllegalArgumentException if {@code mean <= 0} or {@code mean > 1024}.
+         */
+        public static SharedStateDiscreteSampler of(UniformRandomProvider rng,
+                                                    double mean) {
+            validatePoissonDistributionParameters(mean);
+
+            // Create the distribution either from X=0 or from X=mode when the mean is high.
+            return mean < MEAN_THRESHOLD ?
+                createPoissonDistributionFromX0(rng, mean) :
+                createPoissonDistributionFromXMode(rng, mean);
         }
-        final double pMode = p;
 
-        // Find the upper limit using recursive computation of the p-value.
-        // Note this will exit when i overflows to negative so no check on the range
-        for (i = mode + 1; p * DOUBLE_31 >= 1; i++) {
-            p *= mean / i;
-        }
-        final int last = i - 2;
-
-        // Find the lower limit using recursive computation of the p-value.
-        p = pMode;
-        int j = -1;
-        for (i = mode - 1; i >= 0; i--) {
-            p *= (i + 1) / mean;
-            if (p * DOUBLE_31 < 1) {
-                j = i;
-                break;
+        /**
+         * Validate the Poisson distribution parameters.
+         *
+         * @param mean Mean.
+         * @throws IllegalArgumentException if {@code mean <= 0} or {@code mean > 1024}.
+         */
+        private static void validatePoissonDistributionParameters(double mean) {
+            if (mean <= 0) {
+                throw new IllegalArgumentException("mean is not strictly positive: " + mean);
+            }
+            if (mean > MAX_MEAN) {
+                throw new IllegalArgumentException("mean " + mean + " > " + MAX_MEAN);
             }
         }
 
-        // Probabilities are 30-bit integers, assumed denominator 2^30.
-        // This is the minimum sample value: prob[x - offset] = p(x)
-        final int offset = j + 1;
-        final int size = last - offset + 1;
-        final int[] prob = new int[size];
-
-        p = pMode;
-        prob[mode - offset] = toUnsignedInt30(p);
-        // The sum must exceed 2^30. In edges cases this is false due to round-off.
-        int sum = prob[mode - offset];
-        // From mode to upper limit
-        for (i = mode + 1; i <= last; i++) {
-            p *= mean / i;
-            prob[i - offset] = toUnsignedInt30(p);
-            sum += prob[i - offset];
-        }
-        // From mode to lower limit
-        p = pMode;
-        for (i = mode - 1; i >= offset; i--) {
-            p *= (i + 1) / mean;
-            prob[i - offset] = toUnsignedInt30(p);
-            sum += prob[i - offset];
+        /**
+         * Creates the Poisson distribution by computing probabilities recursively from {@code X=0}.
+         *
+         * @param rng Generator of uniformly distributed random numbers.
+         * @param mean Mean.
+         * @return Sampler.
+         */
+        private static SharedStateDiscreteSampler createPoissonDistributionFromX0(
+                UniformRandomProvider rng, double mean) {
+            final double p0 = Math.exp(-mean);
+
+            // Recursive update of Poisson probability until the value is too small
+            // p(x + 1) = p(x) * mean / (x + 1)
+            double p = p0;
+            int i;
+            for (i = 1; p * DOUBLE_31 >= 1; i++) {
+                p *= mean / i;
+            }
+
+            // Probabilities are 30-bit integers, assumed denominator 2^30
+            final int size = i - 1;
+            final int[] prob = new int[size];
+
+            p = p0;
+            prob[0] = toUnsignedInt30(p);
+            // The sum must exceed 2^30. In edges cases this is false due to round-off.
+            int sum = prob[0];
+            for (i = 1; i < prob.length; i++) {
+                p *= mean / i;
+                prob[i] = toUnsignedInt30(p);
+                sum += prob[i];
+            }
+
+            // If the sum is < 2^30 add the remaining sum to the mode (floor(mean)).
+            prob[(int) mean] += Math.max(0, INT_30 - sum);
+
+            // Note: offset = 0
+            return createSampler(rng, POISSON_NAME, prob, 0);
         }
 
-        // If the sum is < 2^30 add the remaining sum to the mode.
-        // If above 2^30 then the effect is truncation of the long tail of the distribution.
-        prob[mode - offset] += Math.max(0, INT_30 - sum);
+        /**
+         * Creates the Poisson distribution by computing probabilities recursively upward and downward
+         * from {@code X=mode}, the location of the largest p-value.
+         *
+         * @param rng Generator of uniformly distributed random numbers.
+         * @param mean Mean.
+         * @return Sampler.
+         */
+        private static SharedStateDiscreteSampler createPoissonDistributionFromXMode(
+                UniformRandomProvider rng, double mean) {
+            // If mean >= 21.4, generate from largest p-value up, then largest down.
+            // The largest p-value will be at the mode (floor(mean)).
+
+            // Find p(x=mode)
+            final int mode = (int) mean;
+            // This transform is stable until mean >= 1941 where p will result in Infinity
+            // before the divisor i is large enough to start reducing the product (i.e. i > c).
+            final double c = mean * Math.exp(-mean / mode);
+            double p = 1.0;
+            int i;
+            for (i = 1; i <= mode; i++) {
+                p *= c / i;
+            }
+            final double pMode = p;
+
+            // Find the upper limit using recursive computation of the p-value.
+            // Note this will exit when i overflows to negative so no check on the range
+            for (i = mode + 1; p * DOUBLE_31 >= 1; i++) {
+                p *= mean / i;
+            }
+            final int last = i - 2;
+
+            // Find the lower limit using recursive computation of the p-value.
+            p = pMode;
+            int j = -1;
+            for (i = mode - 1; i >= 0; i--) {
+                p *= (i + 1) / mean;
+                if (p * DOUBLE_31 < 1) {
+                    j = i;
+                    break;
+                }
+            }
 
-        return createSampler(rng, POISSON_NAME, prob, offset);
+            // Probabilities are 30-bit integers, assumed denominator 2^30.
+            // This is the minimum sample value: prob[x - offset] = p(x)
+            final int offset = j + 1;
+            final int size = last - offset + 1;
+            final int[] prob = new int[size];
+
+            p = pMode;
+            prob[mode - offset] = toUnsignedInt30(p);
+            // The sum must exceed 2^30. In edges cases this is false due to round-off.
+            int sum = prob[mode - offset];
+            // From mode to upper limit
+            for (i = mode + 1; i <= last; i++) {
+                p *= mean / i;
+                prob[i - offset] = toUnsignedInt30(p);
+                sum += prob[i - offset];
+            }
+            // From mode to lower limit
+            p = pMode;
+            for (i = mode - 1; i >= offset; i--) {
+                p *= (i + 1) / mean;
+                prob[i - offset] = toUnsignedInt30(p);
+                sum += prob[i - offset];
+            }
+
+            // If the sum is < 2^30 add the remaining sum to the mode.
+            // If above 2^30 then the effect is truncation of the long tail of the distribution.
+            prob[mode - offset] += Math.max(0, INT_30 - sum);
+
+            return createSampler(rng, POISSON_NAME, prob, offset);
+        }
     }
 
     /**
-     * Creates a sampler for the Binomial distribution.
-     *
-     * <p>Any probability less than 2<sup>-31</sup> will not be observed in samples.</p>
-     *
-     * <p>Storage requirements depend on the tabulated probability values. Example storage
-     * requirements are listed below (in kB).</p>
-     *
-     * <pre>
-     *          p
-     * trials   0.5    0.1   0.01  0.001
-     *    4    0.06   0.63   0.44   0.44
-     *   16    0.69   1.14   0.76   0.44
-     *   64    4.73   2.40   1.14   0.51
-     *  256    8.63   5.17   1.89   0.82
-     * 1024   31.12   9.45   3.34   0.89
-     * </pre>
-     *
-     * <p>The method requires that the Binomial distribution probability at {@code x=0} can be computed.
-     * This will fail when {@code (1 - p)^trials == 0} which requires {@code trials} to be large
-     * and/or {@code p} to be small. In this case an exception is raised.</p>
-     *
-     * @param rng Generator of uniformly distributed random numbers.
-     * @param trials Number of trials.
-     * @param probabilityOfSuccess Probability of success (p).
-     * @return Sampler.
-     * @throws IllegalArgumentException if {@code trials < 0} or {@code trials >= 2^16},
-     * {@code p} is not in the range {@code [0-1]}, or the probability distribution cannot
-     * be computed.
+     * Create a sampler for the Binomial distribution.
      */
-    public static MarsagliaTsangWangDiscreteSampler createBinomialDistribution(UniformRandomProvider rng,
-                                                                               int trials,
-                                                                               double probabilityOfSuccess) {
-        validateBinomialDistributionParameters(trials, probabilityOfSuccess);
-
-        // Handle edge cases
-        if (probabilityOfSuccess == 0) {
-            return new MarsagliaTsangWangFixedResultBinomialSampler(0);
-        }
-        if (probabilityOfSuccess == 1) {
-            return new MarsagliaTsangWangFixedResultBinomialSampler(trials);
-        }
+    public static final class Binomial {
+        /** The name of the Binomial distribution. */
+        private static final String BINOMIAL_NAME = "Binomial";
+
+        /**
+         * Return a fixed result for the Binomial distribution. This is a special class to handle
+         * an edge case of probability of success equal to 0 or 1.
+         */
+        private static class MarsagliaTsangWangFixedResultBinomialSampler
+            extends AbstractMarsagliaTsangWangDiscreteSampler {
+            /** The result. */
+            private final int result;
+
+            /**
+             * @param result Result.
+             */
+            MarsagliaTsangWangFixedResultBinomialSampler(int result) {
+                super(null, BINOMIAL_NAME);
+                this.result = result;
+            }
 
-        // Check the supported size.
-        if (trials >= INT_16) {
-            throw new IllegalArgumentException("Unsupported number of trials: " + trials);
+            @Override
+            public int sample() {
+                return result;
+            }
+
+            @Override
+            public String toString() {
+                return BINOMIAL_NAME + " deviate";
+            }
+
+            @Override
+            public SharedStateDiscreteSampler withUniformRandomProvider(UniformRandomProvider rng) {
+                // No shared state
+                return this;
+            }
         }
 
-        return createBinomialDistributionSampler(rng, trials, probabilityOfSuccess);
-    }
+        /**
+         * Return an inversion result for the Binomial distribution. This assumes the
+         * following:
+         *
+         * <pre>
+         * Binomial(n, p) = 1 - Binomial(n, 1 - p)
+         * </pre>
+         */
+        private static class MarsagliaTsangWangInversionBinomialSampler
+            extends AbstractMarsagliaTsangWangDiscreteSampler {
+            /** The number of trials. */
+            private final int trials;
+            /** The Binomial distribution sampler. */
+            private final SharedStateDiscreteSampler sampler;
+
+            /**
+             * @param trials Number of trials.
+             * @param sampler Binomial distribution sampler.
+             */
+            MarsagliaTsangWangInversionBinomialSampler(int trials,
+                                                       SharedStateDiscreteSampler sampler) {
+                super(null, BINOMIAL_NAME);
+                this.trials = trials;
+                this.sampler = sampler;
+            }
 
-    /**
-     * Validate the Binomial distribution parameters.
-     *
-     * @param trials Number of trials.
-     * @param probabilityOfSuccess Probability of success (p).
-     * @throws IllegalArgumentException if {@code trials < 0} or
-     * {@code p} is not in the range {@code [0-1]}
-     */
-    private static void validateBinomialDistributionParameters(int trials, double probabilityOfSuccess) {
-        if (trials < 0) {
-            throw new IllegalArgumentException("Trials is not positive: " + trials);
+            @Override
+            public int sample() {
+                return trials - sampler.sample();
+            }
+
+            @Override
+            public String toString() {
+                return sampler.toString();
+            }
+
+            @Override
+            public SharedStateDiscreteSampler withUniformRandomProvider(UniformRandomProvider rng) {
+                return new MarsagliaTsangWangInversionBinomialSampler(this.trials,
+                    this.sampler.withUniformRandomProvider(rng));
+            }
         }
-        if (probabilityOfSuccess < 0 || probabilityOfSuccess > 1) {
-            throw new IllegalArgumentException("Probability is not in range [0,1]: " + probabilityOfSuccess);
+
+        /** Class contains only static methods. */
+        private Binomial() {}
+
+        /**
+         * Creates a sampler for the Binomial distribution.
+         *
+         * <p>Any probability less than 2<sup>-31</sup> will not be observed in samples.</p>
+         *
+         * <p>Storage requirements depend on the tabulated probability values. Example storage
+         * requirements are listed below (in kB).</p>
+         *
+         * <pre>
+         *          p
+         * trials   0.5    0.1   0.01  0.001
+         *    4    0.06   0.63   0.44   0.44
+         *   16    0.69   1.14   0.76   0.44
+         *   64    4.73   2.40   1.14   0.51
+         *  256    8.63   5.17   1.89   0.82
+         * 1024   31.12   9.45   3.34   0.89
+         * </pre>
+         *
+         * <p>The method requires that the Binomial distribution probability at {@code x=0} can be computed.
+         * This will fail when {@code (1 - p)^trials == 0} which requires {@code trials} to be large
+         * and/or {@code p} to be small. In this case an exception is raised.</p>
+         *
+         * @param rng Generator of uniformly distributed random numbers.
+         * @param trials Number of trials.
+         * @param probabilityOfSuccess Probability of success (p).
+         * @return Sampler.
+         * @throws IllegalArgumentException if {@code trials < 0} or {@code trials >= 2^16},
+         * {@code p} is not in the range {@code [0-1]}, or the probability distribution cannot
+         * be computed.
+         */
+        public static SharedStateDiscreteSampler of(UniformRandomProvider rng,
+                                                    int trials,
+                                                    double probabilityOfSuccess) {
+            validateBinomialDistributionParameters(trials, probabilityOfSuccess);
+
+            // Handle edge cases
+            if (probabilityOfSuccess == 0) {
+                return new MarsagliaTsangWangFixedResultBinomialSampler(0);
+            }
+            if (probabilityOfSuccess == 1) {
+                return new MarsagliaTsangWangFixedResultBinomialSampler(trials);
+            }
+
+            // Check the supported size.
+            if (trials >= INT_16) {
+                throw new IllegalArgumentException("Unsupported number of trials: " + trials);
+            }
+
+            return createBinomialDistributionSampler(rng, trials, probabilityOfSuccess);
         }
-    }
 
-    /**
-     * Creates the Binomial distribution sampler.
-     *
-     * <p>This assumes the parameters for the distribution are valid. The method
-     * will only fail if the initial probability for {@code X=0} is zero.</p>
-     *
-     * @param rng Generator of uniformly distributed random numbers.
-     * @param trials Number of trials.
-     * @param probabilityOfSuccess Probability of success (p).
-     * @return Sampler.
-     * @throws IllegalArgumentException if the probability distribution cannot be
-     * computed.
-     */
-    private static MarsagliaTsangWangDiscreteSampler createBinomialDistributionSampler(
-            UniformRandomProvider rng, int trials, double probabilityOfSuccess) {
-
-        // The maximum supported value for Math.exp is approximately -744.
-        // This occurs when trials is large and p is close to 1.
-        // Handle this by using an inversion: generate j=Binomial(n,1-p), return n-j
-        final boolean useInversion = probabilityOfSuccess > 0.5;
-        final double p = useInversion ? 1 - probabilityOfSuccess : probabilityOfSuccess;
-
-        // Check if the distribution can be computed
-        final double p0 = Math.exp(trials * Math.log(1 - p));
-        if (p0 < Double.MIN_VALUE) {
-            throw new IllegalArgumentException("Unable to compute distribution");
+        /**
+         * Validate the Binomial distribution parameters.
+         *
+         * @param trials Number of trials.
+         * @param probabilityOfSuccess Probability of success (p).
+         * @throws IllegalArgumentException if {@code trials < 0} or
+         * {@code p} is not in the range {@code [0-1]}
+         */
+        private static void validateBinomialDistributionParameters(int trials, double probabilityOfSuccess) {
+            if (trials < 0) {
+                throw new IllegalArgumentException("Trials is not positive: " + trials);
+            }
+            if (probabilityOfSuccess < 0 || probabilityOfSuccess > 1) {
+                throw new IllegalArgumentException("Probability is not in range [0,1]: " + probabilityOfSuccess);
+            }
         }
 
-        // First find size of probability array
-        double t = p0;
-        final double h = p / (1 - p);
-        // Find first probability above the threshold of 2^-31
-        int begin = 0;
-        if (t * DOUBLE_31 < 1) {
-            // Somewhere after p(0)
-            // Note:
-            // If this loop is entered p(0) is < 2^-31.
-            // This has been tested at the extreme for p(0)=Double.MIN_VALUE and either
-            // p=0.5 or trials=2^16-1 and does not fail to find the beginning.
-            for (int i = 1; i <= trials; i++) {
+        /**
+         * Creates the Binomial distribution sampler.
+         *
+         * <p>This assumes the parameters for the distribution are valid. The method
+         * will only fail if the initial probability for {@code X=0} is zero.</p>
+         *
+         * @param rng Generator of uniformly distributed random numbers.
+         * @param trials Number of trials.
+         * @param probabilityOfSuccess Probability of success (p).
+         * @return Sampler.
+         * @throws IllegalArgumentException if the probability distribution cannot be
+         * computed.
+         */
+        private static SharedStateDiscreteSampler createBinomialDistributionSampler(
+                UniformRandomProvider rng, int trials, double probabilityOfSuccess) {
+
+            // The maximum supported value for Math.exp is approximately -744.
+            // This occurs when trials is large and p is close to 1.
+            // Handle this by using an inversion: generate j=Binomial(n,1-p), return n-j
+            final boolean useInversion = probabilityOfSuccess > 0.5;
+            final double p = useInversion ? 1 - probabilityOfSuccess : probabilityOfSuccess;
+
+            // Check if the distribution can be computed
+            final double p0 = Math.exp(trials * Math.log(1 - p));
+            if (p0 < Double.MIN_VALUE) {
+                throw new IllegalArgumentException("Unable to compute distribution");
+            }
+
+            // First find size of probability array
+            double t = p0;
+            final double h = p / (1 - p);
+            // Find first probability above the threshold of 2^-31
+            int begin = 0;
+            if (t * DOUBLE_31 < 1) {
+                // Somewhere after p(0)
+                // Note:
+                // If this loop is entered p(0) is < 2^-31.
+                // This has been tested at the extreme for p(0)=Double.MIN_VALUE and either
+                // p=0.5 or trials=2^16-1 and does not fail to find the beginning.
+                for (int i = 1; i <= trials; i++) {
+                    t *= (trials + 1 - i) * h / i;
+                    if (t * DOUBLE_31 >= 1) {
+                        begin = i;
+                        break;
+                    }
+                }
+            }
+            // Find last probability
+            int end = trials;
+            for (int i = begin + 1; i <= trials; i++) {
                 t *= (trials + 1 - i) * h / i;
-                if (t * DOUBLE_31 >= 1) {
-                    begin = i;
+                if (t * DOUBLE_31 < 1) {
+                    end = i - 1;
                     break;
                 }
             }
-        }
-        // Find last probability
-        int end = trials;
-        for (int i = begin + 1; i <= trials; i++) {
-            t *= (trials + 1 - i) * h / i;
-            if (t * DOUBLE_31 < 1) {
-                end = i - 1;
-                break;
-            }
-        }
-
-        return createBinomialDistributionSamplerFromRange(rng, trials, p, useInversion,
-                p0, begin, end);
-    }
 
-    /**
-     * Creates the Binomial distribution sampler using only the probability values for {@code X}
-     * between the begin and the end (inclusive).
-     *
-     * @param rng Generator of uniformly distributed random numbers.
-     * @param trials Number of trials.
-     * @param p Probability of success (p).
-     * @param useInversion Set to {@code true} if the probability was inverted.
-     * @param p0 Probability at {@code X=0}
-     * @param begin Begin value {@code X} for the distribution.
-     * @param end End value {@code X} for the distribution.
-     * @return Sampler.
-     */
-    private static MarsagliaTsangWangDiscreteSampler createBinomialDistributionSamplerFromRange(
-            UniformRandomProvider rng, int trials, double p,
-            boolean useInversion, double p0, int begin, int end) {
-
-        // Assign probability values as 30-bit integers
-        final int size = end - begin + 1;
-        final int[] prob = new int[size];
-        double t = p0;
-        final double h = p / (1 - p);
-        for (int i = 1; i <= begin; i++) {
-            t *= (trials + 1 - i) * h / i;
-        }
-        int sum = toUnsignedInt30(t);
-        prob[0] = sum;
-        for (int i = begin + 1; i <= end; i++) {
-            t *= (trials + 1 - i) * h / i;
-            prob[i - begin] = toUnsignedInt30(t);
-            sum += prob[i - begin];
+            return createBinomialDistributionSamplerFromRange(rng, trials, p, useInversion,
+                    p0, begin, end);
         }
 
-        // If the sum is < 2^30 add the remaining sum to the mode (floor((n+1)p))).
-        // If above 2^30 then the effect is truncation of the long tail of the distribution.
-        final int mode = (int) ((trials + 1) * p) - begin;
-        prob[mode] += Math.max(0, INT_30 - sum);
+        /**
+         * Creates the Binomial distribution sampler using only the probability values for {@code X}
+         * between the begin and the end (inclusive).
+         *
+         * @param rng Generator of uniformly distributed random numbers.
+         * @param trials Number of trials.
+         * @param p Probability of success (p).
+         * @param useInversion Set to {@code true} if the probability was inverted.
+         * @param p0 Probability at {@code X=0}
+         * @param begin Begin value {@code X} for the distribution.
+         * @param end End value {@code X} for the distribution.
+         * @return Sampler.
+         */
+        private static SharedStateDiscreteSampler createBinomialDistributionSamplerFromRange(
+                UniformRandomProvider rng, int trials, double p,
+                boolean useInversion, double p0, int begin, int end) {
+
+            // Assign probability values as 30-bit integers
+            final int size = end - begin + 1;
+            final int[] prob = new int[size];
+            double t = p0;
+            final double h = p / (1 - p);
+            for (int i = 1; i <= begin; i++) {
+                t *= (trials + 1 - i) * h / i;
+            }
+            int sum = toUnsignedInt30(t);
+            prob[0] = sum;
+            for (int i = begin + 1; i <= end; i++) {
+                t *= (trials + 1 - i) * h / i;
+                prob[i - begin] = toUnsignedInt30(t);
+                sum += prob[i - begin];
+            }
 
-        final MarsagliaTsangWangDiscreteSampler sampler = createSampler(rng, BINOMIAL_NAME, prob, begin);
+            // If the sum is < 2^30 add the remaining sum to the mode (floor((n+1)p))).
+            // If above 2^30 then the effect is truncation of the long tail of the distribution.
+            final int mode = (int) ((trials + 1) * p) - begin;
+            prob[mode] += Math.max(0, INT_30 - sum);
 
-        // Check if an inversion was made
-        return useInversion ?
-               new MarsagliaTsangWangInversionBinomialSampler(trials, sampler) :
-               sampler;
-    }
+            final SharedStateDiscreteSampler sampler = createSampler(rng, BINOMIAL_NAME, prob, begin);
 
-    /**
-     * Convert the probability to an integer in the range [0,2^30]. This is the numerator of
-     * a fraction with assumed denominator 2<sup>30</sup>.
-     *
-     * @param p Probability.
-     * @return the fraction numerator
-     */
-    private static int toUnsignedInt30(double p) {
-        return (int) (p * INT_30 + 0.5);
+            // Check if an inversion was made
+            return useInversion ?
+                   new MarsagliaTsangWangInversionBinomialSampler(trials, sampler) :
+                   sampler;
+        }
     }
 }
diff --git a/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/AliasMethodDiscreteSamplerTest.java b/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/AliasMethodDiscreteSamplerTest.java
index e5f9108..0264c7c 100644
--- a/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/AliasMethodDiscreteSamplerTest.java
+++ b/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/AliasMethodDiscreteSamplerTest.java
@@ -68,7 +68,7 @@ public class AliasMethodDiscreteSamplerTest {
 
     @Test
     public void testToString() {
-        final AliasMethodDiscreteSampler sampler = createSampler(new double[] {0.5, 0.5});
+        final SharedStateDiscreteSampler sampler = createSampler(new double[] {0.5, 0.5});
         Assert.assertTrue(sampler.toString().toLowerCase().contains("alias method"));
     }
 
@@ -78,9 +78,9 @@ public class AliasMethodDiscreteSamplerTest {
      * @param probabilities the probabilities
      * @return the alias method discrete sampler
      */
-    private static AliasMethodDiscreteSampler createSampler(double[] probabilities) {
+    private static SharedStateDiscreteSampler createSampler(double[] probabilities) {
         final UniformRandomProvider rng = RandomSource.create(RandomSource.SPLIT_MIX_64);
-        return AliasMethodDiscreteSampler.create(rng, probabilities, -1);
+        return AliasMethodDiscreteSampler.of(rng, probabilities, -1);
     }
 
     /**
@@ -130,7 +130,7 @@ public class AliasMethodDiscreteSamplerTest {
     @Test
     public void testNonUniformSamplesWithProbabilitiesWithDefaultFactoryConstructor() {
         final double[] expected = {0.1, 0.2, 0.3, 0.1, 0.3 };
-        checkSamples(AliasMethodDiscreteSampler.create(RandomSource.create(RandomSource.SPLIT_MIX_64), expected), expected);
+        checkSamples(AliasMethodDiscreteSampler.of(RandomSource.create(RandomSource.SPLIT_MIX_64), expected), expected);
     }
 
     /**
@@ -218,7 +218,7 @@ public class AliasMethodDiscreteSamplerTest {
      *
      * @param expected the expected probabilities
      */
-    private static void checkSamples(AliasMethodDiscreteSampler sampler, double[] probabilies) {
+    private static void checkSamples(SharedStateDiscreteSampler sampler, double[] probabilies) {
         final int numberOfSamples = 10000;
         final long[] samples = new long[probabilies.length];
         for (int i = 0; i < numberOfSamples; i++) {
@@ -275,8 +275,8 @@ public class AliasMethodDiscreteSamplerTest {
         final UniformRandomProvider rng1 = RandomSource.create(RandomSource.SPLIT_MIX_64, 0L);
         final UniformRandomProvider rng2 = RandomSource.create(RandomSource.SPLIT_MIX_64, 0L);
         // Use negative alpha to disable padding
-        final AliasMethodDiscreteSampler sampler1 =
-            AliasMethodDiscreteSampler.create(rng1, probabilities, -1);
+        final SharedStateDiscreteSampler sampler1 =
+            AliasMethodDiscreteSampler.of(rng1, probabilities, -1);
         final SharedStateDiscreteSampler sampler2 = sampler1.withUniformRandomProvider(rng2);
         RandomAssert.assertProduceSameSequence(sampler1, sampler2);
     }
diff --git a/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/DiscreteSamplersList.java b/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/DiscreteSamplersList.java
index f6fe40e..73c7cb7 100644
--- a/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/DiscreteSamplersList.java
+++ b/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/DiscreteSamplersList.java
@@ -53,12 +53,12 @@ public final class DiscreteSamplersList {
             add(LIST, new org.apache.commons.math3.distribution.BinomialDistribution(unusedRng, trialsBinomial, probSuccessBinomial),
                 // range [9,16]
                 MathArrays.sequence(8, 9, 1),
-                MarsagliaTsangWangDiscreteSampler.createBinomialDistribution(RandomSource.create(RandomSource.WELL_19937_A), trialsBinomial, probSuccessBinomial));
+                MarsagliaTsangWangDiscreteSampler.Binomial.of(RandomSource.create(RandomSource.WELL_19937_A), trialsBinomial, probSuccessBinomial));
             // Inverted
             add(LIST, new org.apache.commons.math3.distribution.BinomialDistribution(unusedRng, trialsBinomial, 1 - probSuccessBinomial),
                 // range [4,11] = [20-16, 20-9]
                 MathArrays.sequence(8, 4, 1),
-                MarsagliaTsangWangDiscreteSampler.createBinomialDistribution(RandomSource.create(RandomSource.WELL_19937_C), trialsBinomial, 1 - probSuccessBinomial));
+                MarsagliaTsangWangDiscreteSampler.Binomial.of(RandomSource.create(RandomSource.WELL_19937_C), trialsBinomial, 1 - probSuccessBinomial));
 
             // Geometric ("inverse method").
             final double probSuccessGeometric = 0.21;
@@ -68,7 +68,7 @@ public final class DiscreteSamplersList {
             // Geometric.
             add(LIST, new org.apache.commons.math3.distribution.GeometricDistribution(unusedRng, probSuccessGeometric),
                 MathArrays.sequence(10, 0, 1),
-                new GeometricSampler(RandomSource.create(RandomSource.XOR_SHIFT_1024_S), probSuccessGeometric));
+                GeometricSampler.of(RandomSource.create(RandomSource.XOR_SHIFT_1024_S), probSuccessGeometric));
 
             // Hypergeometric ("inverse method").
             final int popSizeHyper = 34;
@@ -136,10 +136,10 @@ public final class DiscreteSamplersList {
                 new SmallMeanPoissonSampler(RandomSource.create(RandomSource.XO_SHI_RO_256_PLUS), meanPoisson));
             add(LIST, new org.apache.commons.math3.distribution.PoissonDistribution(unusedRng, meanPoisson, epsilonPoisson, maxIterationsPoisson),
                 MathArrays.sequence(10, 0, 1),
-                new KempSmallMeanPoissonSampler(RandomSource.create(RandomSource.XO_SHI_RO_128_PLUS), meanPoisson));
+                KempSmallMeanPoissonSampler.of(RandomSource.create(RandomSource.XO_SHI_RO_128_PLUS), meanPoisson));
             add(LIST, new org.apache.commons.math3.distribution.PoissonDistribution(unusedRng, meanPoisson, epsilonPoisson, maxIterationsPoisson),
                     MathArrays.sequence(10, 0, 1),
-                    MarsagliaTsangWangDiscreteSampler.createPoissonDistribution(RandomSource.create(RandomSource.XO_SHI_RO_128_PLUS), meanPoisson));
+                    MarsagliaTsangWangDiscreteSampler.Poisson.of(RandomSource.create(RandomSource.XO_SHI_RO_128_PLUS), meanPoisson));
             // Poisson (40 < mean < 80).
             final double largeMeanPoisson = 67.89;
             add(LIST, new org.apache.commons.math3.distribution.PoissonDistribution(unusedRng, largeMeanPoisson, epsilonPoisson, maxIterationsPoisson),
@@ -151,7 +151,7 @@ public final class DiscreteSamplersList {
                 new LargeMeanPoissonSampler(RandomSource.create(RandomSource.SPLIT_MIX_64), largeMeanPoisson));
             add(LIST, new org.apache.commons.math3.distribution.PoissonDistribution(unusedRng, largeMeanPoisson, epsilonPoisson, maxIterationsPoisson),
                 MathArrays.sequence(50, (int) (largeMeanPoisson - 25), 1),
-                MarsagliaTsangWangDiscreteSampler.createPoissonDistribution(RandomSource.create(RandomSource.XO_RO_SHI_RO_128_PLUS), largeMeanPoisson));
+                MarsagliaTsangWangDiscreteSampler.Poisson.of(RandomSource.create(RandomSource.XO_RO_SHI_RO_128_PLUS), largeMeanPoisson));
             // Poisson (mean >> 40).
             final double veryLargeMeanPoisson = 543.21;
             add(LIST, new org.apache.commons.math3.distribution.PoissonDistribution(unusedRng, veryLargeMeanPoisson, epsilonPoisson, maxIterationsPoisson),
@@ -163,16 +163,16 @@ public final class DiscreteSamplersList {
                 new LargeMeanPoissonSampler(RandomSource.create(RandomSource.SPLIT_MIX_64), veryLargeMeanPoisson));
             add(LIST, new org.apache.commons.math3.distribution.PoissonDistribution(unusedRng, veryLargeMeanPoisson, epsilonPoisson, maxIterationsPoisson),
                 MathArrays.sequence(100, (int) (veryLargeMeanPoisson - 50), 1),
-                MarsagliaTsangWangDiscreteSampler.createPoissonDistribution(RandomSource.create(RandomSource.XO_RO_SHI_RO_64_SS), veryLargeMeanPoisson));
+                MarsagliaTsangWangDiscreteSampler.Poisson.of(RandomSource.create(RandomSource.XO_RO_SHI_RO_64_SS), veryLargeMeanPoisson));
 
             // Any discrete distribution
             final double[] discreteProbabilities = new double[] {0.1, 0.2, 0.3, 0.4, 0.5};
             add(LIST, discreteProbabilities,
-                MarsagliaTsangWangDiscreteSampler.createDiscreteDistribution(RandomSource.create(RandomSource.XO_SHI_RO_512_PLUS), discreteProbabilities));
+                MarsagliaTsangWangDiscreteSampler.Enumerated.of(RandomSource.create(RandomSource.XO_SHI_RO_512_PLUS), discreteProbabilities));
             add(LIST, discreteProbabilities,
-                new GuideTableDiscreteSampler(RandomSource.create(RandomSource.XO_SHI_RO_512_SS), discreteProbabilities));
+                GuideTableDiscreteSampler.of(RandomSource.create(RandomSource.XO_SHI_RO_512_SS), discreteProbabilities));
             add(LIST, discreteProbabilities,
-                    AliasMethodDiscreteSampler.create(RandomSource.create(RandomSource.KISS), discreteProbabilities));
+                    AliasMethodDiscreteSampler.of(RandomSource.create(RandomSource.KISS), discreteProbabilities));
         } catch (Exception e) {
             // CHECKSTYLE: stop Regexp
             System.err.println("Unexpected exception while creating the list of samplers: " + e);
diff --git a/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/GeometricSamplerTest.java b/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/GeometricSamplerTest.java
index f384485..baaa77f 100644
--- a/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/GeometricSamplerTest.java
+++ b/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/GeometricSamplerTest.java
@@ -33,7 +33,7 @@ public class GeometricSamplerTest {
     @Test
     public void testProbabilityOfSuccessIsOneGeneratesZeroForSamples() {
         final UniformRandomProvider rng = RandomSource.create(RandomSource.SPLIT_MIX_64);
-        final GeometricSampler sampler = new GeometricSampler(rng, 1);
+        final SharedStateDiscreteSampler sampler = GeometricSampler.of(rng, 1);
         // All samples should be 0
         for (int i = 0; i < 10; i++) {
             Assert.assertEquals("p=1 should have 0 for all samples", 0, sampler.sample());
@@ -56,7 +56,7 @@ public class GeometricSamplerTest {
         // The internal exponential sampler validates the mean so demonstrate creating a
         // geometric sampler does not throw.
         final UniformRandomProvider rng = RandomSource.create(RandomSource.SPLIT_MIX_64);
-        new GeometricSampler(rng, probabilityOfSuccess);
+        GeometricSampler.of(rng, probabilityOfSuccess);
     }
 
     /**
@@ -66,7 +66,7 @@ public class GeometricSamplerTest {
     @Test
     public void testProbabilityOfSuccessIsOneSamplerToString() {
         final UniformRandomProvider unusedRng = RandomSource.create(RandomSource.SPLIT_MIX_64);
-        final GeometricSampler sampler = new GeometricSampler(unusedRng, 1);
+        final SharedStateDiscreteSampler sampler = GeometricSampler.of(unusedRng, 1);
         Assert.assertTrue("Missing 'Geometric' from toString",
             sampler.toString().contains("Geometric"));
     }
@@ -83,7 +83,7 @@ public class GeometricSamplerTest {
     @Test
     public void testProbabilityOfSuccessIsAlmostZeroGeneratesMaxValueForSamples() {
         final UniformRandomProvider rng = RandomSource.create(RandomSource.SPLIT_MIX_64);
-        final GeometricSampler sampler = new GeometricSampler(rng, Double.MIN_VALUE);
+        final SharedStateDiscreteSampler sampler = GeometricSampler.of(rng, Double.MIN_VALUE);
         // All samples should be max value
         for (int i = 0; i < 10; i++) {
             Assert.assertEquals("p=(almost 0) should have Integer.MAX_VALUE for all samples",
@@ -98,8 +98,7 @@ public class GeometricSamplerTest {
     public void testProbabilityOfSuccessAboveOneThrows() {
         final UniformRandomProvider unusedRng = RandomSource.create(RandomSource.SPLIT_MIX_64);
         final double probabilityOfSuccess = Math.nextUp(1.0);
-        @SuppressWarnings("unused")
-        final GeometricSampler sampler = new GeometricSampler(unusedRng, probabilityOfSuccess);
+        GeometricSampler.of(unusedRng, probabilityOfSuccess);
     }
 
     /**
@@ -109,8 +108,7 @@ public class GeometricSamplerTest {
     public void testProbabilityOfSuccessIsZeroThrows() {
         final UniformRandomProvider unusedRng = RandomSource.create(RandomSource.SPLIT_MIX_64);
         final double probabilityOfSuccess = 0;
-        @SuppressWarnings("unused")
-        final GeometricSampler sampler = new GeometricSampler(unusedRng, probabilityOfSuccess);
+        GeometricSampler.of(unusedRng, probabilityOfSuccess);
     }
 
     /**
@@ -138,8 +136,8 @@ public class GeometricSamplerTest {
     private static void testSharedStateSampler(double probabilityOfSuccess) {
         final UniformRandomProvider rng1 = RandomSource.create(RandomSource.SPLIT_MIX_64, 0L);
         final UniformRandomProvider rng2 = RandomSource.create(RandomSource.SPLIT_MIX_64, 0L);
-        final GeometricSampler sampler1 =
-            new GeometricSampler(rng1, probabilityOfSuccess);
+        final SharedStateDiscreteSampler sampler1 =
+            GeometricSampler.of(rng1, probabilityOfSuccess);
         final SharedStateDiscreteSampler sampler2 = sampler1.withUniformRandomProvider(rng2);
         RandomAssert.assertProduceSameSequence(sampler1, sampler2);
     }
diff --git a/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/GuideTableDiscreteSamplerTest.java b/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/GuideTableDiscreteSamplerTest.java
index e38b3db..fc29e0e 100644
--- a/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/GuideTableDiscreteSamplerTest.java
+++ b/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/GuideTableDiscreteSamplerTest.java
@@ -76,7 +76,7 @@ public class GuideTableDiscreteSamplerTest {
 
     @Test
     public void testToString() {
-        final GuideTableDiscreteSampler sampler = createSampler(new double[] {0.5, 0.5}, 1.0);
+        final SharedStateDiscreteSampler sampler = createSampler(new double[] {0.5, 0.5}, 1.0);
         Assert.assertTrue(sampler.toString().toLowerCase().contains("guide table"));
     }
 
@@ -86,9 +86,9 @@ public class GuideTableDiscreteSamplerTest {
      * @param probabilities the probabilities
      * @return the alias method discrete sampler
      */
-    private static GuideTableDiscreteSampler createSampler(double[] probabilities, double alpha) {
+    private static SharedStateDiscreteSampler createSampler(double[] probabilities, double alpha) {
         final UniformRandomProvider rng = RandomSource.create(RandomSource.SPLIT_MIX_64);
-        return new GuideTableDiscreteSampler(rng, probabilities, alpha);
+        return GuideTableDiscreteSampler.of(rng, probabilities, alpha);
     }
 
     /**
@@ -201,7 +201,7 @@ public class GuideTableDiscreteSamplerTest {
      * @param alpha the alpha
      */
     private static void checkSamples(double[] probabilies, double alpha) {
-        final GuideTableDiscreteSampler sampler = createSampler(probabilies, alpha);
+        final SharedStateDiscreteSampler sampler = createSampler(probabilies, alpha);
 
         final int numberOfSamples = 10000;
         final long[] samples = new long[probabilies.length];
@@ -244,8 +244,8 @@ public class GuideTableDiscreteSamplerTest {
         final UniformRandomProvider rng1 = RandomSource.create(RandomSource.SPLIT_MIX_64, 0L);
         final UniformRandomProvider rng2 = RandomSource.create(RandomSource.SPLIT_MIX_64, 0L);
         final double[] probabilities = {0.1, 0, 0.2, 0.3, 0.1, 0.3, 0};
-        final GuideTableDiscreteSampler sampler1 =
-            new GuideTableDiscreteSampler(rng1, probabilities);
+        final SharedStateDiscreteSampler sampler1 =
+            GuideTableDiscreteSampler.of(rng1, probabilities);
         final SharedStateDiscreteSampler sampler2 = sampler1.withUniformRandomProvider(rng2);
         RandomAssert.assertProduceSameSequence(sampler1, sampler2);
     }
diff --git a/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/KempSmallMeanPoissonSamplerTest.java b/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/KempSmallMeanPoissonSamplerTest.java
index 2796ff0..cc0b7c8 100644
--- a/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/KempSmallMeanPoissonSamplerTest.java
+++ b/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/KempSmallMeanPoissonSamplerTest.java
@@ -46,7 +46,7 @@ public class KempSmallMeanPoissonSamplerTest {
     public void testConstructorThrowsWithMeanLargerThanUpperBound() {
         final double mean = SUPPORTED_UPPER_BOUND + 1;
         @SuppressWarnings("unused")
-        KempSmallMeanPoissonSampler sampler = new KempSmallMeanPoissonSampler(dummyRng, mean);
+        SharedStateDiscreteSampler sampler = KempSmallMeanPoissonSampler.of(dummyRng, mean);
     }
 
     /**
@@ -56,7 +56,7 @@ public class KempSmallMeanPoissonSamplerTest {
     public void testConstructorThrowsWithZeroMean() {
         final double mean = 0;
         @SuppressWarnings("unused")
-        KempSmallMeanPoissonSampler sampler = new KempSmallMeanPoissonSampler(dummyRng, mean);
+        SharedStateDiscreteSampler sampler = KempSmallMeanPoissonSampler.of(dummyRng, mean);
     }
 
     /**
@@ -66,7 +66,7 @@ public class KempSmallMeanPoissonSamplerTest {
     public void testConstructorThrowsWithNegativeMean() {
         final double mean = -1;
         @SuppressWarnings("unused")
-        KempSmallMeanPoissonSampler sampler = new KempSmallMeanPoissonSampler(dummyRng, mean);
+        SharedStateDiscreteSampler sampler = KempSmallMeanPoissonSampler.of(dummyRng, mean);
     }
 
     /**
@@ -76,7 +76,7 @@ public class KempSmallMeanPoissonSamplerTest {
     public void testConstructorWithNaNMean() {
         final double mean = Double.NaN;
         @SuppressWarnings("unused")
-        KempSmallMeanPoissonSampler sampler = new KempSmallMeanPoissonSampler(dummyRng, mean);
+        SharedStateDiscreteSampler sampler = KempSmallMeanPoissonSampler.of(dummyRng, mean);
     }
 
     /**
@@ -129,7 +129,7 @@ public class KempSmallMeanPoissonSamplerTest {
             PoissonDistribution.DEFAULT_EPSILON, PoissonDistribution.DEFAULT_MAX_ITERATIONS);
 
         final FixedRNG rng = new FixedRNG(0);
-        final KempSmallMeanPoissonSampler sampler = new KempSmallMeanPoissonSampler(rng, mean);
+        final SharedStateDiscreteSampler sampler = KempSmallMeanPoissonSampler.of(rng, mean);
 
         // Lower bound should be zero
         testSample(rng, sampler, 0, 0, 0);
@@ -154,8 +154,8 @@ public class KempSmallMeanPoissonSamplerTest {
         final UniformRandomProvider rng1 = RandomSource.create(RandomSource.SPLIT_MIX_64, 0L);
         final UniformRandomProvider rng2 = RandomSource.create(RandomSource.SPLIT_MIX_64, 0L);
         final double mean = 1.23;
-        final KempSmallMeanPoissonSampler sampler1 =
-            new KempSmallMeanPoissonSampler(rng1, mean);
+        final SharedStateDiscreteSampler sampler1 =
+            KempSmallMeanPoissonSampler.of(rng1, mean);
         final SharedStateDiscreteSampler sampler2 = sampler1.withUniformRandomProvider(rng2);
         RandomAssert.assertProduceSameSequence(sampler1, sampler2);
     }
@@ -169,7 +169,7 @@ public class KempSmallMeanPoissonSamplerTest {
      * @param lower the expected lower limit
      * @param upper the expected upper limit
      */
-    private static void testSample(FixedRNG rng, KempSmallMeanPoissonSampler sampler, double cumulativeProbability,
+    private static void testSample(FixedRNG rng, SharedStateDiscreteSampler sampler, double cumulativeProbability,
         int lower, int upper) {
         rng.setValue(cumulativeProbability);
         final int sample = sampler.sample();
diff --git a/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/MarsagliaTsangWangDiscreteSamplerTest.java b/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/MarsagliaTsangWangDiscreteSamplerTest.java
index 5f2dd87..4b05b1e 100644
--- a/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/MarsagliaTsangWangDiscreteSamplerTest.java
+++ b/commons-rng-sampling/src/test/java/org/apache/commons/rng/sampling/distribution/MarsagliaTsangWangDiscreteSamplerTest.java
@@ -85,9 +85,9 @@ public class MarsagliaTsangWangDiscreteSamplerTest {
      * @param probabilities Probabilities.
      * @return the sampler
      */
-    private static MarsagliaTsangWangDiscreteSampler createDiscreteDistributionSampler(double[] probabilities) {
+    private static SharedStateDiscreteSampler createDiscreteDistributionSampler(double[] probabilities) {
         final UniformRandomProvider rng = new SplitMix64(0L);
-        return MarsagliaTsangWangDiscreteSampler.createDiscreteDistribution(rng, probabilities);
+        return MarsagliaTsangWangDiscreteSampler.Enumerated.of(rng, probabilities);
     }
 
     // Sampling tests
@@ -146,9 +146,9 @@ public class MarsagliaTsangWangDiscreteSamplerTest {
         final double[] p2 = createProbabilities(offset2, prob);
         final double[] p3 = createProbabilities(offset3, prob);
 
-        final MarsagliaTsangWangDiscreteSampler sampler1 = MarsagliaTsangWangDiscreteSampler.createDiscreteDistribution(rng1, p1);
-        final MarsagliaTsangWangDiscreteSampler sampler2 = MarsagliaTsangWangDiscreteSampler.createDiscreteDistribution(rng2, p2);
-        final MarsagliaTsangWangDiscreteSampler sampler3 = MarsagliaTsangWangDiscreteSampler.createDiscreteDistribution(rng3, p3);
+        final SharedStateDiscreteSampler sampler1 = MarsagliaTsangWangDiscreteSampler.Enumerated.of(rng1, p1);
+        final SharedStateDiscreteSampler sampler2 = MarsagliaTsangWangDiscreteSampler.Enumerated.of(rng2, p2);
+        final SharedStateDiscreteSampler sampler3 = MarsagliaTsangWangDiscreteSampler.Enumerated.of(rng3, p3);
 
         for (int i = 0; i < values.length; i++) {
             // Remove offsets
@@ -189,12 +189,12 @@ public class MarsagliaTsangWangDiscreteSamplerTest {
 
         // First test the table is completely filled to 2^30
         final UniformRandomProvider dummyRng = new FixedSequenceIntProvider(new int[] {0xffffffff});
-        final MarsagliaTsangWangDiscreteSampler dummySampler = MarsagliaTsangWangDiscreteSampler.createDiscreteDistribution(dummyRng, probabilities);
+        final SharedStateDiscreteSampler dummySampler = MarsagliaTsangWangDiscreteSampler.Enumerated.of(dummyRng, probabilities);
         // This will throw if the table is incomplete as it hits the upper limit
         dummySampler.sample();
 
         // Do a test of the actual sampler
-        final MarsagliaTsangWangDiscreteSampler sampler = MarsagliaTsangWangDiscreteSampler.createDiscreteDistribution(rng, probabilities);
+        final SharedStateDiscreteSampler sampler = MarsagliaTsangWangDiscreteSampler.Enumerated.of(rng, probabilities);
 
         final int numberOfSamples = 10000;
         final long[] samples = new long[probabilities.length];
@@ -300,7 +300,7 @@ public class MarsagliaTsangWangDiscreteSamplerTest {
         final UniformRandomProvider rng = new FixedRNG();
         final double mean = 1025;
         @SuppressWarnings("unused")
-        final DiscreteSampler sampler = MarsagliaTsangWangDiscreteSampler.createPoissonDistribution(rng, mean);
+        final DiscreteSampler sampler = MarsagliaTsangWangDiscreteSampler.Poisson.of(rng, mean);
     }
 
     /**
@@ -311,7 +311,7 @@ public class MarsagliaTsangWangDiscreteSamplerTest {
         final UniformRandomProvider rng = new FixedRNG();
         final double mean = 0;
         @SuppressWarnings("unused")
-        final DiscreteSampler sampler = MarsagliaTsangWangDiscreteSampler.createPoissonDistribution(rng, mean);
+        final DiscreteSampler sampler = MarsagliaTsangWangDiscreteSampler.Poisson.of(rng, mean);
     }
 
     /**
@@ -322,7 +322,7 @@ public class MarsagliaTsangWangDiscreteSamplerTest {
         final UniformRandomProvider rng = new FixedRNG();
         final double mean = 1024;
         @SuppressWarnings("unused")
-        final DiscreteSampler sampler = MarsagliaTsangWangDiscreteSampler.createPoissonDistribution(rng, mean);
+        final DiscreteSampler sampler = MarsagliaTsangWangDiscreteSampler.Poisson.of(rng, mean);
     }
 
     /**
@@ -333,7 +333,7 @@ public class MarsagliaTsangWangDiscreteSamplerTest {
     public void testCreatePoissonDistributionWithSmallMean() {
         final UniformRandomProvider rng = new FixedRNG();
         final double mean = 0.25;
-        final DiscreteSampler sampler = MarsagliaTsangWangDiscreteSampler.createPoissonDistribution(rng, mean);
+        final DiscreteSampler sampler = MarsagliaTsangWangDiscreteSampler.Poisson.of(rng, mean);
         // This will throw if the table does not sum to 2^30
         sampler.sample();
     }
@@ -347,7 +347,7 @@ public class MarsagliaTsangWangDiscreteSamplerTest {
     public void testCreatePoissonDistributionWithMediumMean() {
         final UniformRandomProvider rng = new FixedRNG();
         final double mean = 21.4;
-        final DiscreteSampler sampler = MarsagliaTsangWangDiscreteSampler.createPoissonDistribution(rng, mean);
+        final DiscreteSampler sampler = MarsagliaTsangWangDiscreteSampler.Poisson.of(rng, mean);
         // This will throw if the table does not sum to 2^30
         sampler.sample();
     }
@@ -361,7 +361,7 @@ public class MarsagliaTsangWangDiscreteSamplerTest {
         final int trials = -1;
         final double p = 0.5;
         @SuppressWarnings("unused")
-        final DiscreteSampler sampler = MarsagliaTsangWangDiscreteSampler.createBinomialDistribution(rng, trials, p);
+        final DiscreteSampler sampler = MarsagliaTsangWangDiscreteSampler.Binomial.of(rng, trials, p);
     }
 
     /**
@@ -373,7 +373,7 @@ public class MarsagliaTsangWangDiscreteSamplerTest {
         final int trials = 1 << 16; // 2^16
         final double p = 0.5;
         @SuppressWarnings("unused")
-        final DiscreteSampler sampler = MarsagliaTsangWangDiscreteSampler.createBinomialDistribution(rng, trials, p);
+        final DiscreteSampler sampler = MarsagliaTsangWangDiscreteSampler.Binomial.of(rng, trials, p);
     }
 
     /**
@@ -385,7 +385,7 @@ public class MarsagliaTsangWangDiscreteSamplerTest {
         final int trials = 1;
         final double p = -0.5;
         @SuppressWarnings("unused")
-        final DiscreteSampler sampler = MarsagliaTsangWangDiscreteSampler.createBinomialDistribution(rng, trials, p);
+        final DiscreteSampler sampler = MarsagliaTsangWangDiscreteSampler.Binomial.of(rng, trials, p);
     }
 
     /**
@@ -397,7 +397,7 @@ public class MarsagliaTsangWangDiscreteSamplerTest {
         final int trials = 1;
         final double p = 1.5;
         @SuppressWarnings("unused")
-        final DiscreteSampler sampler = MarsagliaTsangWangDiscreteSampler.createBinomialDistribution(rng, trials, p);
+        final DiscreteSampler sampler = MarsagliaTsangWangDiscreteSampler.Binomial.of(rng, trials, p);
     }
 
     /**
@@ -422,7 +422,7 @@ public class MarsagliaTsangWangDiscreteSamplerTest {
         Assert.assertEquals("Invalid test set-up for p(0)", 0, getBinomialP0(trials + 1, p), 0);
 
         // This will throw if the table does not sum to 2^30
-        final DiscreteSampler sampler = MarsagliaTsangWangDiscreteSampler.createBinomialDistribution(rng, trials, p);
+        final DiscreteSampler sampler = MarsagliaTsangWangDiscreteSampler.Binomial.of(rng, trials, p);
         sampler.sample();
     }
 
@@ -439,7 +439,7 @@ public class MarsagliaTsangWangDiscreteSamplerTest {
         // Validate set-up
         Assert.assertEquals("Invalid test set-up for p(0)", 0, getBinomialP0(trials, p), 0);
         @SuppressWarnings("unused")
-        final DiscreteSampler sampler = MarsagliaTsangWangDiscreteSampler.createBinomialDistribution(rng, trials, p);
+        final DiscreteSampler sampler = MarsagliaTsangWangDiscreteSampler.Binomial.of(rng, trials, p);
     }
 
     /**
@@ -482,7 +482,7 @@ public class MarsagliaTsangWangDiscreteSamplerTest {
         Assert.assertEquals("Invalid test set-up for p(0)", Double.MIN_VALUE, getBinomialP0(trials, p), 0);
         Assert.assertEquals("Invalid test set-up for p(0)", 0, getBinomialP0(trials, Math.nextAfter(p, 1)), 0);
 
-        final DiscreteSampler sampler = MarsagliaTsangWangDiscreteSampler.createBinomialDistribution(rng, trials, p);
+        final DiscreteSampler sampler = MarsagliaTsangWangDiscreteSampler.Binomial.of(rng, trials, p);
         // This will throw if the table does not sum to 2^30
         sampler.sample();
     }
@@ -506,7 +506,7 @@ public class MarsagliaTsangWangDiscreteSamplerTest {
         final UniformRandomProvider rng = new FixedRNG();
         final int trials = 1000000;
         final double p = 0;
-        final DiscreteSampler sampler = MarsagliaTsangWangDiscreteSampler.createBinomialDistribution(rng, trials, p);
+        final DiscreteSampler sampler = MarsagliaTsangWangDiscreteSampler.Binomial.of(rng, trials, p);
         for (int i = 0; i < 5; i++) {
             Assert.assertEquals(0, sampler.sample());
         }
@@ -523,7 +523,7 @@ public class MarsagliaTsangWangDiscreteSamplerTest {
         final UniformRandomProvider rng = new FixedRNG();
         final int trials = 1000000;
         final double p = 1;
-        final DiscreteSampler sampler = MarsagliaTsangWangDiscreteSampler.createBinomialDistribution(rng, trials, p);
+        final DiscreteSampler sampler = MarsagliaTsangWangDiscreteSampler.Binomial.of(rng, trials, p);
         for (int i = 0; i < 5; i++) {
             Assert.assertEquals(trials, sampler.sample());
         }
@@ -541,7 +541,7 @@ public class MarsagliaTsangWangDiscreteSamplerTest {
         final UniformRandomProvider rng = new FixedRNG();
         final int trials = 65000;
         final double p = 0.01;
-        final DiscreteSampler sampler = MarsagliaTsangWangDiscreteSampler.createBinomialDistribution(rng, trials, p);
+        final DiscreteSampler sampler = MarsagliaTsangWangDiscreteSampler.Binomial.of(rng, trials, p);
         // This will throw if the table does not sum to 2^30
         sampler.sample();
     }
@@ -555,7 +555,7 @@ public class MarsagliaTsangWangDiscreteSamplerTest {
         final UniformRandomProvider rng = new FixedRNG();
         final int trials = 10;
         final double p = 0.5;
-        final DiscreteSampler sampler = MarsagliaTsangWangDiscreteSampler.createBinomialDistribution(rng, trials, p);
+        final DiscreteSampler sampler = MarsagliaTsangWangDiscreteSampler.Binomial.of(rng, trials, p);
         // This will throw if the table does not sum to 2^30
         sampler.sample();
     }
@@ -570,8 +570,8 @@ public class MarsagliaTsangWangDiscreteSamplerTest {
         final int trials = 10;
         final double p1 = 0.4;
         final double p2 = 1 - p1;
-        final DiscreteSampler sampler1 = MarsagliaTsangWangDiscreteSampler.createBinomialDistribution(rng, trials, p1);
-        final DiscreteSampler sampler2 = MarsagliaTsangWangDiscreteSampler.createBinomialDistribution(rng, trials, p2);
+        final DiscreteSampler sampler1 = MarsagliaTsangWangDiscreteSampler.Binomial.of(rng, trials, p1);
+        final DiscreteSampler sampler2 = MarsagliaTsangWangDiscreteSampler.Binomial.of(rng, trials, p2);
         Assert.assertEquals(sampler1.toString(), sampler2.toString());
     }
 
@@ -610,8 +610,8 @@ public class MarsagliaTsangWangDiscreteSamplerTest {
         final UniformRandomProvider rng1 = RandomSource.create(RandomSource.SPLIT_MIX_64, 0L);
         final UniformRandomProvider rng2 = RandomSource.create(RandomSource.SPLIT_MIX_64, 0L);
         double[] probabilities = createProbabilities(offset, prob);
-        final MarsagliaTsangWangDiscreteSampler sampler1 =
-                MarsagliaTsangWangDiscreteSampler.createDiscreteDistribution(rng1, probabilities);
+        final SharedStateDiscreteSampler sampler1 =
+                MarsagliaTsangWangDiscreteSampler.Enumerated.of(rng1, probabilities);
         final SharedStateDiscreteSampler sampler2 = sampler1.withUniformRandomProvider(rng2);
         RandomAssert.assertProduceSameSequence(sampler1, sampler2);
     }
@@ -643,8 +643,8 @@ public class MarsagliaTsangWangDiscreteSamplerTest {
     private static void testSharedStateSampler(int trials, double probabilityOfSuccess) {
         final UniformRandomProvider rng1 = RandomSource.create(RandomSource.SPLIT_MIX_64, 0L);
         final UniformRandomProvider rng2 = RandomSource.create(RandomSource.SPLIT_MIX_64, 0L);
-        final MarsagliaTsangWangDiscreteSampler sampler1 =
-                MarsagliaTsangWangDiscreteSampler.createBinomialDistribution(rng1, trials, probabilityOfSuccess);
+        final SharedStateDiscreteSampler sampler1 =
+                MarsagliaTsangWangDiscreteSampler.Binomial.of(rng1, trials, probabilityOfSuccess);
         final SharedStateDiscreteSampler sampler2 = sampler1.withUniformRandomProvider(rng2);
         RandomAssert.assertProduceSameSequence(sampler1, sampler2);
     }
diff --git a/src/main/resources/pmd/pmd-ruleset.xml b/src/main/resources/pmd/pmd-ruleset.xml
index 39d64db..40c1312 100644
--- a/src/main/resources/pmd/pmd-ruleset.xml
+++ b/src/main/resources/pmd/pmd-ruleset.xml
@@ -74,7 +74,8 @@
     <properties>
       <!-- Array is generated internally in this case. -->
       <property name="violationSuppressXPath"
-      value="//ClassOrInterfaceDeclaration[@Image='PoissonSamplerCache' or @Image='AliasMethodDiscreteSampler']"/>
+        value="//ClassOrInterfaceDeclaration[@Image='PoissonSamplerCache' or @Image='AliasMethodDiscreteSampler'
+          or @Image='GuideTableDiscreteSampler']"/>
     </properties>
   </rule>