001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.statistics.inference; 018 019import java.util.Arrays; 020import java.util.EnumSet; 021import java.util.Objects; 022import org.apache.commons.numbers.core.Sum; 023import org.apache.commons.statistics.distribution.NormalDistribution; 024import org.apache.commons.statistics.ranking.NaNStrategy; 025import org.apache.commons.statistics.ranking.NaturalRanking; 026import org.apache.commons.statistics.ranking.RankingAlgorithm; 027import org.apache.commons.statistics.ranking.TiesStrategy; 028 029/** 030 * Implements the Wilcoxon signed-rank test. 031 * 032 * @see <a href="https://en.wikipedia.org/wiki/Wilcoxon_signed-rank_test">Wilcoxon signed-rank test (Wikipedia)</a> 033 * @since 1.1 034 */ 035public final class WilcoxonSignedRankTest { 036 /** Limit on sample size for the exact p-value computation. */ 037 private static final int EXACT_LIMIT = 1023; 038 /** Limit on sample size for the exact p-value computation for the auto mode. */ 039 private static final int AUTO_LIMIT = 50; 040 /** Ranking instance. */ 041 private static final RankingAlgorithm RANKING = new NaturalRanking(NaNStrategy.FAILED, TiesStrategy.AVERAGE); 042 /** Default instance. */ 043 private static final WilcoxonSignedRankTest DEFAULT = new WilcoxonSignedRankTest( 044 AlternativeHypothesis.TWO_SIDED, PValueMethod.AUTO, true, 0); 045 046 /** Alternative hypothesis. */ 047 private final AlternativeHypothesis alternative; 048 /** Method to compute the p-value. */ 049 private final PValueMethod pValueMethod; 050 /** Perform continuity correction. */ 051 private final boolean continuityCorrection; 052 /** Expected location shift. */ 053 private final double mu; 054 055 /** 056 * Result for the Wilcoxon signed-rank test. 057 * 058 * <p>This class is immutable. 059 * 060 * @since 1.1 061 */ 062 public static final class Result extends BaseSignificanceResult { 063 /** Flag indicating the data had tied values. */ 064 private final boolean tiedValues; 065 /** Flag indicating the data had zero values. */ 066 private final boolean zeroValues; 067 068 /** 069 * Create an instance. 070 * 071 * @param statistic Test statistic. 072 * @param tiedValues Flag indicating the data had tied values. 073 * @param zeroValues Flag indicating the data had zero values. 074 * @param p Result p-value. 075 */ 076 Result(double statistic, boolean tiedValues, boolean zeroValues, double p) { 077 super(statistic, p); 078 this.tiedValues = tiedValues; 079 this.zeroValues = zeroValues; 080 } 081 082 /** 083 * Return {@code true} if the data had tied values (with equal ranks). 084 * 085 * <p>Note: The exact computation cannot be used when there are tied values. 086 * The p-value uses the asymptotic approximation using a tie correction. 087 * 088 * @return {@code true} if there were tied values 089 */ 090 public boolean hasTiedValues() { 091 return tiedValues; 092 } 093 094 /** 095 * Return {@code true} if the data had zero values. This occurs when the differences between 096 * sample values matched the expected location shift: {@code z = x - y == mu}. 097 * 098 * <p>Note: The exact computation cannot be used when there are zero values. 099 * 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}