View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.statistics.inference;
18  
19  import java.util.Arrays;
20  import java.util.EnumSet;
21  import java.util.Objects;
22  import org.apache.commons.numbers.core.Sum;
23  import org.apache.commons.statistics.distribution.NormalDistribution;
24  import org.apache.commons.statistics.ranking.NaNStrategy;
25  import org.apache.commons.statistics.ranking.NaturalRanking;
26  import org.apache.commons.statistics.ranking.RankingAlgorithm;
27  import org.apache.commons.statistics.ranking.TiesStrategy;
28  
29  /**
30   * Implements the Wilcoxon signed-rank test.
31   *
32   * @see <a href="https://en.wikipedia.org/wiki/Wilcoxon_signed-rank_test">Wilcoxon signed-rank test (Wikipedia)</a>
33   * @since 1.1
34   */
35  public final class WilcoxonSignedRankTest {
36      /** Limit on sample size for the exact p-value computation. */
37      private static final int EXACT_LIMIT = 1023;
38      /** Limit on sample size for the exact p-value computation for the auto mode. */
39      private static final int AUTO_LIMIT = 50;
40      /** Ranking instance. */
41      private static final RankingAlgorithm RANKING = new NaturalRanking(NaNStrategy.FAILED, TiesStrategy.AVERAGE);
42      /** Default instance. */
43      private static final WilcoxonSignedRankTest DEFAULT = new WilcoxonSignedRankTest(
44          AlternativeHypothesis.TWO_SIDED, PValueMethod.AUTO, true, 0);
45  
46      /** Alternative hypothesis. */
47      private final AlternativeHypothesis alternative;
48      /** Method to compute the p-value. */
49      private final PValueMethod pValueMethod;
50      /** Perform continuity correction. */
51      private final boolean continuityCorrection;
52      /** Expected location shift. */
53      private final double mu;
54  
55      /**
56       * Result for the Wilcoxon signed-rank test.
57       *
58       * <p>This class is immutable.
59       *
60       * @since 1.1
61       */
62      public static final class Result extends BaseSignificanceResult {
63          /** Flag indicating the data had tied values. */
64          private final boolean tiedValues;
65          /** Flag indicating the data had zero values. */
66          private final boolean zeroValues;
67  
68          /**
69           * Create an instance.
70           *
71           * @param statistic Test statistic.
72           * @param tiedValues Flag indicating the data had tied values.
73           * @param zeroValues Flag indicating the data had zero values.
74           * @param p Result p-value.
75           */
76          Result(double statistic, boolean tiedValues, boolean zeroValues, double p) {
77              super(statistic, p);
78              this.tiedValues = tiedValues;
79              this.zeroValues = zeroValues;
80          }
81  
82          /**
83           * Return {@code true} if the data had tied values (with equal ranks).
84           *
85           * <p>Note: The exact computation cannot be used when there are tied values.
86           * The p-value uses the asymptotic approximation using a tie correction.
87           *
88           * @return {@code true} if there were tied values
89           */
90          public boolean hasTiedValues() {
91              return tiedValues;
92          }
93  
94          /**
95           * Return {@code true} if the data had zero values. This occurs when the differences between
96           * sample values matched the expected location shift: {@code z = x - y == mu}.
97           *
98           * <p>Note: The exact computation cannot be used when there are zero values.
99           * The p-value uses the asymptotic approximation.
100          *
101          * @return {@code true} if there were zero values
102          */
103         public boolean hasZeroValues() {
104             return zeroValues;
105         }
106     }
107 
108     /**
109      * @param alternative Alternative hypothesis.
110      * @param method P-value method.
111      * @param continuityCorrection true to perform continuity correction.
112      * @param mu Expected location shift.
113      */
114     private WilcoxonSignedRankTest(AlternativeHypothesis alternative, PValueMethod method,
115         boolean continuityCorrection, double mu) {
116         this.alternative = alternative;
117         this.pValueMethod = method;
118         this.continuityCorrection = continuityCorrection;
119         this.mu = mu;
120     }
121 
122     /**
123      * Return an instance using the default options.
124      *
125      * <ul>
126      * <li>{@link AlternativeHypothesis#TWO_SIDED}
127      * <li>{@link PValueMethod#AUTO}
128      * <li>{@link ContinuityCorrection#ENABLED}
129      * <li>{@linkplain #withMu(double) mu = 0}
130      * </ul>
131      *
132      * @return default instance
133      */
134     public static WilcoxonSignedRankTest withDefaults() {
135         return DEFAULT;
136     }
137 
138     /**
139      * Return an instance with the configured alternative hypothesis.
140      *
141      * @param v Value.
142      * @return an instance
143      */
144     public WilcoxonSignedRankTest with(AlternativeHypothesis v) {
145         return new WilcoxonSignedRankTest(Objects.requireNonNull(v), pValueMethod, continuityCorrection, mu);
146     }
147 
148     /**
149      * Return an instance with the configured p-value method.
150      *
151      * @param v Value.
152      * @return an instance
153      * @throws IllegalArgumentException if the value is not in the allowed options or is null
154      */
155     public WilcoxonSignedRankTest with(PValueMethod v) {
156         return new WilcoxonSignedRankTest(alternative,
157             Arguments.checkOption(v, EnumSet.of(PValueMethod.AUTO, PValueMethod.EXACT, PValueMethod.ASYMPTOTIC)),
158             continuityCorrection, mu);
159     }
160 
161     /**
162      * Return an instance with the configured continuity correction.
163      *
164      * <p>If {@code enabled}, adjust the Wilcoxon rank statistic by 0.5 towards the
165      * mean value when computing the z-statistic if a normal approximation is used
166      * to compute the p-value.
167      *
168      * @param v Value.
169      * @return an instance
170      */
171     public WilcoxonSignedRankTest with(ContinuityCorrection v) {
172         return new WilcoxonSignedRankTest(alternative, pValueMethod,
173             Objects.requireNonNull(v) == ContinuityCorrection.ENABLED, mu);
174     }
175 
176     /**
177      * Return an instance with the configured expected difference {@code mu}.
178      *
179      * @param v Value.
180      * @return an instance
181      * @throws IllegalArgumentException if the value is not finite
182      */
183     public WilcoxonSignedRankTest withMu(double v) {
184         return new WilcoxonSignedRankTest(alternative, pValueMethod, continuityCorrection, Arguments.checkFinite(v));
185     }
186 
187     /**
188      * Computes the Wilcoxon signed ranked statistic comparing the differences between
189      * sample values {@code z = x - y} to {@code mu}.
190      *
191      * <p>This method handles matching samples {@code z[i] == mu} (no difference)
192      * by including them in the ranking of samples but excludes them from the test statistic
193      * (<i>signed-rank zero procedure</i>).
194      *
195      * @param z Signed differences between sample values.
196      * @return Wilcoxon <i>positive-rank sum</i> statistic (W+)
197      * @throws IllegalArgumentException if {@code z} is zero-length; contains NaN values;
198      * or all differences are equal to the expected difference
199      * @see #withMu(double)
200      */
201     public double statistic(double[] z) {
202         return computeStatistic(z, mu);
203     }
204 
205     /**
206      * Computes the Wilcoxon signed ranked statistic comparing the differences between two related
207      * samples or repeated measurements on a single sample.
208      *
209      * <p>This method handles matching samples {@code x[i] - mu == y[i]} (no difference)
210      * by including them in the ranking of samples but excludes them from the test statistic
211      * (<i>signed-rank zero procedure</i>).
212      *
213      * <p>This method is functionally equivalent to creating an array of differences
214      * {@code z = x - y} and calling {@link #statistic(double[]) statistic(z)}; the
215      * implementation may use an optimised method to compute the differences and
216      * rank statistic if {@code mu != 0}.
217      *
218      * @param x First sample values.
219      * @param y Second sample values.
220      * @return Wilcoxon <i>positive-rank sum</i> statistic (W+)
221      * @throws IllegalArgumentException if {@code x} or {@code y} are zero-length; are not
222      * the same length; contain NaN values; or {@code x[i] == y[i]} for all data
223      * @see #withMu(double)
224      */
225     public double statistic(double[] x, double[] y) {
226         checkSamples(x, y);
227         // Apply mu before creation of differences
228         final double[] z = calculateDifferences(mu, x, y);
229         return computeStatistic(z, 0);
230     }
231 
232     /**
233      * Performs a Wilcoxon signed ranked statistic comparing the differences between
234      * sample values {@code z = x - y} to {@code mu}.
235      *
236      * <p>This method handles matching samples {@code z[i] == mu} (no difference)
237      * by including them in the ranking of samples but excludes them from the test statistic
238      * (<i>signed-rank zero procedure</i>).
239      *
240      * <p>The test is defined by the {@link AlternativeHypothesis}.
241      *
242      * <ul>
243      * <li>'two-sided': the distribution of the difference is not symmetric about {@code mu}.
244      * <li>'greater': the distribution of the difference is stochastically greater than a
245      * distribution symmetric about {@code mu}.
246      * <li>'less': the distribution of the difference is stochastically less than a distribution
247      * symmetric about {@code mu}.
248      * </ul>
249      *
250      * <p>If the p-value method is {@linkplain PValueMethod#AUTO auto} an exact p-value
251      * is computed if the samples contain less than 50 values; otherwise a normal
252      * approximation is used.
253      *
254      * <p>Computation of the exact p-value is only valid if there are no matching
255      * samples {@code z[i] == mu} and no tied ranks in the data; otherwise the
256      * p-value resorts to the asymptotic Cureton approximation using a tie
257      * correction and an optional continuity correction.
258      *
259      * <p><strong>Note: </strong>
260      * Computation of the exact p-value requires the
261      * sample size {@code <= 1023}. Exact computation requires tabulation of values
262      * not exceeding size {@code n(n+1)/2} and computes in Order(n*n/2). Maximum
263      * memory usage is approximately 4 MiB.
264      *
265      * @param z Differences between sample values.
266      * @return test result
267      * @throws IllegalArgumentException if {@code z} is zero-length; contains NaN values;
268      * or all differences are zero
269      * @see #withMu(double)
270      * @see #with(AlternativeHypothesis)
271      * @see #with(ContinuityCorrection)
272      */
273     public Result test(double[] z) {
274         return computeTest(z, mu);
275     }
276 
277     /**
278      * Performs a Wilcoxon signed ranked statistic comparing mean for two related
279      * samples or repeated measurements on a single sample.
280      *
281      * <p>This method handles matching samples {@code x[i] - mu == y[i]} (no difference)
282      * by including them in the ranking of samples but excludes them
283      * from the test statistic (<i>signed-rank zero procedure</i>).
284      *
285      * <p>This method is functionally equivalent to creating an array of differences
286      * {@code z = x - y} and calling {@link #test(double[]) test(z)}; the
287      * implementation may use an optimised method to compute the differences and
288      * rank statistic if {@code mu != 0}.
289      *
290      * @param x First sample values.
291      * @param y Second sample values.
292      * @return test result
293      * @throws IllegalArgumentException if {@code x} or {@code y} are zero-length; are not
294      * the same length; contain NaN values; or {@code x[i] - mu == y[i]} for all data
295      * @see #statistic(double[], double[])
296      * @see #test(double[])
297      */
298     public Result test(double[] x, double[] y) {
299         checkSamples(x, y);
300         // Apply mu before creation of differences
301         final double[] z = calculateDifferences(mu, x, y);
302         return computeTest(z, 0);
303     }
304 
305     /**
306      * Computes the Wilcoxon signed ranked statistic comparing the differences between
307      * sample values {@code z = x - y} to {@code mu}.
308      *
309      * @param z Signed differences between sample values.
310      * @param mu Expected difference.
311      * @return Wilcoxon <i>positive-rank sum</i> statistic (W+)
312      * @throws IllegalArgumentException if {@code z} is zero-length; contains NaN values;
313      * or all differences are equal to the expected difference
314      * @see #withMu(double)
315      */
316     private static double computeStatistic(double[] z, double mu) {
317         Arguments.checkValuesRequiredSize(z.length, 1);
318         final double[] x = StatisticUtils.subtract(z, mu);
319         // Raises an error if all zeros
320         countZeros(x);
321         final double[] zAbs = calculateAbsoluteDifferences(x);
322         final double[] ranks = RANKING.apply(zAbs);
323         return calculateW(x, ranks);
324     }
325 
326     /**
327      * Performs a Wilcoxon signed ranked statistic comparing the differences between
328      * sample values {@code z = x - y} to {@code mu}.
329      *
330      * @param z Differences between sample values.
331      * @param expectedMu Expected difference.
332      * @return test result
333      * @throws IllegalArgumentException if {@code z} is zero-length; contains NaN values;
334      * or all differences are zero
335      */
336     private Result computeTest(double[] z, double expectedMu) {
337         // Computation as above. The ranks are required for tie correction.
338         Arguments.checkValuesRequiredSize(z.length, 1);
339         final double[] x = StatisticUtils.subtract(z, expectedMu);
340         // Raises an error if all zeros
341         final int zeros = countZeros(x);
342         final double[] zAbs = calculateAbsoluteDifferences(x);
343         final double[] ranks = RANKING.apply(zAbs);
344         final double wPlus = calculateW(x, ranks);
345 
346         // Exact p has strict requirements for no zeros, no ties
347         final double c = calculateTieCorrection(ranks);
348         final boolean tiedValues = c != 0;
349 
350         final int n = z.length;
351         // Exact p requires no ties and no zeros
352         final double p;
353         if (selectMethod(pValueMethod, n) == PValueMethod.EXACT && n <= EXACT_LIMIT && !tiedValues && zeros == 0) {
354             p = calculateExactPValue((int) wPlus, n, alternative);
355         } else {
356             p = calculateAsymptoticPValue(wPlus, n, zeros, c, alternative, continuityCorrection);
357         }
358         return new Result(wPlus, tiedValues, zeros != 0, p);
359     }
360 
361     /**
362      * Ensures that the provided arrays fulfil the assumptions.
363      *
364      * @param x First sample.
365      * @param y Second sample.
366      * @throws IllegalArgumentException if {@code x} or {@code y} are zero-length; or do not
367      * have the same length
368      */
369     private static void checkSamples(double[] x, double[] y) {
370         Arguments.checkValuesRequiredSize(x.length, 1);
371         Arguments.checkValuesRequiredSize(y.length, 1);
372         Arguments.checkValuesSizeMatch(x.length, y.length);
373     }
374 
375     /**
376      * Calculates x[i] - mu - y[i] for all i.
377      *
378      * @param mu Expected difference.
379      * @param x First sample.
380      * @param y Second sample.
381      * @return z = x - y
382      */
383     private static double[] calculateDifferences(double mu, double[] x, double[] y) {
384         final double[] z = new double[x.length];
385         for (int i = 0; i < x.length; ++i) {
386             z[i] = x[i] - mu - y[i];
387         }
388         return z;
389     }
390 
391     /**
392      * Calculates |z[i]| for all i.
393      *
394      * @param z Sample.
395      * @return |z|
396      */
397     private static double[] calculateAbsoluteDifferences(double[] z) {
398         final double[] zAbs = new double[z.length];
399         for (int i = 0; i < z.length; ++i) {
400             zAbs[i] = Math.abs(z[i]);
401         }
402         return zAbs;
403     }
404 
405     /**
406      * Calculate the Wilcoxon <i>positive-rank sum</i> statistic.
407      *
408      * @param obs Observed signed value.
409      * @param ranks Ranks (including averages for ties).
410      * @return Wilcoxon <i>positive-rank sum</i> statistic (W+)
411      */
412     private static double calculateW(final double[] obs, final double[] ranks) {
413         final Sum wPlus = Sum.create();
414         for (int i = 0; i < obs.length; ++i) {
415             // Must be positive (excludes zeros)
416             if (obs[i] > 0) {
417                 wPlus.add(ranks[i]);
418             }
419         }
420         return wPlus.getAsDouble();
421     }
422 
423     /**
424      * Count the number of zeros in the data.
425      *
426      * @param z Input data.
427      * @return the zero count
428      * @throws IllegalArgumentException if the data is all zeros
429      */
430     private static int countZeros(final double[] z) {
431         int c = 0;
432         for (final double v : z) {
433             if (v == 0) {
434                 c++;
435             }
436         }
437         if (c == z.length) {
438             throw new InferenceException("All signed differences are zero");
439         }
440         return c;
441     }
442 
443     /**
444      * Calculate the tie correction.
445      * Destructively modifies ranks (by sorting).
446      * <pre>
447      * c = sum(t^3 - t)
448      * </pre>
449      * <p>where t is the size of each group of tied observations.
450      *
451      * @param ranks Ranks
452      * @return the tie correction
453      */
454     static double calculateTieCorrection(double[] ranks) {
455         double c = 0;
456         int ties = 1;
457         Arrays.sort(ranks);
458         double last = Double.NaN;
459         for (final double rank : ranks) {
460             // Deliberate use of equals
461             if (last == rank) {
462                 // Extend the tied group
463                 ties++;
464             } else {
465                 if (ties != 1) {
466                     c += Math.pow(ties, 3) - ties;
467                     ties = 1;
468                 }
469                 last = rank;
470             }
471         }
472         // Final ties count
473         c += Math.pow(ties, 3) - ties;
474         return c;
475     }
476 
477     /**
478      * Select the method to compute the p-value.
479      *
480      * @param method P-value method.
481      * @param n Size of the data.
482      * @return p-value method.
483      */
484     private static PValueMethod selectMethod(PValueMethod method, int n) {
485         return method == PValueMethod.AUTO && n <= AUTO_LIMIT ? PValueMethod.EXACT : method;
486     }
487 
488     /**
489      * Compute the asymptotic p-value using the Cureton normal approximation. This
490      * corrects for zeros in the signed-rank zero procedure and/or ties corrected using
491      * the average method.
492      *
493      * @param wPlus Wilcoxon signed rank value (W+).
494      * @param n Number of subjects.
495      * @param z Count of number of zeros
496      * @param c Tie-correction
497      * @param alternative Alternative hypothesis.
498      * @param continuityCorrection true to use a continuity correction.
499      * @return two-sided asymptotic p-value
500      */
501     private static double calculateAsymptoticPValue(double wPlus, int n, double z, double c,
502             AlternativeHypothesis alternative, boolean continuityCorrection) {
503         // E[W+] = n * (n + 1) / 4 - z * (z + 1) / 4
504         final double e = (n * (n + 1.0) - z * (z + 1.0)) * 0.25;
505 
506         final double variance = ((n * (n + 1.0) * (2 * n + 1.0)) -
507                                 (z * (z + 1.0) * (2 * z + 1.0)) - c * 0.5) / 24;
508 
509         double x = wPlus - e;
510         if (continuityCorrection) {
511             // +/- 0.5 is a continuity correction towards the expected.
512             if (alternative == AlternativeHypothesis.GREATER_THAN) {
513                 x -= 0.5;
514             } else if (alternative == AlternativeHypothesis.LESS_THAN) {
515                 x += 0.5;
516             } else {
517                 // two-sided. Shift towards the expected of zero.
518                 // Use of signum ignores x==0 (i.e. not copySign(0.5, z))
519                 x -= Math.signum(x) * 0.5;
520             }
521         }
522         x /= Math.sqrt(variance);
523 
524         final NormalDistribution standardNormal = NormalDistribution.of(0, 1);
525         if (alternative == AlternativeHypothesis.GREATER_THAN) {
526             return standardNormal.survivalProbability(x);
527         }
528         if (alternative == AlternativeHypothesis.LESS_THAN) {
529             return standardNormal.cumulativeProbability(x);
530         }
531         // two-sided
532         return 2 * standardNormal.survivalProbability(Math.abs(x));
533     }
534 
535     /**
536      * Compute the exact p-value.
537      *
538      * <p>This computation requires that no zeros or ties are found in the data. The input
539      * value n is limited to 1023.
540      *
541      * @param w1 Wilcoxon signed rank value (W+, or W-).
542      * @param n Number of subjects.
543      * @param alternative Alternative hypothesis.
544      * @return exact p-value (two-sided, greater, or less using the options)
545      */
546     private static double calculateExactPValue(int w1, int n, AlternativeHypothesis alternative) {
547         // T+ plus T- equals the sum of the ranks: n(n+1)/2
548         // Compute using the lower half.
549         // No overflow here if n <= 1023.
550         final int sum = n * (n + 1) / 2;
551         final int w2 = sum - w1;
552 
553         // Return the correct side:
554         if (alternative == AlternativeHypothesis.GREATER_THAN) {
555             // sf(w1 - 1)
556             return sf(w1 - 1, w2 + 1, n);
557         }
558         if (alternative == AlternativeHypothesis.LESS_THAN) {
559             // cdf(w1)
560             return cdf(w1, w2, n);
561         }
562         // two-sided: 2 * sf(max(w1, w2) - 1) or 2 * cdf(min(w1, w2))
563         final double p = 2 * computeCdf(Math.min(w1, w2), n);
564         // Clip to range: [0, 1]
565         return Math.min(1, p);
566     }
567 
568     /**
569      * Compute the cumulative density function of the Wilcoxon signed rank W+ statistic.
570      * The W- statistic is passed for convenience to exploit symmetry in the distribution.
571      *
572      * @param w1 Wilcoxon W+ statistic
573      * @param w2 Wilcoxon W- statistic
574      * @param n Number of subjects.
575      * @return {@code Pr(X <= k)}
576      */
577     private static double cdf(int w1, int w2, int n) {
578         // Exploit symmetry. Note the distribution is discrete thus requiring (w2 - 1).
579         return w2 > w1 ?
580             computeCdf(w1, n) :
581             1 - computeCdf(w2 - 1, n);
582     }
583 
584     /**
585      * Compute the survival function of the Wilcoxon signed rank W+ statistic.
586      * The W- statistic is passed for convenience to exploit symmetry in the distribution.
587      *
588      * @param w1 Wilcoxon W+ statistic
589      * @param w2 Wilcoxon W- statistic
590      * @param n Number of subjects.
591      * @return {@code Pr(X <= k)}
592      */
593     private static double sf(int w1, int w2, int n) {
594         // Opposite of the CDF
595         return w2 > w1 ?
596             1 - computeCdf(w1, n) :
597             computeCdf(w2 - 1, n);
598     }
599 
600     /**
601      * Compute the cumulative density function for the distribution of the Wilcoxon
602      * signed rank statistic. This is a discrete distribution and is only valid
603      * when no zeros or ties are found in the data.
604      *
605      * <p>This should be called with the lower of W+ or W- for computational efficiency.
606      * The input value n is limited to 1023.
607      *
608      * <p>Uses recursion to compute the density for {@code X <= t} and sums the values.
609      * See: https://en.wikipedia.org/wiki/Wilcoxon_signed-rank_test#Computing_the_null_distribution
610      *
611      * @param t Smallest Wilcoxon signed rank value (W+, or W-).
612      * @param n Number of subjects.
613      * @return {@code Pr(T <= t)}
614      */
615     private static double computeCdf(int t, int n) {
616         // Currently limited to n=1023.
617         // Note:
618         // The limit for t is n(n+1)/2.
619         // The highest possible sum is bounded by the normalisation factor 2^n.
620         // n         t              sum          support
621         // 31        [0, 496]       < 2^31       int
622         // 63        [0, 2016]      < 2^63       long
623         // 1023      [0, 523766]    < 2^1023     double
624 
625         if (t <= 0) {
626             // No recursion required
627             return t < 0 ? 0 : Math.scalb(1, -n);
628         }
629 
630         // Define u_n(t) as the number of sign combinations for T = t
631         // Pr(T == t) = u_n(t) / 2^n
632         // Sum them to create the cumulative probability Pr(T <= t).
633         //
634         // Recursive formula:
635         // u_n(t) = u_{n-1}(t) + u_{n-1}(t-n)
636         // u_0(0) = 1
637         // u_0(t) = 0 : t != 0
638         // u_n(t) = 0 : t < 0 || t > n(n+1)/2
639 
640         // Compute all u_n(t) up to t.
641         final double[] u = new double[t + 1];
642         // Initialize u_1(t) using base cases for recursion
643         u[0] = u[1] = 1;
644 
645         // Each u_n(t) is created using the current correct values for u_{n-1}(t)
646         for (int nn = 2; nn < n + 1; nn++) {
647             // u[t] holds the correct value for u_{n-1}(t)
648             // u_n(t) = u_{n-1}(t) + u_{n-1}(t-n)
649             for (int tt = t; tt >= nn; tt--) {
650                 u[tt] += u[tt - nn];
651             }
652         }
653         final double sum = Arrays.stream(u).sum();
654 
655         // Finally divide by the number of possible sums: 2^n
656         return Math.scalb(sum, -n);
657     }
658 }