1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.numbers.examples.jmh.core;
18
19 import java.math.BigDecimal;
20 import java.math.MathContext;
21 import java.util.HashMap;
22 import java.util.LinkedHashMap;
23 import java.util.Map;
24 import java.util.function.ToDoubleFunction;
25
26
27
28
29
30 public class EuclideanNormEvaluator {
31
32
33 private final Map<String, ToDoubleFunction<double[]>> methods = new LinkedHashMap<>();
34
35
36
37
38
39
40 public EuclideanNormEvaluator addMethod(final String name, final ToDoubleFunction<double[]> method) {
41 methods.put(name, method);
42 return this;
43 }
44
45
46
47
48
49 public Map<String, Stats> evaluate(final double[][] inputs) {
50
51 final Map<String, StatsAccumulator> accumulators = new HashMap<>();
52 for (final String name : methods.keySet()) {
53 accumulators.put(name, new StatsAccumulator(inputs.length * 2));
54 }
55
56 for (int i = 0; i < inputs.length; ++i) {
57
58
59 final double[] vec = inputs[i];
60
61 final double[] reverseVec = new double[vec.length];
62 for (int j = 0; j < vec.length; ++j) {
63 reverseVec[vec.length - 1 - j] = vec[j];
64 }
65
66 final double exact = computeExact(vec);
67
68 for (final Map.Entry<String, ToDoubleFunction<double[]>> entry : methods.entrySet()) {
69 final ToDoubleFunction<double[]> fn = entry.getValue();
70
71 final StatsAccumulator acc = accumulators.get(entry.getKey());
72
73 final double forwardSample = fn.applyAsDouble(vec);
74 acc.report(exact, forwardSample);
75
76 final double reverseSample = fn.applyAsDouble(reverseVec);
77 acc.report(exact, reverseSample);
78 }
79 }
80
81 final Map<String, Stats> stats = new LinkedHashMap<>();
82 for (final String name : methods.keySet()) {
83 stats.put(name, accumulators.get(name).computeStats());
84 }
85
86 return stats;
87 }
88
89
90
91
92
93
94 private static double computeExact(final double[] vec) {
95 final MathContext ctx = MathContext.DECIMAL128;
96
97 BigDecimal sum = BigDecimal.ZERO;
98 for (final double v : vec) {
99 sum = sum.add(new BigDecimal(v).pow(2), ctx);
100 }
101
102 return sum.sqrt(ctx).doubleValue();
103 }
104
105
106
107
108
109
110 private static int computeUlpDifference(final double a, final double b) {
111 return (int) (Double.doubleToLongBits(a) - Double.doubleToLongBits(b));
112 }
113
114
115
116 public static final class Stats {
117
118
119 private final double ulpErrorMean;
120
121
122 private final double ulpErrorStdDev;
123
124
125 private final double ulpErrorMin;
126
127
128 private final double ulpErrorMax;
129
130
131 private final int failCount;
132
133
134
135
136
137
138
139
140 Stats(final double ulpErrorMean, final double ulpErrorStdDev, final double ulpErrorMin,
141 final double ulpErrorMax, final int failCount) {
142 this.ulpErrorMean = ulpErrorMean;
143 this.ulpErrorStdDev = ulpErrorStdDev;
144 this.ulpErrorMin = ulpErrorMin;
145 this.ulpErrorMax = ulpErrorMax;
146 this.failCount = failCount;
147 }
148
149
150
151
152 public double getUlpErrorMean() {
153 return ulpErrorMean;
154 }
155
156
157
158
159 public double getUlpErrorStdDev() {
160 return ulpErrorStdDev;
161 }
162
163
164
165
166 public double getUlpErrorMin() {
167 return ulpErrorMin;
168 }
169
170
171
172
173 public double getUlpErrorMax() {
174 return ulpErrorMax;
175 }
176
177
178
179
180
181 public int getFailCount() {
182 return failCount;
183 }
184 }
185
186
187
188 private static final class StatsAccumulator {
189
190
191 private int sampleIdx;
192
193
194 private final double[] ulpErrors;
195
196
197
198
199 StatsAccumulator(final int count) {
200 ulpErrors = new double[count];
201 }
202
203
204
205
206
207 public void report(final double expected, final double actual) {
208 ulpErrors[sampleIdx++] = Double.isFinite(actual) && actual != 0.0 ?
209 computeUlpDifference(expected, actual) :
210 Double.NaN;
211 }
212
213
214
215
216 public Stats computeStats() {
217 int successCount = 0;
218 double sum = 0d;
219 double min = Double.POSITIVE_INFINITY;
220 double max = Double.NEGATIVE_INFINITY;
221
222 for (double ulpError : ulpErrors) {
223 if (Double.isFinite(ulpError)) {
224 ++successCount;
225 min = Math.min(ulpError, min);
226 max = Math.max(ulpError, max);
227 sum += ulpError;
228 }
229 }
230
231 final double mean = sum / successCount;
232
233 double diffSumSq = 0d;
234 double diff;
235 for (double ulpError : ulpErrors) {
236 if (Double.isFinite(ulpError)) {
237 diff = ulpError - mean;
238 diffSumSq += diff * diff;
239 }
240 }
241
242 final double stdDev = successCount > 1 ?
243 Math.sqrt(diffSumSq / (successCount - 1)) :
244 0d;
245
246 return new Stats(mean, stdDev, min, max, ulpErrors.length - successCount);
247 }
248 }
249 }