|
| 1 | +/* |
| 2 | + * Copyright (C) 2012 The Guava Authors |
| 3 | + * |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except |
| 5 | + * in compliance with the License. You may obtain a copy of the License at |
| 6 | + * |
| 7 | + * http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | + * |
| 9 | + * Unless required by applicable law or agreed to in writing, software distributed under the License |
| 10 | + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express |
| 11 | + * or implied. See the License for the specific language governing permissions and limitations under |
| 12 | + * the License. |
| 13 | + */ |
| 14 | + |
| 15 | +package guava.examples.math; |
| 16 | + |
| 17 | +import java.util.Iterator; |
| 18 | + |
| 19 | +import static java.lang.Double.*; |
| 20 | + |
| 21 | +public final class StatsAccumulator { |
| 22 | + |
| 23 | + // These fields must satisfy the requirements of Stats' constructor as well as those of the stat |
| 24 | + // methods of this class. |
| 25 | + private long count = 0; |
| 26 | + private double mean = 0.0; // any finite value will do, we only use it to multiply by zero for sum |
| 27 | + private double sumOfSquaresOfDeltas = 0.0; |
| 28 | + private double min = NaN; // any value will do |
| 29 | + private double max = NaN; // any value will do |
| 30 | + |
| 31 | + /** Adds the given value to the dataset. */ |
| 32 | + public void add(double value) { |
| 33 | + if (count == 0) { |
| 34 | + count = 1; |
| 35 | + mean = value; |
| 36 | + min = value; |
| 37 | + max = value; |
| 38 | + if (!isFinite(value)) { |
| 39 | + sumOfSquaresOfDeltas = NaN; |
| 40 | + } |
| 41 | + } else { |
| 42 | + count++; |
| 43 | + if (isFinite(value) && isFinite(mean)) { |
| 44 | + // Art of Computer Programming vol. 2, Knuth, 4.2.2, (15) and (16) |
| 45 | + double delta = value - mean; |
| 46 | + mean += delta / count; |
| 47 | + sumOfSquaresOfDeltas += delta * (value - mean); |
| 48 | + } else { |
| 49 | + mean = calculateNewMeanNonFinite(mean, value); |
| 50 | + sumOfSquaresOfDeltas = NaN; |
| 51 | + } |
| 52 | + min = Math.min(min, value); |
| 53 | + max = Math.max(max, value); |
| 54 | + } |
| 55 | + } |
| 56 | + |
| 57 | + /** |
| 58 | + * Adds the given values to the dataset. |
| 59 | + * |
| 60 | + * @param values a series of values, which will be converted to {@code double} values (this may |
| 61 | + * cause loss of precision) |
| 62 | + */ |
| 63 | + public void addAll(Iterable<? extends Number> values) { |
| 64 | + for (Number value : values) { |
| 65 | + add(value.doubleValue()); |
| 66 | + } |
| 67 | + } |
| 68 | + |
| 69 | + /** |
| 70 | + * Adds the given values to the dataset. |
| 71 | + * |
| 72 | + * @param values a series of values, which will be converted to {@code double} values (this may |
| 73 | + * cause loss of precision) |
| 74 | + */ |
| 75 | + public void addAll(Iterator<? extends Number> values) { |
| 76 | + while (values.hasNext()) { |
| 77 | + add(values.next().doubleValue()); |
| 78 | + } |
| 79 | + } |
| 80 | + |
| 81 | + /** |
| 82 | + * Adds the given values to the dataset. |
| 83 | + * |
| 84 | + * @param values a series of values |
| 85 | + */ |
| 86 | + public void addAll(double... values) { |
| 87 | + for (double value : values) { |
| 88 | + add(value); |
| 89 | + } |
| 90 | + } |
| 91 | + |
| 92 | + /** |
| 93 | + * Adds the given values to the dataset. |
| 94 | + * |
| 95 | + * @param values a series of values |
| 96 | + */ |
| 97 | + public void addAll(int... values) { |
| 98 | + for (int value : values) { |
| 99 | + add(value); |
| 100 | + } |
| 101 | + } |
| 102 | + |
| 103 | + /** |
| 104 | + * Adds the given values to the dataset. |
| 105 | + * |
| 106 | + * @param values a series of values, which will be converted to {@code double} values (this may |
| 107 | + * cause loss of precision for longs of magnitude over 2^53 (slightly over 9e15)) |
| 108 | + */ |
| 109 | + public void addAll(long... values) { |
| 110 | + for (long value : values) { |
| 111 | + add(value); |
| 112 | + } |
| 113 | + } |
| 114 | + |
| 115 | + /** Returns an immutable snapshot of the current statistics. */ |
| 116 | + public Stats snapshot() { |
| 117 | + return new Stats(count, mean, sumOfSquaresOfDeltas, min, max); |
| 118 | + } |
| 119 | + |
| 120 | + /** Returns the number of values. */ |
| 121 | + public long count() { |
| 122 | + return count; |
| 123 | + } |
| 124 | + |
| 125 | + /** |
| 126 | + * Calculates the new value for the accumulated mean when a value is added, in the case where at |
| 127 | + * least one of the previous mean and the value is non-finite. |
| 128 | + */ |
| 129 | + static double calculateNewMeanNonFinite(double previousMean, double value) { |
| 130 | + /* |
| 131 | + * Desired behaviour is to match the results of applying the naive mean formula. In particular, |
| 132 | + * the update formula can subtract infinities in cases where the naive formula would add them. |
| 133 | + * |
| 134 | + * Consequently: |
| 135 | + * 1. If the previous mean is finite and the new value is non-finite then the new mean is that |
| 136 | + * value (whether it is NaN or infinity). |
| 137 | + * 2. If the new value is finite and the previous mean is non-finite then the mean is unchanged |
| 138 | + * (whether it is NaN or infinity). |
| 139 | + * 3. If both the previous mean and the new value are non-finite and... |
| 140 | + * 3a. ...either or both is NaN (so mean != value) then the new mean is NaN. |
| 141 | + * 3b. ...they are both the same infinities (so mean == value) then the mean is unchanged. |
| 142 | + * 3c. ...they are different infinities (so mean != value) then the new mean is NaN. |
| 143 | + */ |
| 144 | + if (isFinite(previousMean)) { |
| 145 | + // This is case 1. |
| 146 | + return value; |
| 147 | + } else if (isFinite(value) || previousMean == value) { |
| 148 | + // This is case 2. or 3b. |
| 149 | + return previousMean; |
| 150 | + } else { |
| 151 | + // This is case 3a. or 3c. |
| 152 | + return NaN; |
| 153 | + } |
| 154 | + } |
| 155 | + |
| 156 | + public static boolean isFinite(double value) { |
| 157 | + return NEGATIVE_INFINITY < value && value < POSITIVE_INFINITY; |
| 158 | + } |
| 159 | +} |
0 commit comments