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.math.stat.descriptive;
18
19 import java.io.Serializable;
20 import java.lang.reflect.InvocationTargetException;
21 import java.util.Arrays;
22
23 import org.apache.commons.math.MathRuntimeException;
24 import org.apache.commons.math.exception.NullArgumentException;
25 import org.apache.commons.math.exception.util.LocalizedFormats;
26 import org.apache.commons.math.stat.descriptive.moment.GeometricMean;
27 import org.apache.commons.math.stat.descriptive.moment.Kurtosis;
28 import org.apache.commons.math.stat.descriptive.moment.Mean;
29 import org.apache.commons.math.stat.descriptive.moment.Skewness;
30 import org.apache.commons.math.stat.descriptive.moment.Variance;
31 import org.apache.commons.math.stat.descriptive.rank.Max;
32 import org.apache.commons.math.stat.descriptive.rank.Min;
33 import org.apache.commons.math.stat.descriptive.rank.Percentile;
34 import org.apache.commons.math.stat.descriptive.summary.Sum;
35 import org.apache.commons.math.stat.descriptive.summary.SumOfSquares;
36 import org.apache.commons.math.util.MathUtils;
37 import org.apache.commons.math.util.ResizableDoubleArray;
38 import org.apache.commons.math.util.FastMath;
39
40
41 /**
42 * Maintains a dataset of values of a single variable and computes descriptive
43 * statistics based on stored data. The {@link #getWindowSize() windowSize}
44 * property sets a limit on the number of values that can be stored in the
45 * dataset. The default value, INFINITE_WINDOW, puts no limit on the size of
46 * the dataset. This value should be used with caution, as the backing store
47 * will grow without bound in this case. For very large datasets,
48 * {@link SummaryStatistics}, which does not store the dataset, should be used
49 * instead of this class. If <code>windowSize</code> is not INFINITE_WINDOW and
50 * more values are added than can be stored in the dataset, new values are
51 * added in a "rolling" manner, with new values replacing the "oldest" values
52 * in the dataset.
53 *
54 * <p>Note: this class is not threadsafe. Use
55 * {@link SynchronizedDescriptiveStatistics} if concurrent access from multiple
56 * threads is required.</p>
57 *
58 * @version $Id: DescriptiveStatistics.java 1132432 2011-06-05 14:59:29Z luc $
59 */
60 public class DescriptiveStatistics implements StatisticalSummary, Serializable {
61
62 /**
63 * Represents an infinite window size. When the {@link #getWindowSize()}
64 * returns this value, there is no limit to the number of data values
65 * that can be stored in the dataset.
66 */
67 public static final int INFINITE_WINDOW = -1;
68
69 /** Serialization UID */
70 private static final long serialVersionUID = 4133067267405273064L;
71
72 /** Name of the setQuantile method. */
73 private static final String SET_QUANTILE_METHOD_NAME = "setQuantile";
74
75 /** hold the window size **/
76 protected int windowSize = INFINITE_WINDOW;
77
78 /**
79 * Stored data values
80 */
81 protected ResizableDoubleArray eDA = new ResizableDoubleArray();
82
83 /** Mean statistic implementation - can be reset by setter. */
84 private UnivariateStatistic meanImpl = new Mean();
85
86 /** Geometric mean statistic implementation - can be reset by setter. */
87 private UnivariateStatistic geometricMeanImpl = new GeometricMean();
88
89 /** Kurtosis statistic implementation - can be reset by setter. */
90 private UnivariateStatistic kurtosisImpl = new Kurtosis();
91
92 /** Maximum statistic implementation - can be reset by setter. */
93 private UnivariateStatistic maxImpl = new Max();
94
95 /** Minimum statistic implementation - can be reset by setter. */
96 private UnivariateStatistic minImpl = new Min();
97
98 /** Percentile statistic implementation - can be reset by setter. */
99 private UnivariateStatistic percentileImpl = new Percentile();
100
101 /** Skewness statistic implementation - can be reset by setter. */
102 private UnivariateStatistic skewnessImpl = new Skewness();
103
104 /** Variance statistic implementation - can be reset by setter. */
105 private UnivariateStatistic varianceImpl = new Variance();
106
107 /** Sum of squares statistic implementation - can be reset by setter. */
108 private UnivariateStatistic sumsqImpl = new SumOfSquares();
109
110 /** Sum statistic implementation - can be reset by setter. */
111 private UnivariateStatistic sumImpl = new Sum();
112
113 /**
114 * Construct a DescriptiveStatistics instance with an infinite window
115 */
116 public DescriptiveStatistics() {
117 }
118
119 /**
120 * Construct a DescriptiveStatistics instance with the specified window
121 *
122 * @param window the window size.
123 */
124 public DescriptiveStatistics(int window) {
125 setWindowSize(window);
126 }
127
128 /**
129 * Construct a DescriptiveStatistics instance with an infinite window
130 * and the initial data values in double[] initialDoubleArray.
131 * If initialDoubleArray is null, then this constructor corresponds to
132 * DescriptiveStatistics()
133 *
134 * @param initialDoubleArray the initial double[].
135 */
136 public DescriptiveStatistics(double[] initialDoubleArray) {
137 if (initialDoubleArray != null) {
138 eDA = new ResizableDoubleArray(initialDoubleArray);
139 }
140 }
141
142 /**
143 * Copy constructor. Construct a new DescriptiveStatistics instance that
144 * is a copy of original.
145 *
146 * @param original DescriptiveStatistics instance to copy
147 */
148 public DescriptiveStatistics(DescriptiveStatistics original) {
149 copy(original, this);
150 }
151
152 /**
153 * Adds the value to the dataset. If the dataset is at the maximum size
154 * (i.e., the number of stored elements equals the currently configured
155 * windowSize), the first (oldest) element in the dataset is discarded
156 * to make room for the new value.
157 *
158 * @param v the value to be added
159 */
160 public void addValue(double v) {
161 if (windowSize != INFINITE_WINDOW) {
162 if (getN() == windowSize) {
163 eDA.addElementRolling(v);
164 } else if (getN() < windowSize) {
165 eDA.addElement(v);
166 }
167 } else {
168 eDA.addElement(v);
169 }
170 }
171
172 /**
173 * Removes the most recent value from the dataset.
174 */
175 public void removeMostRecentValue() {
176 eDA.discardMostRecentElements(1);
177 }
178
179 /**
180 * Replaces the most recently stored value with the given value.
181 * There must be at least one element stored to call this method.
182 *
183 * @param v the value to replace the most recent stored value
184 * @return replaced value
185 */
186 public double replaceMostRecentValue(double v) {
187 return eDA.substituteMostRecentElement(v);
188 }
189
190 /**
191 * Returns the <a href="http://www.xycoon.com/arithmetic_mean.htm">
192 * arithmetic mean </a> of the available values
193 * @return The mean or Double.NaN if no values have been added.
194 */
195 public double getMean() {
196 return apply(meanImpl);
197 }
198
199 /**
200 * Returns the <a href="http://www.xycoon.com/geometric_mean.htm">
201 * geometric mean </a> of the available values
202 * @return The geometricMean, Double.NaN if no values have been added,
203 * or if the product of the available values is less than or equal to 0.
204 */
205 public double getGeometricMean() {
206 return apply(geometricMeanImpl);
207 }
208
209 /**
210 * Returns the variance of the available values.
211 * @return The variance, Double.NaN if no values have been added
212 * or 0.0 for a single value set.
213 */
214 public double getVariance() {
215 return apply(varianceImpl);
216 }
217
218 /**
219 * Returns the standard deviation of the available values.
220 * @return The standard deviation, Double.NaN if no values have been added
221 * or 0.0 for a single value set.
222 */
223 public double getStandardDeviation() {
224 double stdDev = Double.NaN;
225 if (getN() > 0) {
226 if (getN() > 1) {
227 stdDev = FastMath.sqrt(getVariance());
228 } else {
229 stdDev = 0.0;
230 }
231 }
232 return stdDev;
233 }
234
235 /**
236 * Returns the skewness of the available values. Skewness is a
237 * measure of the asymmetry of a given distribution.
238 * @return The skewness, Double.NaN if no values have been added
239 * or 0.0 for a value set <=2.
240 */
241 public double getSkewness() {
242 return apply(skewnessImpl);
243 }
244
245 /**
246 * Returns the Kurtosis of the available values. Kurtosis is a
247 * measure of the "peakedness" of a distribution
248 * @return The kurtosis, Double.NaN if no values have been added, or 0.0
249 * for a value set <=3.
250 */
251 public double getKurtosis() {
252 return apply(kurtosisImpl);
253 }
254
255 /**
256 * Returns the maximum of the available values
257 * @return The max or Double.NaN if no values have been added.
258 */
259 public double getMax() {
260 return apply(maxImpl);
261 }
262
263 /**
264 * Returns the minimum of the available values
265 * @return The min or Double.NaN if no values have been added.
266 */
267 public double getMin() {
268 return apply(minImpl);
269 }
270
271 /**
272 * Returns the number of available values
273 * @return The number of available values
274 */
275 public long getN() {
276 return eDA.getNumElements();
277 }
278
279 /**
280 * Returns the sum of the values that have been added to Univariate.
281 * @return The sum or Double.NaN if no values have been added
282 */
283 public double getSum() {
284 return apply(sumImpl);
285 }
286
287 /**
288 * Returns the sum of the squares of the available values.
289 * @return The sum of the squares or Double.NaN if no
290 * values have been added.
291 */
292 public double getSumsq() {
293 return apply(sumsqImpl);
294 }
295
296 /**
297 * Resets all statistics and storage
298 */
299 public void clear() {
300 eDA.clear();
301 }
302
303
304 /**
305 * Returns the maximum number of values that can be stored in the
306 * dataset, or INFINITE_WINDOW (-1) if there is no limit.
307 *
308 * @return The current window size or -1 if its Infinite.
309 */
310 public int getWindowSize() {
311 return windowSize;
312 }
313
314 /**
315 * WindowSize controls the number of values which contribute
316 * to the reported statistics. For example, if
317 * windowSize is set to 3 and the values {1,2,3,4,5}
318 * have been added <strong> in that order</strong>
319 * then the <i>available values</i> are {3,4,5} and all
320 * reported statistics will be based on these values
321 * @param windowSize sets the size of the window.
322 */
323 public void setWindowSize(int windowSize) {
324 if (windowSize < 1) {
325 if (windowSize != INFINITE_WINDOW) {
326 throw MathRuntimeException.createIllegalArgumentException(
327 LocalizedFormats.NOT_POSITIVE_WINDOW_SIZE, windowSize);
328 }
329 }
330
331 this.windowSize = windowSize;
332
333 // We need to check to see if we need to discard elements
334 // from the front of the array. If the windowSize is less than
335 // the current number of elements.
336 if (windowSize != INFINITE_WINDOW && windowSize < eDA.getNumElements()) {
337 eDA.discardFrontElements(eDA.getNumElements() - windowSize);
338 }
339 }
340
341 /**
342 * Returns the current set of values in an array of double primitives.
343 * The order of addition is preserved. The returned array is a fresh
344 * copy of the underlying data -- i.e., it is not a reference to the
345 * stored data.
346 *
347 * @return returns the current set of numbers in the order in which they
348 * were added to this set
349 */
350 public double[] getValues() {
351 return eDA.getElements();
352 }
353
354 /**
355 * Returns the current set of values in an array of double primitives,
356 * sorted in ascending order. The returned array is a fresh
357 * copy of the underlying data -- i.e., it is not a reference to the
358 * stored data.
359 * @return returns the current set of
360 * numbers sorted in ascending order
361 */
362 public double[] getSortedValues() {
363 double[] sort = getValues();
364 Arrays.sort(sort);
365 return sort;
366 }
367
368 /**
369 * Returns the element at the specified index
370 * @param index The Index of the element
371 * @return return the element at the specified index
372 */
373 public double getElement(int index) {
374 return eDA.getElement(index);
375 }
376
377 /**
378 * Returns an estimate for the pth percentile of the stored values.
379 * <p>
380 * The implementation provided here follows the first estimation procedure presented
381 * <a href="http://www.itl.nist.gov/div898/handbook/prc/section2/prc252.htm">here.</a>
382 * </p><p>
383 * <strong>Preconditions</strong>:<ul>
384 * <li><code>0 < p ≤ 100</code> (otherwise an
385 * <code>IllegalArgumentException</code> is thrown)</li>
386 * <li>at least one value must be stored (returns <code>Double.NaN
387 * </code> otherwise)</li>
388 * </ul></p>
389 *
390 * @param p the requested percentile (scaled from 0 - 100)
391 * @return An estimate for the pth percentile of the stored data
392 * @throws IllegalStateException if percentile implementation has been
393 * overridden and the supplied implementation does not support setQuantile
394 * values
395 */
396 public double getPercentile(double p) {
397 if (percentileImpl instanceof Percentile) {
398 ((Percentile) percentileImpl).setQuantile(p);
399 } else {
400 try {
401 percentileImpl.getClass().getMethod(SET_QUANTILE_METHOD_NAME,
402 new Class[] {Double.TYPE}).invoke(percentileImpl,
403 new Object[] {Double.valueOf(p)});
404 } catch (NoSuchMethodException e1) { // Setter guard should prevent
405 throw MathRuntimeException.createIllegalArgumentException(
406 LocalizedFormats.PERCENTILE_IMPLEMENTATION_UNSUPPORTED_METHOD,
407 percentileImpl.getClass().getName(), SET_QUANTILE_METHOD_NAME);
408 } catch (IllegalAccessException e2) {
409 throw MathRuntimeException.createIllegalArgumentException(
410 LocalizedFormats.PERCENTILE_IMPLEMENTATION_CANNOT_ACCESS_METHOD,
411 SET_QUANTILE_METHOD_NAME, percentileImpl.getClass().getName());
412 } catch (InvocationTargetException e3) {
413 throw MathRuntimeException.createIllegalArgumentException(e3.getCause());
414 }
415 }
416 return apply(percentileImpl);
417 }
418
419 /**
420 * Generates a text report displaying univariate statistics from values
421 * that have been added. Each statistic is displayed on a separate
422 * line.
423 *
424 * @return String with line feeds displaying statistics
425 */
426 @Override
427 public String toString() {
428 StringBuilder outBuffer = new StringBuilder();
429 String endl = "\n";
430 outBuffer.append("DescriptiveStatistics:").append(endl);
431 outBuffer.append("n: ").append(getN()).append(endl);
432 outBuffer.append("min: ").append(getMin()).append(endl);
433 outBuffer.append("max: ").append(getMax()).append(endl);
434 outBuffer.append("mean: ").append(getMean()).append(endl);
435 outBuffer.append("std dev: ").append(getStandardDeviation())
436 .append(endl);
437 outBuffer.append("median: ").append(getPercentile(50)).append(endl);
438 outBuffer.append("skewness: ").append(getSkewness()).append(endl);
439 outBuffer.append("kurtosis: ").append(getKurtosis()).append(endl);
440 return outBuffer.toString();
441 }
442
443 /**
444 * Apply the given statistic to the data associated with this set of statistics.
445 * @param stat the statistic to apply
446 * @return the computed value of the statistic.
447 */
448 public double apply(UnivariateStatistic stat) {
449 return stat.evaluate(eDA.getInternalValues(), eDA.start(), eDA.getNumElements());
450 }
451
452 // Implementation getters and setter
453
454 /**
455 * Returns the currently configured mean implementation.
456 *
457 * @return the UnivariateStatistic implementing the mean
458 * @since 1.2
459 */
460 public synchronized UnivariateStatistic getMeanImpl() {
461 return meanImpl;
462 }
463
464 /**
465 * <p>Sets the implementation for the mean.</p>
466 *
467 * @param meanImpl the UnivariateStatistic instance to use
468 * for computing the mean
469 * @since 1.2
470 */
471 public synchronized void setMeanImpl(UnivariateStatistic meanImpl) {
472 this.meanImpl = meanImpl;
473 }
474
475 /**
476 * Returns the currently configured geometric mean implementation.
477 *
478 * @return the UnivariateStatistic implementing the geometric mean
479 * @since 1.2
480 */
481 public synchronized UnivariateStatistic getGeometricMeanImpl() {
482 return geometricMeanImpl;
483 }
484
485 /**
486 * <p>Sets the implementation for the gemoetric mean.</p>
487 *
488 * @param geometricMeanImpl the UnivariateStatistic instance to use
489 * for computing the geometric mean
490 * @since 1.2
491 */
492 public synchronized void setGeometricMeanImpl(
493 UnivariateStatistic geometricMeanImpl) {
494 this.geometricMeanImpl = geometricMeanImpl;
495 }
496
497 /**
498 * Returns the currently configured kurtosis implementation.
499 *
500 * @return the UnivariateStatistic implementing the kurtosis
501 * @since 1.2
502 */
503 public synchronized UnivariateStatistic getKurtosisImpl() {
504 return kurtosisImpl;
505 }
506
507 /**
508 * <p>Sets the implementation for the kurtosis.</p>
509 *
510 * @param kurtosisImpl the UnivariateStatistic instance to use
511 * for computing the kurtosis
512 * @since 1.2
513 */
514 public synchronized void setKurtosisImpl(UnivariateStatistic kurtosisImpl) {
515 this.kurtosisImpl = kurtosisImpl;
516 }
517
518 /**
519 * Returns the currently configured maximum implementation.
520 *
521 * @return the UnivariateStatistic implementing the maximum
522 * @since 1.2
523 */
524 public synchronized UnivariateStatistic getMaxImpl() {
525 return maxImpl;
526 }
527
528 /**
529 * <p>Sets the implementation for the maximum.</p>
530 *
531 * @param maxImpl the UnivariateStatistic instance to use
532 * for computing the maximum
533 * @since 1.2
534 */
535 public synchronized void setMaxImpl(UnivariateStatistic maxImpl) {
536 this.maxImpl = maxImpl;
537 }
538
539 /**
540 * Returns the currently configured minimum implementation.
541 *
542 * @return the UnivariateStatistic implementing the minimum
543 * @since 1.2
544 */
545 public synchronized UnivariateStatistic getMinImpl() {
546 return minImpl;
547 }
548
549 /**
550 * <p>Sets the implementation for the minimum.</p>
551 *
552 * @param minImpl the UnivariateStatistic instance to use
553 * for computing the minimum
554 * @since 1.2
555 */
556 public synchronized void setMinImpl(UnivariateStatistic minImpl) {
557 this.minImpl = minImpl;
558 }
559
560 /**
561 * Returns the currently configured percentile implementation.
562 *
563 * @return the UnivariateStatistic implementing the percentile
564 * @since 1.2
565 */
566 public synchronized UnivariateStatistic getPercentileImpl() {
567 return percentileImpl;
568 }
569
570 /**
571 * Sets the implementation to be used by {@link #getPercentile(double)}.
572 * The supplied <code>UnivariateStatistic</code> must provide a
573 * <code>setQuantile(double)</code> method; otherwise
574 * <code>IllegalArgumentException</code> is thrown.
575 *
576 * @param percentileImpl the percentileImpl to set
577 * @throws IllegalArgumentException if the supplied implementation does not
578 * provide a <code>setQuantile</code> method
579 * @since 1.2
580 */
581 public synchronized void setPercentileImpl(
582 UnivariateStatistic percentileImpl) {
583 try {
584 percentileImpl.getClass().getMethod(SET_QUANTILE_METHOD_NAME,
585 new Class[] {Double.TYPE}).invoke(percentileImpl,
586 new Object[] {Double.valueOf(50.0d)});
587 } catch (NoSuchMethodException e1) {
588 throw MathRuntimeException.createIllegalArgumentException(
589 LocalizedFormats.PERCENTILE_IMPLEMENTATION_UNSUPPORTED_METHOD,
590 percentileImpl.getClass().getName(), SET_QUANTILE_METHOD_NAME);
591 } catch (IllegalAccessException e2) {
592 throw MathRuntimeException.createIllegalArgumentException(
593 LocalizedFormats.PERCENTILE_IMPLEMENTATION_CANNOT_ACCESS_METHOD,
594 SET_QUANTILE_METHOD_NAME, percentileImpl.getClass().getName());
595 } catch (InvocationTargetException e3) {
596 throw MathRuntimeException.createIllegalArgumentException(e3.getCause());
597 }
598 this.percentileImpl = percentileImpl;
599 }
600
601 /**
602 * Returns the currently configured skewness implementation.
603 *
604 * @return the UnivariateStatistic implementing the skewness
605 * @since 1.2
606 */
607 public synchronized UnivariateStatistic getSkewnessImpl() {
608 return skewnessImpl;
609 }
610
611 /**
612 * <p>Sets the implementation for the skewness.</p>
613 *
614 * @param skewnessImpl the UnivariateStatistic instance to use
615 * for computing the skewness
616 * @since 1.2
617 */
618 public synchronized void setSkewnessImpl(
619 UnivariateStatistic skewnessImpl) {
620 this.skewnessImpl = skewnessImpl;
621 }
622
623 /**
624 * Returns the currently configured variance implementation.
625 *
626 * @return the UnivariateStatistic implementing the variance
627 * @since 1.2
628 */
629 public synchronized UnivariateStatistic getVarianceImpl() {
630 return varianceImpl;
631 }
632
633 /**
634 * <p>Sets the implementation for the variance.</p>
635 *
636 * @param varianceImpl the UnivariateStatistic instance to use
637 * for computing the variance
638 * @since 1.2
639 */
640 public synchronized void setVarianceImpl(
641 UnivariateStatistic varianceImpl) {
642 this.varianceImpl = varianceImpl;
643 }
644
645 /**
646 * Returns the currently configured sum of squares implementation.
647 *
648 * @return the UnivariateStatistic implementing the sum of squares
649 * @since 1.2
650 */
651 public synchronized UnivariateStatistic getSumsqImpl() {
652 return sumsqImpl;
653 }
654
655 /**
656 * <p>Sets the implementation for the sum of squares.</p>
657 *
658 * @param sumsqImpl the UnivariateStatistic instance to use
659 * for computing the sum of squares
660 * @since 1.2
661 */
662 public synchronized void setSumsqImpl(UnivariateStatistic sumsqImpl) {
663 this.sumsqImpl = sumsqImpl;
664 }
665
666 /**
667 * Returns the currently configured sum implementation.
668 *
669 * @return the UnivariateStatistic implementing the sum
670 * @since 1.2
671 */
672 public synchronized UnivariateStatistic getSumImpl() {
673 return sumImpl;
674 }
675
676 /**
677 * <p>Sets the implementation for the sum.</p>
678 *
679 * @param sumImpl the UnivariateStatistic instance to use
680 * for computing the sum
681 * @since 1.2
682 */
683 public synchronized void setSumImpl(UnivariateStatistic sumImpl) {
684 this.sumImpl = sumImpl;
685 }
686
687 /**
688 * Returns a copy of this DescriptiveStatistics instance with the same internal state.
689 *
690 * @return a copy of this
691 */
692 public DescriptiveStatistics copy() {
693 DescriptiveStatistics result = new DescriptiveStatistics();
694 copy(this, result);
695 return result;
696 }
697
698 /**
699 * Copies source to dest.
700 * <p>Neither source nor dest can be null.</p>
701 *
702 * @param source DescriptiveStatistics to copy
703 * @param dest DescriptiveStatistics to copy to
704 * @throws NullArgumentException if either source or dest is null
705 */
706 public static void copy(DescriptiveStatistics source, DescriptiveStatistics dest)
707 throws NullArgumentException {
708 MathUtils.checkNotNull(source);
709 MathUtils.checkNotNull(dest);
710 // Copy data and window size
711 dest.eDA = source.eDA.copy();
712 dest.windowSize = source.windowSize;
713
714 // Copy implementations
715 dest.maxImpl = source.maxImpl.copy();
716 dest.meanImpl = source.meanImpl.copy();
717 dest.minImpl = source.minImpl.copy();
718 dest.sumImpl = source.sumImpl.copy();
719 dest.varianceImpl = source.varianceImpl.copy();
720 dest.sumsqImpl = source.sumsqImpl.copy();
721 dest.geometricMeanImpl = source.geometricMeanImpl.copy();
722 dest.kurtosisImpl = source.kurtosisImpl;
723 dest.skewnessImpl = source.skewnessImpl;
724 dest.percentileImpl = source.percentileImpl;
725 }
726 }