| Crates.io | buff-rs |
| lib.rs | buff-rs |
| version | 0.2.0 |
| created_at | 2026-01-25 20:12:58.610361+00 |
| updated_at | 2026-01-26 00:21:28.825399+00 |
| description | BUFF: Decomposed bounded floats for fast compression and queries |
| homepage | https://github.com/paradedb/buff-rs |
| repository | https://github.com/paradedb/buff-rs |
| max_upload_size | |
| id | 2069435 |
| size | 205,419 |
A Rust implementation of BUFF: Decomposed Bounded Floats for Fast Compression and Queries.
Based on the VLDB 2021 paper: BUFF: Accelerating Queries in Memory through Decomposed Bounded Floats.
BUFF provides efficient compression and query execution for bounded floating-point data. Unlike general-purpose compression, BUFF is designed specifically for numeric data with known precision bounds (e.g., sensor readings, financial data), enabling:
f64 values with configurable precisionf64 with controlled precision lossdecimal_bytes::Decimal (arbitrary precision) or Decimal64 (≤16 digits)Decimal or Decimal64 with controlled precision lossdecimal-bytes interop for PostgreSQL NUMERIC compatibilitythiserror)Add to your Cargo.toml:
[dependencies]
buff-rs = "0.1"
[dependencies]
# Enable decimal-bytes interop for PostgreSQL NUMERIC compatibility
buff-rs = { version = "0.1", features = ["decimal"] }
use buff_rs::BuffCodec;
// Create a codec with 3 decimal places of precision (scale=1000)
let codec = BuffCodec::new(1000);
// Encode an array of f64 values
let data = vec![1.234, 5.678, 9.012, -3.456];
let encoded = codec.encode(&data).unwrap();
// Decode back to f64
let decoded = codec.decode(&encoded).unwrap();
// Query directly on compressed data
let sum = codec.sum(&encoded).unwrap();
let max = codec.max(&encoded).unwrap();
The scale determines the precision of encoded values:
| Scale | Decimal Places | Example Value |
|---|---|---|
| 10 | 1 | 3.1 |
| 100 | 2 | 3.14 |
| 1000 | 3 | 3.142 |
| 10000 | 4 | 3.1416 |
| 100000 | 5 | 3.14159 |
Choose a scale that matches your data's required precision. Higher scales provide more precision but may reduce compression ratio.
BUFF supports special floating-point values:
use buff_rs::BuffCodec;
let codec = BuffCodec::new(1000);
let data = vec![1.0, f64::INFINITY, 2.0, f64::NAN, f64::NEG_INFINITY];
// Use encode_with_special for arrays containing special values
let encoded = codec.encode_with_special(&data).unwrap();
let decoded = codec.decode(&encoded).unwrap();
assert!(decoded[1].is_infinite());
assert!(decoded[3].is_nan());
Enable the decimal feature for decimal-bytes compatibility:
[dependencies]
buff-rs = { version = "0.1", features = ["decimal"] }
use buff_rs::BuffCodec;
use decimal_bytes::Decimal;
let codec = BuffCodec::new(1000);
// Encode Decimal values (with precision loss)
let decimals: Vec<Decimal> = vec![
"1.234".parse().unwrap(),
"5.678".parse().unwrap(),
];
let encoded = codec.encode_decimals(&decimals).unwrap();
// Decode back to Decimal
let decoded: Vec<Decimal> = codec.decode_to_decimals(&encoded).unwrap();
Decimal64 provides faster conversions for values that fit in 16 significant digits:
use buff_rs::BuffCodec;
use decimal_bytes::Decimal64;
let codec = BuffCodec::new(1000);
// Encode Decimal64 values
let decimals: Vec<Decimal64> = vec![
Decimal64::new("1.234", 3).unwrap(),
Decimal64::new("5.678", 3).unwrap(),
];
let encoded = codec.encode_decimal64s(&decimals).unwrap();
// Decode back to Decimal64 (specify output scale)
let decoded: Vec<Decimal64> = codec.decode_to_decimal64s(&encoded, 3).unwrap();
Note: Converting between BUFF and Decimal types involves precision loss because BUFF uses bounded floating-point representation while Decimal types use exact fixed-point representation.
Key performance characteristics (run cargo bench --features decimal locally for your hardware):
| Operation | Time | Throughput |
|---|---|---|
| Encode (1K values) | 2.4 µs | 410 Melem/s |
| Encode (100K values) | 314 µs | 318 Melem/s |
| Decode (1K values) | 600 ns | 1.66 Gelem/s |
| Decode (100K values) | 138 µs | 725 Melem/s |
| Sum (10K, compressed) | 6.9 µs | 1.44 Gelem/s |
| Scale | Precision | Compressed Size | Ratio |
|---|---|---|---|
| 100 | 2 decimal places | 20 KB | 25% of original |
| 1000 | 3 decimal places | 30 KB | 37.5% of original |
| 10000 | 4 decimal places | 30 KB | 37.5% of original |
(For 10,000 f64 values = 80 KB uncompressed)
For 1,000 values with BUFF encoding:
| Operation | Decimal64 | Decimal | Speedup |
|---|---|---|---|
| BUFF encode | 4.0 µs | 74 µs | 18x |
| BUFF decode to type | 213 µs | 189 µs | ~1x |
| BUFF decode to f64 | 600 ns | 600 ns | - |
| Type | Storage |
|---|---|
| BUFF compressed | 2,020 bytes |
| Decimal (bytes) | 4,971 bytes |
| Decimal64 | 8,000 bytes (fixed 8 bytes each) |
| Decimal (stack+heap) | 24,000 + 4,807 bytes |
BUFF provides ~2.5x better compression than decimal-bytes and ~100x faster array decoding for columnar workloads.
| Aspect | Decimal | Decimal64 | buff-rs |
|---|---|---|---|
| Data Type | Single values | Single values | Arrays of floats |
| Precision | Unlimited | ≤16 digits | Bounded (fixed scale) |
| Storage | Variable | 8 bytes | Column-oriented |
| Primary Use | Document storage | Fixed-size storage | Columnar/time-series |
| Query Style | Decode then compare | Decode then compare | Query compressed |
| Sortable Bytes | Yes | No | No |
Use Decimal when:
Use Decimal64 when:
Use buff-rs when:
Given a precision tolerance (e.g., 0.001 for 3 decimal places), BUFF determines the minimum number of bits needed to represent each value. This is done by analyzing the IEEE 754 representation and finding the position where further bits don't affect precision.
Instead of storing values row-by-row, BUFF stores them column-by-column at the byte level:
Traditional: [v1_byte0, v1_byte1] [v2_byte0, v2_byte1] [v3_byte0, v3_byte1]
Byte-sliced: [v1_byte0, v2_byte0, v3_byte0] [v1_byte1, v2_byte1, v3_byte1]
This layout enables SIMD-accelerated comparisons for range queries.
To enable proper ordering of negative and positive values, the sign bit is flipped during encoding. This ensures that comparing encoded bytes yields correct numerical ordering.
Compression ratio depends on data characteristics:
Typical compression ratios range from 0.3x to 0.8x of the original size (8 bytes per f64).
MIT License - see LICENSE for details.