intarray
Memory-efficient packed arrays for Rust. Five types covering unsigned integers, signed integers, floating-point, arbitrary-precision integers, and exact rationals — all with no per-element heap allocation.
IntArray— N-bit unsigned integers packed intoVec<u64>. Store 1–64 bits per element with no per-element overhead.RadixArray— Signed integers in an arbitrary range[A, B], packed using mixed-radix encoding. Maximizes elements per 64-bit word for any value range.FloatArray— Floating-point values stored at a custom bit precision (exponent + mantissa bits). Supports FLOAT16, BFLOAT16, FLOAT32, FLOAT64, and any user-defined format.VarIntArray— Arbitrary-precision integers (num_bigint::BigInt) compressed with Elias gamma + zigzag encoding. Block-based storage for random access.RatioArray— Exact rational numbers (num_rational::Ratio<BigInt>) with no rounding error. Numerator and denominator are each Elias-gamma encoded.
When to use
IntArray |
RadixArray |
FloatArray |
VarIntArray |
RatioArray |
|
|---|---|---|---|---|---|
| Value type | u64 |
i64 |
f64 |
BigInt |
Ratio<BigInt> |
| Precision | fixed bits | fixed range | custom exp+man | arbitrary | arbitrary |
| Bounded values | yes | yes | no | no | no |
| Exact arithmetic | — | — | no | yes | yes |
| Best for | bit-width known at design time | range known at runtime | compact floats | large unbounded ints | rounding-free rationals |
All types: memory is the bottleneck, no per-element heap allocation.
Installation
[]
= "0.4"
Error type
All types share a single error enum:
ArrayError implements std::error::Error and Display.
IntArray
Stores u64 values using exactly bits bits per element. Suitable when all values fit in a known fixed bit width.
Quick start
use ;
// 7-bit unsigned integers, 1000 elements (pre-allocated, zero-filled)
let mut v = new;
v.set.unwrap; // v[0] = 100
v.set.unwrap; // max for 7 bits
assert_eq!;
// Out-of-range returns Err, never panics
assert_eq!;
v.push.unwrap; // append
Construction
// Pre-allocated, zero-filled
let v = new;
// From a Vec
let v = new_with_vec.unwrap;
// From an iterator
let v = new_with_iter.unwrap;
// Infer minimum bit width from data
let v = new_with_vec.unwrap;
let compact = v.shape_auto; // bits = 2 (max value = 3, needs 2 bits)
Element access
let mut v = new; // max value = 15
v.get.unwrap; // → 0
v.set.unwrap; // ok
v.set.unwrap_err; // TooLarge
v.get.unwrap_err; // OutOfBounds
v.push.unwrap; // append, returns new index
v.pop.unwrap; // remove last, returns value
v.incr.unwrap; // v[5] += 1
v.decr.unwrap; // v[5] -= 1
v.add.unwrap; // v[5] += 3
v.sub.unwrap; // v[5] -= 3
incr_limit / decr_limit clamp at the boundary and return None at the edge:
v.incr_limit; // → Some(old_value) or None if already at max
v.decr_limit; // → Some(old_value) or None if already at 0
Bulk operations
push, extend, and extend_array are all atomic: on error, the array is left unchanged.
let mut v = new;
v.extend.unwrap;
let other = new_with_vec.unwrap;
v.extend_array.unwrap; // fast path when bits and alignment match
Arithmetic operators
Element-wise +=, -=, *= on a scalar u64 or another IntArray:
let mut a = new_with_vec.unwrap;
a += 5u64; // [15, 25, 35]
let b = new_with_vec.unwrap;
a += &b; // [16, 27, 38]
Iteration and statistics
let v = new_with_vec.unwrap;
for x in v.iter
v.sum.unwrap; // → 23u128
v.min.unwrap; // → 1
v.max.unwrap; // → 9
v.average.unwrap; // → 3.833...
Shape / reshape
let v = new_with_vec.unwrap;
let v10 = v.shape; // reshape to 10 bits
let compact = v.shape_auto; // minimum bits for max value (10 bits for 1000)
let sub = v.subarray; // elements [1..3) — zero-copy when aligned
Memory layout
v.len(); // number of elements
v.capacity(); // allocated capacity in elements (rounded to word boundary)
v.datasize(); // total size in bytes
Each u64 word holds 64 / bits elements. For example, 4-bit integers pack 16 per word; a 100-element array uses 7 words (56 bytes of data).
RadixArray
Stores i64 values in a fixed range [A, B] using mixed-radix (base-K) encoding, where K = B − A + 1. Each u64 word holds floor(64·ln2 / ln(K)) elements — maximally dense for any value range.
Quick start
use ;
// Values in [0, 9] (10 possible values), 5 elements
let mut v = new.unwrap;
v.set.unwrap;
assert_eq!;
// Out-of-range returns Err
assert_eq!;
assert_eq!;
v.push.unwrap; // → index 5
Construction
// Pre-allocated, values initialized to A
let v = new.unwrap;
// From a Vec — atomic (Err if any value out of range)
let v = new_with_vec.unwrap;
// From an iterator
let v = new_with_iter.unwrap;
Element access
let mut v = new.unwrap;
v.get.unwrap; // → -100 (initialized to A)
v.set.unwrap;
v.set.unwrap_err; // TooLarge
v.set.unwrap_err; // TooSmall
v.push.unwrap; // append, returns new index
v.pop.unwrap; // remove last, returns value
Bulk operations
let mut v = new.unwrap;
v.extend.unwrap;
// Extend from another RadixArray
let other = new_with_vec.unwrap;
v.extend_array.unwrap; // fast path when ranges match and alignment holds
Iteration and statistics
let v = new_with_vec.unwrap;
for x in v.iter
v.sum.unwrap; // → -2i128
v.min.unwrap; // → -3
v.max.unwrap; // → 2
v.average.unwrap; // → -0.5
Range info
v.base; // K = B − A + 1
v.range; // (A, B) as (i64, i64)
v.len;
v.capacity; // allocated capacity in elements
v.datasize; // total size in bytes
Packing efficiency
| Range size K | elements per word |
|---|---|
| 2 | 64 |
| 10 | 19 |
| 256 | 8 |
| 65536 | 4 |
| 2³² | 2 |
FloatArray
Stores f64 values at a reduced precision defined by (exp_bits, man_bits). Each value is re-encoded into the custom format on write and decoded back to f64 on read. Four predefined formats are provided as constants.
| Constant | exp | man | total bits | notes |
|---|---|---|---|---|
FLOAT64 |
11 | 52 | 64 | standard f64 |
FLOAT32 |
8 | 23 | 32 | standard f32 |
FLOAT16 |
5 | 10 | 16 | IEEE 754 half |
BFLOAT16 |
8 | 7 | 16 | Google Brain float |
Quick start
use ;
// 32-bit floats, block size 64
let mut v = new.unwrap;
v.push.unwrap;
v.push.unwrap;
assert!;
v.sum.unwrap; // → ~2.14
v.min.unwrap; // → ~-1.0
v.average.unwrap; // → ~1.07
Construction
use ;
// Using a predefined format
let v = new.unwrap;
// Custom format: 6-bit exponent, 9-bit mantissa (16 bits total)
let v = new.unwrap;
// From a Vec
let v = new_with_vec.unwrap;
// From an iterator
let v = new_with_iter.unwrap;
Element access
let mut v = new.unwrap;
v.set.unwrap;
v.get.unwrap; // → ~1.5
v.push.unwrap; // append, returns new index
v.pop.unwrap; // remove last, returns value
Iteration and statistics
let v = new_with_vec.unwrap;
for x in v.iter
v.sum.unwrap; // → ~6.0
v.min.unwrap; // → ~1.0
v.max.unwrap; // → ~3.0
v.average.unwrap; // → ~2.0
Metadata
v.len;
v.bits_per_unit; // = 1 + exp_bits + man_bits
v.datasize; // total size in bytes
VarIntArray
Stores arbitrary-precision signed integers (num_bigint::BigInt) using Elias gamma + zigzag encoding. Elements are grouped into blocks of k for O(1) amortized push and O(k) get/set.
Memory usage scales with the actual values stored: small values (near zero) use fewer bits.
Quick start
use VarIntArray;
use BigInt;
let mut v = new.unwrap; // block size = 64
v.push.unwrap;
v.push.unwrap;
v.push.unwrap;
v.push.unwrap;
assert_eq!;
assert_eq!;
Construction
use BigInt;
let v = new.unwrap;
let v = new_with_vec.unwrap;
let v = new_with_iter.unwrap;
Element access
let mut v = new_with_vec.unwrap;
v.get.unwrap; // → BigInt::from(1)
v.set.unwrap; // decode block, replace, re-encode
v.push.unwrap; // append
v.pop.unwrap; // remove last
Iteration and statistics
let v = new_with_vec.unwrap;
for x in v.iter
v.sum.unwrap; // → BigInt::from(14)
v.min.unwrap; // → BigInt::from(-3)
v.max.unwrap; // → BigInt::from(10)
v.average.unwrap; // → ~4.666... (f64, None if empty)
Metadata
v.len;
v.block_size; // k
v.block_count; // number of blocks
v.datasize; // total size in bytes
RatioArray
Stores exact rational numbers (num_rational::Ratio<BigInt>) with no rounding error. The numerator is zigzag + Elias gamma encoded; the denominator (always ≥ 1) uses standard Elias gamma. Integers (denominator = 1) cost only 1 extra bit over VarIntArray.
average() returns an exact Ratio<BigInt>, not a float.
Quick start
use RatioArray;
use BigInt;
use Ratio;
let mut v = new.unwrap;
v.push.unwrap;
v.push.unwrap; // 1/2
v.push.unwrap; // -1/3
v.push.unwrap; // 22/7
assert_eq!;
assert_eq!;
Construction
use BigInt;
use Ratio;
let v = new.unwrap;
let v = new_with_vec.unwrap;
let v = new_with_iter.unwrap;
Element access
let mut v = new_with_vec.unwrap;
v.get.unwrap; // → 1/1
v.set.unwrap;
v.push.unwrap;
v.pop.unwrap;
Iteration and statistics
use BigInt;
use Ratio;
let v = new_with_vec.unwrap;
for x in v.iter
v.sum.unwrap; // → Ratio = 1 (exact: 1/2 + 1/3 + 1/6 = 1)
v.min.unwrap; // → 1/6
v.max.unwrap; // → 1/2
v.average.unwrap; // → Ratio = 1/3 (exact, no float rounding)
Metadata
v.len;
v.block_size; // k
v.block_count; // number of blocks
v.datasize; // total size in bytes
Serialization (serde)
All types serialize as flat sequences. Internal parameters (bit width, range, block size) are not preserved; they are re-inferred or reset to defaults on deserialization.
use serde_json;
// IntArray → JSON array of u64
let v = new_with_vec.unwrap;
let json = to_string.unwrap; // "[1,2,3]"
let v2: IntArray = from_str.unwrap;
// Bit width re-inferred from max value on deserialize.
// RadixArray → JSON array of i64
let r = new_with_vec.unwrap;
let json = to_string.unwrap; // "[-1,0,2]"
let r2: RadixArray = from_str.unwrap;
// Range [A, B] re-inferred from min/max on deserialize.
// FloatArray → JSON array of f64
let f = new_with_vec.unwrap;
let json = to_string.unwrap; // "[1.0,2.0]"
let f2: FloatArray = from_str.unwrap;
// Format defaults to FLOAT64 on deserialize.
// VarIntArray → JSON array of decimal strings (arbitrary precision)
let vi = new_with_vec.unwrap;
let json = to_string.unwrap; // "["-1","2"]"
let vi2: VarIntArray = from_str.unwrap;
// Block size k defaults to 64 on deserialize.
// RatioArray → JSON array of "p/q" strings (integers as "p")
let ra = new_with_vec.unwrap;
let json = to_string.unwrap; // "["1/2","-3"]"
let ra2: RatioArray = from_str.unwrap;
// Block size k defaults to 64 on deserialize.
PartialEq for VarIntArray and RatioArray compares elements only (ignoring block size k), so a serde round-trip always compares equal.
MSRV
Rust 1.87 (uses usize::is_multiple_of, stabilized in 1.87).