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 }