1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.hipparchus.stat.descriptive.rank;
18
19 import java.io.Serializable;
20 import java.util.ArrayList;
21 import java.util.Arrays;
22 import java.util.Collection;
23 import java.util.HashMap;
24 import java.util.Iterator;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.NoSuchElementException;
28 import java.util.UUID;
29
30 import org.hipparchus.exception.LocalizedCoreFormats;
31 import org.hipparchus.exception.MathIllegalArgumentException;
32 import org.hipparchus.exception.MathIllegalStateException;
33 import org.hipparchus.exception.NullArgumentException;
34 import org.hipparchus.random.RandomGenerator;
35 import org.hipparchus.random.Well19937c;
36 import org.hipparchus.stat.StatUtils;
37 import org.hipparchus.stat.descriptive.AbstractStorelessUnivariateStatistic;
38 import org.hipparchus.stat.descriptive.AggregatableStatistic;
39 import org.hipparchus.stat.descriptive.StorelessUnivariateStatistic;
40 import org.hipparchus.util.FastMath;
41 import org.hipparchus.util.MathArrays;
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88 public class RandomPercentile
89 extends AbstractStorelessUnivariateStatistic implements StorelessUnivariateStatistic,
90 AggregatableStatistic<RandomPercentile>, Serializable {
91
92
93 public static final double DEFAULT_EPSILON = 1e-4;
94
95 private static final long serialVersionUID = 1L;
96
97 private final int s;
98
99 private final int h;
100
101 private final BufferMap bufferMap;
102
103 private final double epsilon;
104
105 private final RandomGenerator randomGenerator;
106
107 private long n;
108
109 private Buffer currentBuffer;
110
111
112
113
114
115
116
117
118
119 public RandomPercentile(double epsilon, RandomGenerator randomGenerator) {
120 if (epsilon <= 0) {
121 throw new MathIllegalArgumentException(LocalizedCoreFormats.NUMBER_TOO_SMALL,
122 epsilon, 0);
123 }
124 this.h = (int) FastMath.ceil(log2(1/epsilon));
125 this.s = (int) FastMath.ceil(FastMath.sqrt(log2(1/epsilon)) / epsilon);
126 this.randomGenerator = randomGenerator;
127 bufferMap = new BufferMap(h + 1, s, randomGenerator);
128 currentBuffer = bufferMap.create(0);
129 this.epsilon = epsilon;
130 }
131
132
133
134
135
136
137
138
139 public RandomPercentile(RandomGenerator randomGenerator) {
140 this(DEFAULT_EPSILON, randomGenerator);
141 }
142
143
144
145
146
147
148
149
150 public RandomPercentile(double epsilon) {
151 this(epsilon, new Well19937c());
152 }
153
154
155
156
157
158
159 public RandomPercentile() {
160 this(DEFAULT_EPSILON, new Well19937c());
161 }
162
163
164
165
166
167
168
169
170
171 public RandomPercentile(RandomPercentile original) {
172 super();
173 this.h = original.h;
174 this.n = original.n;
175 this.s = original.s;
176 this.epsilon = original.epsilon;
177 this.bufferMap = new BufferMap(original.bufferMap);
178 this.randomGenerator = original.randomGenerator;
179 Iterator<Buffer> iterator = bufferMap.iterator();
180 Buffer current = null;
181 Buffer curr = null;
182
183 while (current == null && iterator.hasNext()) {
184 curr = iterator.next();
185 if (curr.hasCapacity()) {
186 current = curr;
187 }
188 }
189
190
191
192 this.currentBuffer = current == null ? curr : current;
193 }
194
195 @Override
196 public long getN() {
197 return n;
198 }
199
200
201
202
203
204
205
206
207
208
209
210
211
212 public double evaluate(final double percentile, final double[] values, final int begin, final int length)
213 throws MathIllegalArgumentException {
214 if (MathArrays.verifyValues(values, begin, length)) {
215 RandomPercentile randomPercentile = new RandomPercentile(this.epsilon,
216 this.randomGenerator);
217 randomPercentile.incrementAll(values, begin, length);
218 return randomPercentile.getResult(percentile);
219 }
220 return Double.NaN;
221 }
222
223
224
225
226
227
228
229
230
231
232
233
234 @Override
235 public double evaluate(final double[] values, final int begin, final int length) {
236 return evaluate(50d, values, begin, length);
237 }
238
239
240
241
242
243
244
245
246
247
248 public double evaluate(final double percentile, final double[] values) {
249 return evaluate(percentile, values, 0, values.length);
250 }
251
252 @Override
253 public RandomPercentile copy() {
254 return new RandomPercentile(this);
255 }
256
257 @Override
258 public void clear() {
259 n = 0;
260 bufferMap.clear();
261 currentBuffer = bufferMap.create(0);
262 }
263
264
265
266
267 @Override
268 public double getResult() {
269 return getResult(50d);
270
271 }
272
273
274
275
276
277
278
279
280 public double getResult(double percentile) {
281 if (percentile > 100 || percentile < 0) {
282 throw new MathIllegalArgumentException(LocalizedCoreFormats.OUT_OF_RANGE,
283 percentile, 0, 100);
284 }
285
286 final double q = percentile / 100;
287
288 double min = Double.POSITIVE_INFINITY;
289 double max = Double.NEGATIVE_INFINITY;
290 double bMin;
291 double bMax;
292 Iterator<Buffer> bufferIterator = bufferMap.iterator();
293 while (bufferIterator.hasNext()) {
294 Buffer buffer = bufferIterator.next();
295 bMin = buffer.min();
296 if (bMin < min) {
297 min = bMin;
298 }
299 bMax = buffer.max();
300 if (bMax > max) {
301 max = bMax;
302 }
303 }
304
305
306 if (Double.compare(q, 0d) == 0 || n == 1) {
307 return min;
308 }
309 if (Double.compare(q, 1) == 0) {
310 return max;
311 }
312 if (n == 0) {
313 return Double.NaN;
314 }
315
316
317
318 if (bufferMap.halfEmpty()) {
319 return new Percentile(percentile).evaluate(bufferMap.levelZeroData());
320 }
321
322
323 final double targetRank = q * n;
324
325
326 double estimate = min + q * (max - min);
327 double estimateRank = getRank(estimate);
328 double lower;
329 double upper;
330 if (estimateRank > targetRank) {
331 upper = estimate;
332 lower = min;
333 } else if (estimateRank < targetRank) {
334 lower = estimate;
335 upper = max;
336 } else {
337 return estimate;
338 }
339 final double eps = epsilon / 2;
340 final double rankTolerance = eps * n;
341 final double minWidth = eps / n;
342 double intervalWidth = FastMath.abs(upper - lower);
343 while (FastMath.abs(estimateRank - targetRank) > rankTolerance && intervalWidth > minWidth) {
344 if (estimateRank > targetRank) {
345 upper = estimate;
346 } else {
347 lower = estimate;
348 }
349 intervalWidth = upper - lower;
350 estimate = lower + intervalWidth / 2;
351 estimateRank = getRank(estimate);
352 }
353 return estimate;
354 }
355
356
357
358
359
360
361
362
363 public double getRank(double value) {
364 double rankSum = 0;
365 Iterator<Buffer> bufferIterator = bufferMap.iterator();
366 while (bufferIterator.hasNext()) {
367 Buffer buffer = bufferIterator.next();
368 rankSum += buffer.rankOf(value) * FastMath.pow(2, buffer.level);
369 }
370 return rankSum;
371 }
372
373
374
375
376
377
378
379
380
381 public double getQuantileRank(double value) {
382 return getRank(value) / getN();
383 }
384
385 @Override
386 public void increment(double d) {
387 n++;
388 if (!currentBuffer.hasCapacity()) {
389
390 if (bufferMap.canCreate()) {
391 final int level = (int) Math.ceil(Math.max(0, log2(n/(s * FastMath.pow(2, h - 1)))));
392 currentBuffer = bufferMap.create(level);
393 } else {
394 currentBuffer = bufferMap.merge();
395 }
396 }
397 currentBuffer.consume(d);
398 }
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422 private static class Buffer implements Serializable {
423
424 private static final long serialVersionUID = 1L;
425
426 private final int size;
427
428 private final double[] data;
429
430 private final RandomGenerator randomGenerator;
431
432 private int level;
433
434 private long blockSize;
435
436 private int next;
437
438 private long consumed;
439
440 private long nextToTake;
441
442 private final UUID id;
443
444
445
446
447
448
449
450
451
452 Buffer(int size, int level, RandomGenerator randomGenerator) {
453 this.size = size;
454 data = new double[size];
455 this.level = level;
456 this.randomGenerator = randomGenerator;
457 this.id = UUID.randomUUID();
458 computeBlockSize();
459 }
460
461
462
463
464 private void computeBlockSize() {
465 if (level == 0) {
466 blockSize = 1;
467 } else {
468 long product = 1;
469 for (int i = 0; i < level; i++) {
470 product *= 2;
471 }
472 blockSize = product;
473 }
474 if (blockSize > 1) {
475 nextToTake = randomGenerator.nextLong(blockSize);
476 }
477 }
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495 public void consume(double value) {
496 if (consumed == nextToTake) {
497 data[next] = value;
498 next++;
499 }
500 consumed++;
501 if (consumed == blockSize) {
502 if (next == size) {
503 Arrays.sort(data);
504 } else {
505 consumed = 0;
506 if (blockSize > 1) {
507 nextToTake = randomGenerator.nextLong(blockSize);
508 }
509 }
510 }
511 }
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530 public void mergeWith(Buffer other) {
531
532 if (this.hasCapacity() || other.hasCapacity() || other.level != this.level) {
533 throw new MathIllegalArgumentException(LocalizedCoreFormats.INTERNAL_ERROR);
534 }
535
536 for (int i = 0; i < size; i++) {
537 if (randomGenerator.nextBoolean()) {
538 data[i] = other.data[i];
539 }
540 }
541
542 Arrays.sort(data);
543
544 other.setLevel(level + 1);
545 this.setLevel(level + 1);
546
547 other.clear();
548 }
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573 public void mergeInto(Buffer higher) {
574
575 if (this.size != higher.size || this.hasCapacity() || higher.hasCapacity() ||
576 this.level >= higher.level) {
577 throw new MathIllegalArgumentException(LocalizedCoreFormats.INTERNAL_ERROR);
578 }
579 final int levelDifference = higher.level - this.level;
580 int m = 1;
581 for (int i = 0; i < levelDifference; i++) {
582 m *= 2;
583 }
584
585
586 for (int i = 0; i < size; i++) {
587
588 if (randomGenerator.nextInt(m + 1) == 0) {
589 higher.data[i] = data[i];
590 }
591 }
592
593 Arrays.sort(higher.data);
594 }
595
596
597
598
599 public boolean hasCapacity() {
600
601
602 return next < size || consumed < blockSize;
603 }
604
605
606
607
608
609
610 public void setLevel(int level) {
611 this.level = level;
612 }
613
614
615
616
617 public void clear() {
618 consumed = 0;
619 next = 0;
620 computeBlockSize();
621 }
622
623
624
625
626
627
628 public double[] getData() {
629 final double[] out = new double[next];
630 System.arraycopy(data, 0, out, 0, next);
631 return out;
632 }
633
634
635
636
637
638
639
640 public int rankOf(double value) {
641 int ret = 0;
642 if (!hasCapacity()) {
643 ret = Arrays.binarySearch(data, value);
644 if (ret < 0) {
645 return -ret - 1;
646 } else {
647 return ret;
648 }
649 } else {
650 for (int i = 0; i < next; i++) {
651 if (data[i] < value) {
652 ret++;
653 }
654 }
655 return ret;
656 }
657 }
658
659
660
661
662 public double min() {
663 if (!hasCapacity()) {
664 return data[0];
665 } else {
666 return StatUtils.min(getData());
667 }
668 }
669
670
671
672
673 public double max() {
674 if (!hasCapacity()) {
675 return data[data.length - 1];
676 } else {
677 return StatUtils.max(getData());
678 }
679 }
680
681
682
683
684 public int getLevel() {
685 return level;
686 }
687
688
689
690
691 public UUID getId() {
692 return id;
693 }
694 }
695
696
697
698
699
700
701
702 private static double log2(double x) {
703 return Math.log(x) / Math.log(2);
704 }
705
706
707
708
709
710
711 private static class BufferMap implements Iterable<Buffer>, Serializable {
712
713 private static final long serialVersionUID = 1L;
714
715 private final int capacity;
716
717 private final RandomGenerator randomGenerator;
718
719 private int count;
720
721 private final int bufferSize;
722
723 private final Map<Integer,List<Buffer>> registry;
724
725 private int maxLevel;
726
727
728
729
730
731
732
733
734
735 BufferMap(int capacity, int bufferSize, RandomGenerator randomGenerator) {
736 this.bufferSize = bufferSize;
737 this.capacity = capacity;
738 this.randomGenerator = randomGenerator;
739 this.registry = new HashMap<>();
740 }
741
742
743
744
745
746
747 BufferMap(BufferMap original) {
748 super();
749 this.bufferSize = original.bufferSize;
750 this.capacity = original.capacity;
751 this.count = 0;
752 this.randomGenerator = original.randomGenerator;
753 this.registry = new HashMap<>();
754 Iterator<Buffer> iterator = original.iterator();
755 while (iterator.hasNext()) {
756 final Buffer current = iterator.next();
757
758 final Buffer newCopy = create(current.getLevel());
759
760 final double[] data = current.getData();
761 for (double value : data) {
762 newCopy.consume(value);
763 }
764 }
765 }
766
767
768
769
770
771
772
773
774
775
776
777
778 public Buffer create(int level) {
779 if (!canCreate()) {
780 return null;
781 }
782 count++;
783 Buffer buffer = new Buffer(bufferSize, level, randomGenerator);
784 List<Buffer> bufferList = registry.get(level);
785 if (bufferList == null) {
786 bufferList = new ArrayList<>();
787 registry.put(level, bufferList);
788 }
789 bufferList.add(buffer);
790 if (level > maxLevel) {
791 maxLevel = level;
792 }
793 return buffer;
794 }
795
796
797
798
799
800
801 public boolean canCreate() {
802 return count < capacity;
803 }
804
805
806
807
808
809
810
811
812
813
814
815
816
817 public boolean halfEmpty() {
818 return count * 2 < capacity &&
819 registry.size() == 1 &&
820 registry.containsKey(0);
821 }
822
823
824
825
826
827
828 public double[] levelZeroData() {
829 List<Buffer> levelZeroBuffers = registry.get(0);
830
831 int length = 0;
832 for (Buffer buffer : levelZeroBuffers) {
833 if (!buffer.hasCapacity()) {
834 length += buffer.size;
835 } else {
836 length += buffer.next;
837 }
838 }
839
840 int pos = 0;
841 int currLen;
842 final double[] out = new double[length];
843 for (Buffer buffer : levelZeroBuffers) {
844 if (!buffer.hasCapacity()) {
845 currLen = buffer.size;
846 } else {
847 currLen = buffer.next;
848 }
849 System.arraycopy(buffer.data, 0, out, pos, currLen);
850 pos += currLen;
851 }
852 return out;
853 }
854
855
856
857
858
859
860
861
862 public Buffer merge() {
863 int l = 0;
864 List<Buffer> mergeCandidates = null;
865
866 while (mergeCandidates == null && l <= maxLevel) {
867 final List<Buffer> bufferList = registry.get(l);
868 if (bufferList != null && bufferList.size() > 1) {
869 mergeCandidates = bufferList;
870 } else {
871 l++;
872 }
873 }
874 if (mergeCandidates == null) {
875
876 throw new MathIllegalStateException(LocalizedCoreFormats.INTERNAL_ERROR);
877 }
878 Buffer buffer1 = mergeCandidates.get(0);
879 Buffer buffer2 = mergeCandidates.get(1);
880
881 mergeCandidates.remove(0);
882 mergeCandidates.remove(0);
883
884 if (registry.get(l).isEmpty()) {
885 registry.remove(l);
886 }
887
888 buffer1.mergeWith(buffer2);
889
890
891 register(buffer1);
892 register(buffer2);
893
894
895 return buffer2;
896 }
897
898
899
900
901 public void clear() {
902 for (List<Buffer> bufferList : registry.values()) {
903 bufferList.clear();
904 }
905 registry.clear();
906 count = 0;
907 }
908
909
910
911
912
913
914 public void register(Buffer buffer) {
915 final int level = buffer.getLevel();
916 List<Buffer> list = registry.get(level);
917 if (list == null) {
918 list = new ArrayList<>();
919 registry.put(level, list);
920 if (level > maxLevel) {
921 maxLevel = level;
922 }
923 }
924 list.add(buffer);
925 }
926
927
928
929
930
931
932
933 public void deRegister(Buffer buffer) {
934 final Iterator<Buffer> iterator = registry.get(buffer.getLevel()).iterator();
935 while (iterator.hasNext()) {
936 if (iterator.next().getId().equals(buffer.getId())) {
937 iterator.remove();
938 return;
939 }
940 }
941 throw new MathIllegalStateException(LocalizedCoreFormats.INTERNAL_ERROR);
942 }
943
944
945
946
947
948 @Override
949 public Iterator<Buffer> iterator() {
950 return new Iterator<Buffer>() {
951
952
953 private final Iterator<Integer> levelIterator = registry.keySet().iterator();
954
955
956 private List<Buffer> currentList = registry.get(levelIterator.next());
957
958
959 private Iterator<Buffer> bufferIterator =
960 currentList == null ? null : currentList.iterator();
961
962 @Override
963 public boolean hasNext() {
964 if (bufferIterator == null) {
965 return false;
966 }
967 if (bufferIterator.hasNext()) {
968 return true;
969 }
970
971 if (levelIterator.hasNext()) {
972 List<Buffer> currentList = registry.get(levelIterator.next());
973 bufferIterator = currentList.iterator();
974 return true;
975 } else {
976
977 bufferIterator = null;
978 return false;
979 }
980 }
981
982 @Override
983 public Buffer next() {
984 if (hasNext()) {
985 return bufferIterator.next();
986 }
987 throw new NoSuchElementException();
988 }
989
990 @Override
991 public void remove() {
992 throw new UnsupportedOperationException();
993 }
994 };
995 }
996
997
998
999
1000
1001
1002
1003
1004 public void absorb(BufferMap other) {
1005
1006 boolean full = true;
1007 Iterator<Buffer> otherIterator = other.iterator();
1008 while (otherIterator.hasNext()) {
1009 Buffer buffer = otherIterator.next();
1010 if (buffer.hasCapacity()) {
1011 full = false;
1012 }
1013 register(buffer);
1014 count++;
1015 }
1016
1017 while (count > capacity || (count == capacity && full)) {
1018 mergeUp();
1019 count--;
1020 }
1021 }
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033 public void mergeUp() {
1034
1035
1036
1037
1038 Iterator<Buffer> bufferIterator = iterator();
1039 Buffer first = null;
1040 Buffer second = null;
1041 while ((first == null || second == null) && bufferIterator.hasNext()) {
1042 Buffer buffer = bufferIterator.next();
1043 if (!buffer.hasCapacity()) {
1044 if (first == null) {
1045 first = buffer;
1046 } else {
1047 second = buffer;
1048 }
1049 }
1050 }
1051 if (first == null || second == null || first.level > second.level) {
1052 throw new MathIllegalStateException(LocalizedCoreFormats.INTERNAL_ERROR);
1053 }
1054
1055
1056 if (first.getLevel() == second.getLevel()) {
1057 deRegister(first);
1058 deRegister(second);
1059 second.mergeWith(first);
1060 register(second);
1061 } else {
1062 deRegister(first);
1063 first.mergeInto(second);
1064 }
1065 }
1066 }
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078 public double reduce(double percentile, Collection<RandomPercentile> aggregates) {
1079 if (percentile > 100 || percentile < 0) {
1080 throw new MathIllegalArgumentException(LocalizedCoreFormats.OUT_OF_RANGE,
1081 percentile, 0, 100);
1082 }
1083
1084
1085
1086
1087
1088 Iterator<RandomPercentile> iterator = aggregates.iterator();
1089 boolean small = true;
1090 while (small && iterator.hasNext()) {
1091 small = iterator.next().bufferMap.halfEmpty();
1092 }
1093 if (small) {
1094 iterator = aggregates.iterator();
1095 double[] combined = {};
1096 while (iterator.hasNext()) {
1097 combined = MathArrays.concatenate(combined, iterator.next().bufferMap.levelZeroData());
1098 }
1099 final Percentile exactP = new Percentile(percentile);
1100 return exactP.evaluate(combined);
1101 }
1102
1103
1104
1105
1106
1107 double min = Double.POSITIVE_INFINITY;
1108 double max = Double.NEGATIVE_INFINITY;
1109 double combinedN = 0;
1110 iterator = aggregates.iterator();
1111 while (iterator.hasNext()) {
1112 final RandomPercentile curr = iterator.next();
1113 final double curMin = curr.getResult(0);
1114 final double curMax = curr.getResult(100);
1115 if (curMin < min) {
1116 min = curMin;
1117 }
1118 if (curMax > max) {
1119 max = curMax;
1120 }
1121 combinedN += curr.getN();
1122 }
1123
1124 final double q = percentile / 100;
1125
1126 if (Double.compare(q, 0d) == 0) {
1127 return min;
1128 }
1129 if (Double.compare(q, 1) == 0) {
1130 return max;
1131 }
1132
1133
1134 final double targetRank = q * combinedN;
1135
1136
1137
1138 double estimate = min + q * (max - min);
1139 double estimateRank = getAggregateRank(estimate, aggregates);
1140 double lower;
1141 double upper;
1142 if (estimateRank > targetRank) {
1143 upper = estimate;
1144 lower = min;
1145 } else if (estimateRank < targetRank) {
1146 lower = estimate;
1147 upper = max;
1148 } else {
1149 return estimate;
1150 }
1151 final double eps = epsilon / 2;
1152 double intervalWidth = FastMath.abs(upper - lower);
1153 while (FastMath.abs(estimateRank / combinedN - q) > eps && intervalWidth > eps / combinedN) {
1154 if (estimateRank == targetRank) {
1155 return estimate;
1156 }
1157 if (estimateRank > targetRank) {
1158 upper = estimate;
1159 } else {
1160 lower = estimate;
1161 }
1162 intervalWidth = FastMath.abs(upper - lower);
1163 estimate = lower + intervalWidth / 2;
1164 estimateRank = getAggregateRank(estimate, aggregates);
1165 }
1166 return estimate;
1167 }
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177 public double getAggregateRank(double value, Collection<RandomPercentile> aggregates) {
1178 double result = 0;
1179 final Iterator<RandomPercentile> iterator = aggregates.iterator();
1180 while (iterator.hasNext()) {
1181 result += iterator.next().getRank(value);
1182 }
1183 return result;
1184 }
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196 public double getAggregateQuantileRank(double value, Collection<RandomPercentile> aggregates) {
1197 return getAggregateRank(value, aggregates) / getAggregateN(aggregates);
1198 }
1199
1200
1201
1202
1203
1204
1205
1206 public double getAggregateN(Collection<RandomPercentile> aggregates) {
1207 double result = 0;
1208 final Iterator<RandomPercentile> iterator = aggregates.iterator();
1209 while (iterator.hasNext()) {
1210 result += iterator.next().getN();
1211 }
1212 return result;
1213 }
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228 @Override
1229 public void aggregate(RandomPercentile other)
1230 throws NullArgumentException {
1231 if (other == null) {
1232 throw new NullArgumentException();
1233 }
1234 if (other.s != s) {
1235 throw new MathIllegalArgumentException(LocalizedCoreFormats.INTERNAL_ERROR);
1236 }
1237 bufferMap.absorb(other.bufferMap);
1238 n += other.n;
1239 }
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252 public static long maxValuesRetained(double epsilon) {
1253 if (epsilon >= 1) {
1254 throw new MathIllegalArgumentException(
1255 LocalizedCoreFormats.NUMBER_TOO_LARGE_BOUND_EXCLUDED, epsilon, 1);
1256 }
1257 if (epsilon <= 0) {
1258 throw new MathIllegalArgumentException(
1259 LocalizedCoreFormats.NUMBER_TOO_SMALL_BOUND_EXCLUDED, epsilon, 0);
1260 }
1261 final long h = (long) FastMath.ceil(log2(1/epsilon));
1262 final long s = (long) FastMath.ceil(FastMath.sqrt(log2(1/epsilon)) / epsilon);
1263 return (h+1) * s;
1264 }
1265 }