0
# Double Value Histograms
1
2
Histogram implementations for recording and analyzing double (floating-point) values with configurable precision and dynamic range. These histograms provide the same statistical analysis capabilities as integer histograms but for continuous-valued data.
3
4
## DoubleHistogram
5
6
Core implementation for recording double values with HDR precision.
7
8
```java { .api }
9
public class DoubleHistogram extends EncodableHistogram
10
implements DoubleValueRecorder, Serializable {
11
12
// Constructors
13
public DoubleHistogram(int numberOfSignificantValueDigits);
14
public DoubleHistogram(long highestToLowestValueRatio, int numberOfSignificantValueDigits);
15
public DoubleHistogram(int numberOfSignificantValueDigits,
16
Class<? extends AbstractHistogram> internalCountsHistogramClass);
17
18
// Factory methods
19
static DoubleHistogram decodeFromByteBuffer(ByteBuffer buffer,
20
long minBarForHighestToLowestValueRatio);
21
static DoubleHistogram decodeFromCompressedByteBuffer(ByteBuffer buffer,
22
long minBarForHighestToLowestValueRatio);
23
static DoubleHistogram fromString(String base64CompressedHistogramString);
24
25
// Implementation methods
26
public DoubleHistogram copy();
27
public DoubleHistogram copyCorrectedForCoordinatedOmission(double expectedInterval);
28
}
29
```
30
31
### Recording Double Values
32
33
```java { .api }
34
// Core recording methods
35
void recordValue(double value);
36
void recordValueWithCount(double value, long count);
37
void recordValueWithExpectedInterval(double value, double expectedInterval);
38
void reset();
39
```
40
41
**Usage Examples:**
42
43
```java
44
// Create double histogram with auto-resizing
45
DoubleHistogram histogram = new DoubleHistogram(3);
46
47
// Record floating-point measurements
48
histogram.recordValue(1.234); // Response time in seconds
49
histogram.recordValue(0.987); // Another measurement
50
histogram.recordValue(2.345); // Higher latency
51
52
// Record multiple occurrences
53
histogram.recordValueWithCount(1.5, 10); // Value 1.5 occurred 10 times
54
55
// Coordinated omission correction for timing measurements
56
double expectedInterval = 0.1; // Expected 100ms intervals
57
double actualMeasurement = 1.5; // But measured 1.5 seconds (pause occurred)
58
histogram.recordValueWithExpectedInterval(actualMeasurement, expectedInterval);
59
```
60
61
### Statistical Analysis
62
63
```java { .api }
64
// Count and statistics
65
long getTotalCount();
66
double getMaxValue();
67
double getMinValue();
68
double getMinNonZeroValue();
69
double getMean();
70
double getStdDeviation();
71
72
// Percentile queries
73
double getValueAtPercentile(double percentile);
74
double getPercentileAtOrBelowValue(double value);
75
76
// Value-specific queries
77
long getCountAtValue(double value);
78
long getCountBetweenValues(double lowValue, double highValue);
79
```
80
81
**Usage Examples:**
82
83
```java
84
// Analyze response time distribution
85
long totalRequests = histogram.getTotalCount();
86
double avgResponseTime = histogram.getMean();
87
double maxResponseTime = histogram.getMaxValue();
88
89
// Key percentiles for SLA monitoring
90
double p50 = histogram.getValueAtPercentile(50.0); // Median response time
91
double p95 = histogram.getValueAtPercentile(95.0); // 95th percentile
92
double p99 = histogram.getValueAtPercentile(99.0); // 99th percentile
93
double p999 = histogram.getValueAtPercentile(99.9); // 99.9th percentile
94
95
System.out.printf("Response Time Analysis:%n");
96
System.out.printf(" Requests: %d%n", totalRequests);
97
System.out.printf(" Average: %.3f sec%n", avgResponseTime);
98
System.out.printf(" P50: %.3f sec%n", p50);
99
System.out.printf(" P95: %.3f sec%n", p95);
100
System.out.printf(" P99: %.3f sec%n", p99);
101
System.out.printf(" P999: %.3f sec%n", p999);
102
103
// SLA analysis - what percentage meets 2-second target?
104
double percentileAt2Sec = histogram.getPercentileAtOrBelowValue(2.0);
105
System.out.printf("%.2f%% of requests complete within 2 seconds%n", percentileAt2Sec);
106
```
107
108
### Value Equivalence Analysis
109
110
```java { .api }
111
// Value equivalence methods
112
double sizeOfEquivalentValueRange(double value);
113
double lowestEquivalentValue(double value);
114
double highestEquivalentValue(double value);
115
double medianEquivalentValue(double value);
116
double nextNonEquivalentValue(double value);
117
boolean valuesAreEquivalent(double value1, double value2);
118
```
119
120
**Usage Examples:**
121
122
```java
123
// Understand histogram precision for double values
124
double value = 1.234;
125
double rangeSize = histogram.sizeOfEquivalentValueRange(value);
126
double lowest = histogram.lowestEquivalentValue(value);
127
double highest = histogram.highestEquivalentValue(value);
128
double median = histogram.medianEquivalentValue(value);
129
130
System.out.printf("Value %.6f represents range [%.6f, %.6f]%n", value, lowest, highest);
131
System.out.printf("Range size: %.6f%n", rangeSize);
132
System.out.printf("Median equivalent: %.6f%n", median);
133
134
// Check if two values would be considered equivalent
135
double val1 = 1.2345;
136
double val2 = 1.2346;
137
if (histogram.valuesAreEquivalent(val1, val2)) {
138
System.out.println("Values are equivalent within histogram precision");
139
}
140
141
// Iterate through distinct values efficiently
142
double currentValue = histogram.getMinNonZeroValue();
143
while (currentValue <= histogram.getMaxValue()) {
144
long count = histogram.getCountAtValue(currentValue);
145
if (count > 0) {
146
System.out.printf("Value: %.6f, Count: %d%n", currentValue, count);
147
}
148
currentValue = histogram.nextNonEquivalentValue(currentValue);
149
}
150
```
151
152
### Configuration and Auto-Resize
153
154
```java { .api }
155
// Configuration
156
void setAutoResize(boolean autoResize);
157
boolean isAutoResize();
158
long getHighestToLowestValueRatio();
159
int getNumberOfSignificantValueDigits();
160
161
// Timestamp and metadata
162
long getStartTimeStamp();
163
void setStartTimeStamp(long timestamp);
164
long getEndTimeStamp();
165
void setEndTimeStamp(long timestamp);
166
String getTag();
167
void setTag(String tag);
168
```
169
170
**Usage Examples:**
171
172
```java
173
// Create histogram with specific dynamic range
174
long dynamicRange = 1000000; // 1M:1 ratio (6 orders of magnitude)
175
DoubleHistogram histogram = new DoubleHistogram(dynamicRange, 3);
176
177
// Enable auto-resize for unknown ranges
178
histogram.setAutoResize(true);
179
180
// Tag histogram for identification
181
histogram.setTag("api-response-times");
182
histogram.setStartTimeStamp(System.currentTimeMillis());
183
184
// Record measurements
185
recordApiResponseTimes(histogram);
186
187
histogram.setEndTimeStamp(System.currentTimeMillis());
188
System.out.printf("Measurement period: %s%n", histogram.getTag());
189
```
190
191
### Constructor Variations
192
193
```java
194
// Auto-resizing histogram (recommended for most use cases)
195
DoubleHistogram autoResize = new DoubleHistogram(3);
196
197
// Fixed dynamic range (1M:1 ratio)
198
DoubleHistogram fixedRange = new DoubleHistogram(1_000_000, 3);
199
200
// Custom internal histogram implementation
201
DoubleHistogram withIntCounts = new DoubleHistogram(3, IntCountsHistogram.class);
202
DoubleHistogram withPackedStorage = new DoubleHistogram(3, PackedHistogram.class);
203
```
204
205
## ConcurrentDoubleHistogram
206
207
Thread-safe version of DoubleHistogram supporting concurrent recording operations.
208
209
```java { .api }
210
public class ConcurrentDoubleHistogram extends DoubleHistogram {
211
212
// Constructors
213
public ConcurrentDoubleHistogram(int numberOfSignificantValueDigits);
214
public ConcurrentDoubleHistogram(long highestToLowestValueRatio, int numberOfSignificantValueDigits);
215
public ConcurrentDoubleHistogram(int numberOfSignificantValueDigits,
216
Class<? extends AbstractHistogram> internalCountsHistogramClass);
217
218
// Implementation methods
219
public ConcurrentDoubleHistogram copy();
220
public ConcurrentDoubleHistogram copyCorrectedForCoordinatedOmission(double expectedInterval);
221
}
222
```
223
224
### Thread Safety Characteristics
225
226
**Wait-Free Operations:**
227
- `recordValue()` - Concurrent recording without contention
228
- `recordValueWithCount()` - Concurrent recording with counts
229
- `recordValueWithExpectedInterval()` - Concurrent coordinated omission correction
230
- Auto-resizing operations
231
232
**Requires External Synchronization:**
233
- Statistical queries and analysis
234
- Iterator operations
235
- Histogram manipulation operations
236
237
### Usage Examples
238
239
```java
240
// Create concurrent double histogram for multi-threaded recording
241
ConcurrentDoubleHistogram histogram = new ConcurrentDoubleHistogram(3);
242
243
// Multiple threads recording response times concurrently
244
ExecutorService requestProcessors = Executors.newFixedThreadPool(10);
245
246
for (int i = 0; i < 10; i++) {
247
requestProcessors.submit(() -> {
248
Random random = new Random();
249
for (int j = 0; j < 10000; j++) {
250
// Simulate processing and record response time
251
double startTime = System.nanoTime() / 1e9;
252
simulateApiCall(); // Your API processing
253
double endTime = System.nanoTime() / 1e9;
254
255
// Thread-safe recording (no synchronization needed)
256
histogram.recordValue(endTime - startTime);
257
}
258
});
259
}
260
261
// Separate monitoring thread
262
ScheduledExecutorService monitor = Executors.newSingleThreadScheduledExecutor();
263
monitor.scheduleAtFixedRate(() -> {
264
// Synchronized reading for consistent snapshot
265
synchronized (histogram) {
266
double p95 = histogram.getValueAtPercentile(95.0);
267
double p99 = histogram.getValueAtPercentile(99.0);
268
System.out.printf("Current P95: %.3fs, P99: %.3fs%n", p95, p99);
269
}
270
}, 0, 10, TimeUnit.SECONDS);
271
272
requestProcessors.shutdown();
273
monitor.shutdown();
274
```
275
276
### Writer-Reader Coordination
277
278
For more sophisticated coordination without blocking:
279
280
```java
281
ConcurrentDoubleHistogram histogram = new ConcurrentDoubleHistogram(3);
282
WriterReaderPhaser phaser = histogram.getWriterReaderPhaser();
283
284
// Recording threads (multiple)
285
Runnable recorder = () -> {
286
Random random = new Random();
287
for (int i = 0; i < 100000; i++) {
288
double measurement = random.nextGaussian() * 0.1 + 0.5; // ~500ms ± 100ms
289
histogram.recordValue(measurement);
290
}
291
};
292
293
// Analysis thread (single)
294
Runnable analyzer = () -> {
295
phaser.readerLock();
296
try {
297
// Safe to iterate and analyze
298
System.out.printf("Total count: %d%n", histogram.getTotalCount());
299
histogram.outputPercentileDistribution(System.out, 1.0);
300
} finally {
301
phaser.readerUnlock();
302
}
303
};
304
305
// Start concurrent operations
306
new Thread(recorder).start();
307
new Thread(recorder).start();
308
new Thread(analyzer).start();
309
```
310
311
## SynchronizedDoubleHistogram
312
313
Fully thread-safe double histogram with synchronized access for all operations.
314
315
```java { .api }
316
public class SynchronizedDoubleHistogram extends DoubleHistogram {
317
318
// Constructors
319
public SynchronizedDoubleHistogram(int numberOfSignificantValueDigits);
320
public SynchronizedDoubleHistogram(long highestToLowestValueRatio, int numberOfSignificantValueDigits);
321
public SynchronizedDoubleHistogram(int numberOfSignificantValueDigits,
322
Class<? extends AbstractHistogram> internalCountsHistogramClass);
323
324
// Implementation methods
325
public SynchronizedDoubleHistogram copy();
326
public SynchronizedDoubleHistogram copyCorrectedForCoordinatedOmission(double expectedInterval);
327
}
328
```
329
330
### Usage Examples
331
332
```java
333
// Fully synchronized double histogram
334
SynchronizedDoubleHistogram histogram = new SynchronizedDoubleHistogram(3);
335
336
// All operations are thread-safe but may block
337
Runnable measurements = () -> {
338
Random random = new Random();
339
for (int i = 0; i < 50000; i++) {
340
double value = Math.abs(random.nextGaussian()) * 2.0; // Positive values 0-6 range
341
histogram.recordValue(value); // Thread-safe, may block
342
}
343
};
344
345
Runnable monitoring = () -> {
346
while (true) {
347
// No synchronization needed - all operations are atomic
348
long count = histogram.getTotalCount();
349
double mean = histogram.getMean();
350
double p95 = histogram.getValueAtPercentile(95.0);
351
352
System.out.printf("Count: %d, Mean: %.3f, P95: %.3f%n", count, mean, p95);
353
354
try { Thread.sleep(2000); } catch (InterruptedException e) { break; }
355
}
356
};
357
358
// Start concurrent operations
359
new Thread(measurements).start();
360
new Thread(measurements).start();
361
new Thread(monitoring).start();
362
```
363
364
## DoubleValueRecorder Interface
365
366
Core interface for double value recording functionality.
367
368
```java { .api }
369
public interface DoubleValueRecorder {
370
void recordValue(double value);
371
void recordValueWithCount(double value, long count);
372
void recordValueWithExpectedInterval(double value, double expectedInterval);
373
void reset();
374
}
375
```
376
377
## Double Histogram Iteration
378
379
DoubleHistogram provides specialized iterators for double values:
380
381
```java { .api }
382
// Double-specific iterators
383
DoublePercentileIterator percentileIterator(int percentileTicksPerHalfDistance);
384
DoubleLinearIterator linearIterator(double valueUnitsPerBucket);
385
DoubleLogarithmicIterator logarithmicIterator(double valueUnitsInFirstBucket, double logBase);
386
DoubleRecordedValuesIterator recordedValuesIterator();
387
DoubleAllValuesIterator allValuesIterator();
388
```
389
390
**Usage Examples:**
391
392
```java
393
// Iterate through percentiles
394
DoublePercentileIterator percentileIter = histogram.percentileIterator(5);
395
while (percentileIter.hasNext()) {
396
DoubleHistogramIterationValue value = percentileIter.next();
397
System.out.printf("Percentile %.2f: %.6f (count: %d)%n",
398
value.getPercentileLevelIteratedTo(),
399
value.getValueIteratedTo(),
400
value.getCountAtValueIteratedTo());
401
}
402
403
// Linear iteration through value ranges
404
DoubleLinearIterator linearIter = histogram.linearIterator(0.1); // 100ms buckets
405
while (linearIter.hasNext()) {
406
DoubleHistogramIterationValue value = linearIter.next();
407
if (value.getCountAtValueIteratedTo() > 0) {
408
System.out.printf("Range %.3f-%.3f: %d samples%n",
409
value.getValueIteratedFrom(),
410
value.getValueIteratedTo(),
411
value.getTotalCountToThisValue());
412
}
413
}
414
```
415
416
## Practical Use Cases
417
418
### Web Application Response Times
419
420
```java
421
// Track API endpoint response times
422
ConcurrentDoubleHistogram apiResponseTimes = new ConcurrentDoubleHistogram(3);
423
apiResponseTimes.setTag("api-latency");
424
425
// In your request handler
426
@GetMapping("/api/data")
427
public ResponseEntity<Data> getData() {
428
double startTime = System.nanoTime() / 1e9;
429
430
try {
431
Data result = dataService.getData();
432
return ResponseEntity.ok(result);
433
} finally {
434
double endTime = System.nanoTime() / 1e9;
435
apiResponseTimes.recordValue(endTime - startTime);
436
}
437
}
438
439
// Periodic reporting
440
@Scheduled(fixedRate = 60000) // Every minute
441
public void reportMetrics() {
442
synchronized (apiResponseTimes) {
443
double p95 = apiResponseTimes.getValueAtPercentile(95.0);
444
double p99 = apiResponseTimes.getValueAtPercentile(99.0);
445
446
if (p95 > 2.0) { // Alert if P95 > 2 seconds
447
alertingService.alert("API P95 latency high: " + p95 + "s");
448
}
449
450
metricsService.record("api.p95", p95);
451
metricsService.record("api.p99", p99);
452
}
453
}
454
```
455
456
### Financial Transaction Processing
457
458
```java
459
// Track transaction processing times with high precision
460
DoubleHistogram transactionTimes = new DoubleHistogram(4); // High precision
461
transactionTimes.setTag("transaction-processing");
462
463
// Record transaction with coordinated omission correction
464
public void processTransaction(Transaction tx) {
465
double expectedProcessingTime = 0.050; // Expected 50ms processing
466
double startTime = System.nanoTime() / 1e9;
467
468
try {
469
// Process transaction
470
processTransactionInternal(tx);
471
} finally {
472
double endTime = System.nanoTime() / 1e9;
473
double actualTime = endTime - startTime;
474
475
// Account for coordinated omission (system pauses, GC, etc.)
476
transactionTimes.recordValueWithExpectedInterval(actualTime, expectedProcessingTime);
477
}
478
}
479
480
// Compliance reporting
481
public ComplianceReport generateComplianceReport() {
482
return ComplianceReport.builder()
483
.totalTransactions(transactionTimes.getTotalCount())
484
.averageProcessingTime(transactionTimes.getMean())
485
.p95ProcessingTime(transactionTimes.getValueAtPercentile(95.0))
486
.p99ProcessingTime(transactionTimes.getValueAtPercentile(99.0))
487
.maxProcessingTime(transactionTimes.getMaxValue())
488
.complianceThresholdMet(transactionTimes.getValueAtPercentile(99.0) < 0.100) // < 100ms P99
489
.build();
490
}
491
```
492
493
## Performance Considerations
494
495
### Memory Usage
496
DoubleHistogram internally uses an integer histogram scaled to the appropriate range:
497
- Memory usage similar to equivalent integer histogram
498
- Dynamic range affects memory requirements
499
- Consider using packed variants for sparse double distributions
500
501
### Precision and Range
502
The `numberOfSignificantValueDigits` parameter affects both precision and memory:
503
- **2 digits**: ~10% precision, low memory
504
- **3 digits**: ~1% precision, moderate memory (recommended)
505
- **4 digits**: ~0.1% precision, higher memory
506
- **5 digits**: ~0.01% precision, high memory
507
508
### Double Value Scaling
509
Double values are internally scaled to integers:
510
- Very small values (< 1e-12) may lose precision
511
- Very large values (> 1e12) may lose precision
512
- Choose appropriate dynamic range for your data
513
514
## PackedDoubleHistogram
515
516
Memory-optimized version of DoubleHistogram using packed array representation for sparse double value distributions.
517
518
```java { .api }
519
public class PackedDoubleHistogram extends DoubleHistogram {
520
521
// Constructors
522
public PackedDoubleHistogram(int numberOfSignificantValueDigits);
523
public PackedDoubleHistogram(long highestToLowestValueRatio, int numberOfSignificantValueDigits);
524
public PackedDoubleHistogram(DoubleHistogram source);
525
526
// Factory methods
527
static PackedDoubleHistogram decodeFromByteBuffer(ByteBuffer buffer,
528
long minBarForHighestToLowestValueRatio);
529
static PackedDoubleHistogram decodeFromCompressedByteBuffer(ByteBuffer buffer,
530
long minBarForHighestToLowestValueRatio);
531
}
532
```
533
534
### Usage Examples
535
536
```java
537
// Create packed double histogram for sparse distributions
538
PackedDoubleHistogram histogram = new PackedDoubleHistogram(3);
539
540
// Record sparse response time data (most values 0.1-1.0s, few outliers)
541
Random random = new Random();
542
for (int i = 0; i < 100000; i++) {
543
if (random.nextDouble() < 0.9) {
544
// 90% of values in normal range
545
histogram.recordValue(0.1 + random.nextDouble() * 0.9);
546
} else {
547
// 10% outliers
548
histogram.recordValue(5.0 + random.nextDouble() * 95.0);
549
}
550
}
551
552
// Packed storage provides memory efficiency for sparse distributions
553
System.out.printf("Memory usage: %d bytes%n", histogram.getEstimatedFootprintInBytes());
554
```
555
556
## PackedConcurrentDoubleHistogram
557
558
Thread-safe packed double histogram combining memory efficiency with concurrent recording support.
559
560
```java { .api }
561
public class PackedConcurrentDoubleHistogram extends ConcurrentDoubleHistogram {
562
563
// Constructors
564
public PackedConcurrentDoubleHistogram(int numberOfSignificantValueDigits);
565
public PackedConcurrentDoubleHistogram(long highestToLowestValueRatio, int numberOfSignificantValueDigits);
566
public PackedConcurrentDoubleHistogram(DoubleHistogram source);
567
568
// Factory methods
569
static PackedConcurrentDoubleHistogram decodeFromByteBuffer(ByteBuffer buffer,
570
long minBarForHighestToLowestValueRatio);
571
static PackedConcurrentDoubleHistogram decodeFromCompressedByteBuffer(ByteBuffer buffer,
572
long minBarForHighestToLowestValueRatio);
573
574
// Implementation methods
575
public PackedConcurrentDoubleHistogram copy();
576
public PackedConcurrentDoubleHistogram copyCorrectedForCoordinatedOmission(double expectedInterval);
577
}
578
```
579
580
### Usage Examples
581
582
```java
583
// Thread-safe packed double histogram for concurrent sparse recording
584
PackedConcurrentDoubleHistogram histogram = new PackedConcurrentDoubleHistogram(3);
585
586
// Multiple threads recording sparse latency measurements
587
ExecutorService executor = Executors.newFixedThreadPool(8);
588
589
for (int t = 0; t < 8; t++) {
590
executor.submit(() -> {
591
Random random = new Random();
592
for (int i = 0; i < 50000; i++) {
593
// Simulate API latency: mostly fast, occasional slow responses
594
double latency = random.nextDouble() < 0.95
595
? 0.001 + random.nextDouble() * 0.099 // 95% fast: 1-100ms
596
: 1.0 + random.nextDouble() * 9.0; // 5% slow: 1-10s
597
598
histogram.recordValue(latency); // Thread-safe recording
599
}
600
});
601
}
602
603
// Coordinated analysis
604
WriterReaderPhaser phaser = histogram.getWriterReaderPhaser();
605
phaser.readerLock();
606
try {
607
System.out.printf("Packed memory usage: %d bytes%n",
608
histogram.getEstimatedFootprintInBytes());
609
610
double p95 = histogram.getValueAtPercentile(95.0);
611
double p99 = histogram.getValueAtPercentile(99.0);
612
613
System.out.printf("P95: %.3fs, P99: %.3fs%n", p95, p99);
614
} finally {
615
phaser.readerUnlock();
616
}
617
618
executor.shutdown();
619
```
620
621
## Thread Safety Summary
622
623
| Histogram Type | Recording Thread Safety | Query Thread Safety | Auto-Resize Support |
624
|---------------|------------------------|-------------------|-------------------|
625
| DoubleHistogram | No | No | Yes |
626
| ConcurrentDoubleHistogram | Yes (wait-free) | External sync needed | Yes |
627
| SynchronizedDoubleHistogram | Yes (synchronized) | Yes (synchronized) | Yes |
628
| PackedDoubleHistogram | No | No | Yes |
629
| PackedConcurrentDoubleHistogram | Yes (wait-free) | External sync needed | Yes |