0
# Bloom Filters
1
2
Probabilistic data structures including Bloom filters, Count-Min Sketch, Cuckoo filters, T-Digest, and Top-K using RedisBloom. These structures provide memory-efficient approximations for set membership, counting, ranking, and statistical operations.
3
4
## Capabilities
5
6
### Bloom Filters
7
8
Probabilistic set membership testing with configurable false positive rates.
9
10
```typescript { .api }
11
/**
12
* Add item to Bloom filter
13
* @param key - Bloom filter key
14
* @param item - Item to add
15
* @returns 1 if item was added, 0 if probably already exists
16
*/
17
function add(key: RedisArgument, item: RedisArgument): Promise<BooleanReply>;
18
19
/**
20
* Add multiple items to Bloom filter
21
* @param key - Bloom filter key
22
* @param items - Items to add
23
* @returns Array of results for each item
24
*/
25
function mAdd(key: RedisArgument, ...items: RedisArgument[]): Promise<ArrayReply<BooleanReply>>;
26
27
/**
28
* Check if item exists in Bloom filter
29
* @param key - Bloom filter key
30
* @param item - Item to check
31
* @returns 1 if item probably exists, 0 if definitely does not exist
32
*/
33
function exists(key: RedisArgument, item: RedisArgument): Promise<BooleanReply>;
34
35
/**
36
* Check multiple items in Bloom filter
37
* @param key - Bloom filter key
38
* @param items - Items to check
39
* @returns Array of existence results for each item
40
*/
41
function mExists(key: RedisArgument, ...items: RedisArgument[]): Promise<ArrayReply<BooleanReply>>;
42
43
/**
44
* Reserve Bloom filter with specific parameters
45
* @param key - Bloom filter key
46
* @param errorRate - Desired false positive rate (0 < errorRate < 1)
47
* @param capacity - Expected number of items
48
* @param options - Optional configuration
49
* @returns 'OK'
50
*/
51
function reserve(
52
key: RedisArgument,
53
errorRate: number,
54
capacity: number,
55
options?: BfReserveOptions
56
): Promise<SimpleStringReply<'OK'>>;
57
58
/**
59
* Get Bloom filter information and statistics
60
* @param key - Bloom filter key
61
* @returns Array containing filter info
62
*/
63
function info(key: RedisArgument): Promise<ArrayReply>;
64
65
/**
66
* Get cardinality estimation of Bloom filter
67
* @param key - Bloom filter key
68
* @returns Estimated number of unique items added
69
*/
70
function card(key: RedisArgument): Promise<NumberReply>;
71
72
/**
73
* Insert items with options
74
* @param key - Bloom filter key
75
* @param options - Insert configuration
76
* @param items - Items to insert
77
* @returns Array of results for each item
78
*/
79
function insert(
80
key: RedisArgument,
81
options: BfInsertOptions,
82
...items: RedisArgument[]
83
): Promise<ArrayReply<BooleanReply>>;
84
85
/**
86
* Begin incremental save of Bloom filter
87
* @param key - Bloom filter key
88
* @param iterator - Iterator value (start at 0)
89
* @returns Object with iterator and chunk data
90
*/
91
function scanDump(key: RedisArgument, iterator: number): Promise<{ iterator: NumberReply; chunk: BlobStringReply }>;
92
93
/**
94
* Restore Bloom filter chunk
95
* @param key - Bloom filter key
96
* @param iterator - Iterator value from scanDump
97
* @param chunk - Data chunk from scanDump
98
* @returns 'OK'
99
*/
100
function loadChunk(key: RedisArgument, iterator: number, chunk: RedisArgument): Promise<SimpleStringReply<'OK'>>;
101
102
interface BfReserveOptions {
103
/** Number of sub-filters for expansion */
104
EXPANSION?: number;
105
/** Non-scaling filter (no expansion) */
106
NONSCALING?: boolean;
107
}
108
109
interface BfInsertOptions {
110
/** Capacity for auto-created filter */
111
CAPACITY?: number;
112
/** Error rate for auto-created filter */
113
ERROR?: number;
114
/** Expansion rate for auto-created filter */
115
EXPANSION?: number;
116
/** Create non-scaling filter */
117
NONSCALING?: boolean;
118
/** Only insert if key doesn't exist */
119
NOCREATE?: boolean;
120
}
121
```
122
123
**Usage Examples:**
124
125
```typescript
126
import { createClient } from "redis";
127
128
const client = createClient();
129
await client.connect();
130
131
// Reserve Bloom filter with specific parameters
132
await client.bf.reserve("user_emails", 0.01, 10000); // 1% false positive rate, 10k capacity
133
134
// Add items to filter
135
await client.bf.add("user_emails", "alice@example.com");
136
await client.bf.add("user_emails", "bob@example.com");
137
138
// Add multiple items at once
139
const addResults = await client.bf.mAdd("user_emails",
140
"charlie@example.com",
141
"diana@example.com",
142
"alice@example.com" // Already exists
143
);
144
// Results: [1, 1, 0] (1=added, 0=probably already exists)
145
146
// Check membership
147
const exists1 = await client.bf.exists("user_emails", "alice@example.com"); // 1 (probably exists)
148
const exists2 = await client.bf.exists("user_emails", "unknown@example.com"); // 0 (definitely not)
149
150
// Check multiple items
151
const existsResults = await client.bf.mExists("user_emails",
152
"alice@example.com", // Exists
153
"bob@example.com", // Exists
154
"unknown@example.com" // Doesn't exist
155
);
156
// Results: [1, 1, 0]
157
158
// Get filter statistics
159
const info = await client.bf.info("user_emails");
160
console.log("Filter info:", info);
161
162
// Get cardinality estimate
163
const cardinality = await client.bf.card("user_emails");
164
console.log("Estimated items:", cardinality);
165
166
// Insert with auto-creation
167
await client.bf.insert("auto_filter", {
168
CAPACITY: 5000,
169
ERROR: 0.001 // 0.1% false positive rate
170
}, "item1", "item2", "item3");
171
```
172
173
### Count-Min Sketch
174
175
Frequency estimation for streaming data with bounded error guarantees.
176
177
```typescript { .api }
178
/**
179
* Initialize Count-Min Sketch by dimensions
180
* @param key - Sketch key
181
* @param width - Width parameter (affects accuracy)
182
* @param depth - Depth parameter (affects accuracy)
183
* @returns 'OK'
184
*/
185
function initByDim(key: RedisArgument, width: number, depth: number): Promise<SimpleStringReply<'OK'>>;
186
187
/**
188
* Initialize Count-Min Sketch by error and probability parameters
189
* @param key - Sketch key
190
* @param error - Relative error (0 < error < 1)
191
* @param probability - Probability of error (0 < probability < 1)
192
* @returns 'OK'
193
*/
194
function initByProb(key: RedisArgument, error: number, probability: number): Promise<SimpleStringReply<'OK'>>;
195
196
/**
197
* Increment items in Count-Min Sketch
198
* @param key - Sketch key
199
* @param items - Array of item-increment pairs
200
* @returns Array of estimated frequencies after increment
201
*/
202
function incrBy(key: RedisArgument, ...items: Array<{ item: RedisArgument; increment: number }>): Promise<ArrayReply<NumberReply>>;
203
204
/**
205
* Query estimated frequencies of items
206
* @param key - Sketch key
207
* @param items - Items to query
208
* @returns Array of estimated frequencies
209
*/
210
function query(key: RedisArgument, ...items: RedisArgument[]): Promise<ArrayReply<NumberReply>>;
211
212
/**
213
* Merge multiple sketches into destination
214
* @param destKey - Destination sketch key
215
* @param srcKeys - Source sketch keys to merge
216
* @param weights - Optional weights for each source sketch
217
* @returns 'OK'
218
*/
219
function merge(destKey: RedisArgument, srcKeys: RedisArgument[], weights?: number[]): Promise<SimpleStringReply<'OK'>>;
220
221
/**
222
* Get Count-Min Sketch information
223
* @param key - Sketch key
224
* @returns Array containing sketch info
225
*/
226
function info(key: RedisArgument): Promise<ArrayReply>;
227
```
228
229
**Usage Examples:**
230
231
```typescript
232
// Initialize sketch with specific dimensions
233
await client.cms.initByDim("page_views", 2000, 5); // 2000 width, 5 depth
234
235
// Initialize sketch with error/probability bounds
236
await client.cms.initByProb("word_count", 0.001, 0.01); // 0.1% error, 1% probability
237
238
// Increment item frequencies
239
const frequencies = await client.cms.incrBy("page_views",
240
{ item: "/home", increment: 10 },
241
{ item: "/about", increment: 5 },
242
{ item: "/contact", increment: 2 }
243
);
244
console.log("New frequencies:", frequencies); // [10, 5, 2]
245
246
// Add more visits
247
await client.cms.incrBy("page_views",
248
{ item: "/home", increment: 15 },
249
{ item: "/products", increment: 8 }
250
);
251
252
// Query frequencies
253
const counts = await client.cms.query("page_views", "/home", "/about", "/products", "/unknown");
254
console.log("Estimated counts:", counts); // [25, 5, 8, 0] (approximately)
255
256
// Create another sketch for merging
257
await client.cms.initByDim("page_views_backup", 2000, 5);
258
await client.cms.incrBy("page_views_backup",
259
{ item: "/home", increment: 5 },
260
{ item: "/blog", increment: 12 }
261
);
262
263
// Merge sketches
264
await client.cms.merge("page_views_combined", ["page_views", "page_views_backup"]);
265
266
// Query merged results
267
const mergedCounts = await client.cms.query("page_views_combined", "/home", "/blog");
268
console.log("Merged counts:", mergedCounts); // [30, 12] (approximately)
269
270
// Get sketch info
271
const info = await client.cms.info("page_views");
272
console.log("Sketch info:", info);
273
```
274
275
### Cuckoo Filters
276
277
Alternative to Bloom filters with deletion support and generally better performance.
278
279
```typescript { .api }
280
/**
281
* Reserve Cuckoo filter with specific capacity
282
* @param key - Filter key
283
* @param capacity - Maximum number of items
284
* @param options - Optional configuration
285
* @returns 'OK'
286
*/
287
function reserve(key: RedisArgument, capacity: number, options?: CfReserveOptions): Promise<SimpleStringReply<'OK'>>;
288
289
/**
290
* Add item to Cuckoo filter
291
* @param key - Filter key
292
* @param item - Item to add
293
* @returns 1 if added successfully
294
*/
295
function add(key: RedisArgument, item: RedisArgument): Promise<BooleanReply>;
296
297
/**
298
* Add item only if it doesn't exist
299
* @param key - Filter key
300
* @param item - Item to add
301
* @returns 1 if added, 0 if already exists
302
*/
303
function addNx(key: RedisArgument, item: RedisArgument): Promise<BooleanReply>;
304
305
/**
306
* Insert items with auto-creation
307
* @param key - Filter key
308
* @param options - Insert configuration
309
* @param items - Items to insert
310
* @returns Array of results for each item
311
*/
312
function insert(key: RedisArgument, options: CfInsertOptions, ...items: RedisArgument[]): Promise<ArrayReply<BooleanReply>>;
313
314
/**
315
* Check if item exists in Cuckoo filter
316
* @param key - Filter key
317
* @param item - Item to check
318
* @returns 1 if exists, 0 if not
319
*/
320
function exists(key: RedisArgument, item: RedisArgument): Promise<BooleanReply>;
321
322
/**
323
* Delete item from Cuckoo filter
324
* @param key - Filter key
325
* @param item - Item to delete
326
* @returns 1 if deleted, 0 if not found
327
*/
328
function del(key: RedisArgument, item: RedisArgument): Promise<BooleanReply>;
329
330
/**
331
* Count occurrences of item in filter
332
* @param key - Filter key
333
* @param item - Item to count
334
* @returns Number of occurrences (0-2 typically)
335
*/
336
function count(key: RedisArgument, item: RedisArgument): Promise<NumberReply>;
337
338
/**
339
* Get Cuckoo filter information
340
* @param key - Filter key
341
* @returns Array containing filter info
342
*/
343
function info(key: RedisArgument): Promise<ArrayReply>;
344
345
interface CfReserveOptions {
346
/** Bucket size (default: 2) */
347
BUCKETSIZE?: number;
348
/** Maximum number of iterations for insertion */
349
MAXITERATIONS?: number;
350
/** Expansion rate when filter is full */
351
EXPANSION?: number;
352
}
353
354
interface CfInsertOptions {
355
/** Capacity for auto-created filter */
356
CAPACITY?: number;
357
/** Only insert if key doesn't exist */
358
NOCREATE?: boolean;
359
}
360
```
361
362
**Usage Examples:**
363
364
```typescript
365
// Reserve Cuckoo filter
366
await client.cf.reserve("user_sessions", 10000, {
367
BUCKETSIZE: 4,
368
MAXITERATIONS: 20,
369
EXPANSION: 1
370
});
371
372
// Add items
373
await client.cf.add("user_sessions", "session_123");
374
await client.cf.add("user_sessions", "session_456");
375
376
// Add only if not exists
377
const added = await client.cf.addNx("user_sessions", "session_123"); // 0 (already exists)
378
const newAdded = await client.cf.addNx("user_sessions", "session_789"); // 1 (new)
379
380
// Check existence
381
const exists = await client.cf.exists("user_sessions", "session_123"); // 1
382
const notExists = await client.cf.exists("user_sessions", "session_999"); // 0
383
384
// Count occurrences
385
const count = await client.cf.count("user_sessions", "session_123"); // 1
386
387
// Delete item (unlike Bloom filters!)
388
const deleted = await client.cf.del("user_sessions", "session_123"); // 1
389
const checkDeleted = await client.cf.exists("user_sessions", "session_123"); // 0
390
391
// Insert with auto-creation
392
await client.cf.insert("temp_filter", {
393
CAPACITY: 1000
394
}, "item1", "item2", "item3");
395
396
// Get filter info
397
const info = await client.cf.info("user_sessions");
398
console.log("Filter info:", info);
399
```
400
401
### T-Digest
402
403
Probabilistic data structure for estimating quantiles and percentiles of streaming data.
404
405
```typescript { .api }
406
/**
407
* Create T-Digest with optional compression parameter
408
* @param key - T-Digest key
409
* @param compression - Compression factor (default: 100, higher = more accurate)
410
* @returns 'OK'
411
*/
412
function create(key: RedisArgument, compression?: number): Promise<SimpleStringReply<'OK'>>;
413
414
/**
415
* Reset T-Digest to empty state
416
* @param key - T-Digest key
417
* @returns 'OK'
418
*/
419
function reset(key: RedisArgument): Promise<SimpleStringReply<'OK'>>;
420
421
/**
422
* Add values to T-Digest
423
* @param key - T-Digest key
424
* @param values - Numeric values to add
425
* @returns 'OK'
426
*/
427
function add(key: RedisArgument, ...values: number[]): Promise<SimpleStringReply<'OK'>>;
428
429
/**
430
* Merge multiple T-Digests
431
* @param destKey - Destination key
432
* @param srcKeys - Source T-Digest keys
433
* @param options - Optional compression and weights
434
* @returns 'OK'
435
*/
436
function merge(destKey: RedisArgument, srcKeys: RedisArgument[], options?: TDigestMergeOptions): Promise<SimpleStringReply<'OK'>>;
437
438
/**
439
* Get minimum value
440
* @param key - T-Digest key
441
* @returns Minimum value
442
*/
443
function min(key: RedisArgument): Promise<DoubleReply>;
444
445
/**
446
* Get maximum value
447
* @param key - T-Digest key
448
* @returns Maximum value
449
*/
450
function max(key: RedisArgument): Promise<DoubleReply>;
451
452
/**
453
* Get quantile values
454
* @param key - T-Digest key
455
* @param quantiles - Quantile values (0.0 to 1.0)
456
* @returns Array of quantile values
457
*/
458
function quantile(key: RedisArgument, ...quantiles: number[]): Promise<ArrayReply<DoubleReply>>;
459
460
/**
461
* Get cumulative distribution function values
462
* @param key - T-Digest key
463
* @param values - Values to get CDF for
464
* @returns Array of CDF values (0.0 to 1.0)
465
*/
466
function cdf(key: RedisArgument, ...values: number[]): Promise<ArrayReply<DoubleReply>>;
467
468
/**
469
* Get T-Digest information
470
* @param key - T-Digest key
471
* @returns Array containing digest info
472
*/
473
function info(key: RedisArgument): Promise<ArrayReply>;
474
475
interface TDigestMergeOptions {
476
/** Compression factor for merged digest */
477
COMPRESSION?: number;
478
/** Override destination key */
479
OVERRIDE?: boolean;
480
}
481
```
482
483
**Usage Examples:**
484
485
```typescript
486
// Create T-Digest for response time analysis
487
await client.tDigest.create("response_times", 200); // Higher compression for accuracy
488
489
// Add response time measurements (in milliseconds)
490
await client.tDigest.add("response_times",
491
45, 52, 38, 67, 91, 23, 156, 78, 44, 89,
492
234, 67, 45, 123, 78, 56, 234, 67, 89, 145
493
);
494
495
// Add more measurements over time
496
await client.tDigest.add("response_times", 67, 89, 234, 45, 78, 123, 56);
497
498
// Get basic statistics
499
const minTime = await client.tDigest.min("response_times");
500
const maxTime = await client.tDigest.max("response_times");
501
console.log(`Response time range: ${minTime}ms - ${maxTime}ms`);
502
503
// Get percentiles (common SLA metrics)
504
const percentiles = await client.tDigest.quantile("response_times",
505
0.5, // 50th percentile (median)
506
0.9, // 90th percentile
507
0.95, // 95th percentile
508
0.99 // 99th percentile
509
);
510
console.log("Percentiles (50%, 90%, 95%, 99%):", percentiles);
511
512
// Get probability of response time being under certain thresholds
513
const cdfValues = await client.tDigest.cdf("response_times", 100, 200, 500);
514
console.log("Probability under 100ms, 200ms, 500ms:", cdfValues);
515
516
// Create another digest for different service
517
await client.tDigest.create("service_b_times", 100);
518
await client.tDigest.add("service_b_times", 123, 145, 167, 189, 234, 67, 89);
519
520
// Merge digests for overall analysis
521
await client.tDigest.merge("combined_times", ["response_times", "service_b_times"], {
522
COMPRESSION: 200
523
});
524
525
// Analyze combined data
526
const combinedP95 = await client.tDigest.quantile("combined_times", 0.95);
527
console.log("Combined 95th percentile:", combinedP95);
528
529
// Get digest info
530
const info = await client.tDigest.info("response_times");
531
console.log("T-Digest info:", info);
532
```
533
534
### Top-K
535
536
Track top-K most frequent items in a stream with bounded memory usage.
537
538
```typescript { .api }
539
/**
540
* Reserve Top-K filter with capacity and optional parameters
541
* @param key - Top-K key
542
* @param k - Number of top items to track
543
* @param width - Width parameter (affects accuracy)
544
* @param depth - Depth parameter (affects accuracy)
545
* @param decay - Decay factor for aging (0.0 to 1.0)
546
* @returns 'OK'
547
*/
548
function reserve(key: RedisArgument, k: number, width?: number, depth?: number, decay?: number): Promise<SimpleStringReply<'OK'>>;
549
550
/**
551
* Add items to Top-K
552
* @param key - Top-K key
553
* @param items - Items to add
554
* @returns Array of items that were expelled from top-K (null if no expulsion)
555
*/
556
function add(key: RedisArgument, ...items: RedisArgument[]): Promise<ArrayReply<BlobStringReply | null>>;
557
558
/**
559
* Increment items by specific amounts
560
* @param key - Top-K key
561
* @param itemIncrements - Array of item-increment pairs
562
* @returns Array of items that were expelled from top-K
563
*/
564
function incrBy(key: RedisArgument, ...itemIncrements: Array<{ item: RedisArgument; increment: number }>): Promise<ArrayReply<BlobStringReply | null>>;
565
566
/**
567
* Query if items are in current top-K
568
* @param key - Top-K key
569
* @param items - Items to query
570
* @returns Array of boolean results for each item
571
*/
572
function query(key: RedisArgument, ...items: RedisArgument[]): Promise<ArrayReply<BooleanReply>>;
573
574
/**
575
* Get current top-K list
576
* @param key - Top-K key
577
* @returns Array of current top-K items
578
*/
579
function list(key: RedisArgument): Promise<ArrayReply<BlobStringReply>>;
580
581
/**
582
* Get current top-K list with counts
583
* @param key - Top-K key
584
* @returns Array alternating between items and their counts
585
*/
586
function listWithCount(key: RedisArgument): Promise<ArrayReply<BlobStringReply | NumberReply>>;
587
588
/**
589
* Get Top-K information
590
* @param key - Top-K key
591
* @returns Array containing filter info
592
*/
593
function info(key: RedisArgument): Promise<ArrayReply>;
594
```
595
596
**Usage Examples:**
597
598
```typescript
599
// Reserve Top-K for tracking most popular products
600
await client.topK.reserve("popular_products", 10, 50, 7, 0.9); // Track top 10 items
601
602
// Add product views
603
const expelled1 = await client.topK.add("popular_products",
604
"laptop_001", "phone_002", "tablet_003", "laptop_001", "headphones_004"
605
);
606
console.log("Expelled items:", expelled1); // null values initially
607
608
// Add more views with bulk operations
609
await client.topK.add("popular_products",
610
"laptop_001", "laptop_001", "phone_002", "watch_005", "camera_006",
611
"laptop_007", "phone_008", "tablet_009", "speaker_010", "mouse_011"
612
);
613
614
// Increment specific items
615
const expelled2 = await client.topK.incrBy("popular_products",
616
{ item: "laptop_001", increment: 5 },
617
{ item: "premium_headphones", increment: 3 },
618
{ item: "gaming_mouse", increment: 2 }
619
);
620
621
// Get current top-K list
622
const topProducts = await client.topK.list("popular_products");
623
console.log("Current top products:", topProducts);
624
625
// Get top-K with counts
626
const topWithCounts = await client.topK.listWithCount("popular_products");
627
console.log("Top products with counts:", topWithCounts);
628
// Format: [item1, count1, item2, count2, ...]
629
630
// Query specific items
631
const inTopK = await client.topK.query("popular_products",
632
"laptop_001", // Probably in top-K
633
"rare_item", // Probably not in top-K
634
"phone_002" // Check if still in top-K
635
);
636
console.log("In top-K:", inTopK); // [true, false, true] (example)
637
638
// Track page visits
639
await client.topK.reserve("popular_pages", 5); // Simple top-5 pages
640
641
// Simulate page visits
642
const pages = ["/home", "/products", "/about", "/contact", "/blog", "/pricing", "/features"];
643
for (let i = 0; i < 100; i++) {
644
const randomPage = pages[Math.floor(Math.random() * pages.length)];
645
await client.topK.add("popular_pages", randomPage);
646
}
647
648
// Get most visited pages
649
const topPages = await client.topK.listWithCount("popular_pages");
650
console.log("Most popular pages:", topPages);
651
652
// Get Top-K info
653
const info = await client.topK.info("popular_products");
654
console.log("Top-K info:", info);
655
```
656
657
## Use Cases and Best Practices
658
659
### Bloom Filters
660
- **Use for**: Cache filtering, duplicate detection, database query optimization
661
- **Best practices**: Reserve with appropriate capacity and error rate, consider Cuckoo filters if deletion is needed
662
- **Memory**: Very memory efficient, but no deletion support
663
664
### Count-Min Sketch
665
- **Use for**: Frequency estimation, heavy hitters detection, rate limiting
666
- **Best practices**: Choose width/depth based on error requirements, merge sketches for distributed counting
667
- **Memory**: Fixed memory usage regardless of item count
668
669
### Cuckoo Filters
670
- **Use for**: Set membership with deletion support, better than Bloom for small sets
671
- **Best practices**: Good alternative to Bloom filters, supports deletion, generally faster lookups
672
- **Memory**: Slightly more memory than Bloom filters but supports deletion
673
674
### T-Digest
675
- **Use for**: Percentile monitoring, SLA tracking, statistical analysis of streaming data
676
- **Best practices**: Higher compression for more accuracy, merge digests for distributed percentiles
677
- **Memory**: Bounded memory with configurable accuracy/memory trade-off
678
679
### Top-K
680
- **Use for**: Popular item tracking, trending analysis, heavy hitters identification
681
- **Best practices**: Set appropriate decay for temporal relevance, monitor expelled items
682
- **Memory**: Fixed memory for tracking exactly K items with frequency estimates
683
684
## Error Handling
685
686
All probabilistic data structures can throw errors for invalid parameters:
687
688
```typescript
689
try {
690
// Invalid error rate (must be 0 < rate < 1)
691
await client.bf.reserve("filter", 1.5, 1000);
692
} catch (error) {
693
console.error("Invalid error rate:", error.message);
694
}
695
696
try {
697
// Invalid quantile (must be 0.0 to 1.0)
698
await client.tDigest.quantile("digest", 1.5);
699
} catch (error) {
700
console.error("Invalid quantile:", error.message);
701
}
702
```