crypto-ratio
Rational number arithmetic using crypto-bigint crypto-bigint.
Built for use in RISC Zero zkVM applications to leverage accelerated crypto-bigint precompiles with rational number operations.
Features
- Generic over integer width: Works with U64, U256, U512, U1024, U2048, and all crypto-bigint types up to U16384
- RISC Zero optimized: Designed to work with RISC Zero's accelerated crypto-bigint precompiles
- Performance-focused: Deferred reduction and smart heuristics minimize expensive GCD operations
- Overflow handling: Automatic fallback to wider types when operations overflow
- no_std compatible: Works in embedded, constrained, and zkVM environments
Why RISC Zero?
This library was built to enable efficient rational number arithmetic in RISC Zero's zkVM, where crypto-bigint operations are hardware-accelerated through precompiles. By building on crypto-bigint rather than num-bigint, this crate can leverage:
- Hardware acceleration for big integer operations in zkVM
- Reduced cycle counts for cryptographic workloads (U256 and U512 extrem fast)
Special thanks to the RISC Zero team for building an incredible zkVM platform and for their excellent documentation on accelerator usage.
Installation
Add to your Cargo.toml:
[]
= "0.1"
= "0.5"
For RISC Zero zkVM guests:
[]
= "0.1"
= "0.5"
= { = "3.0.3" }
Quick Start
use ;
use U512;
// Create rationals
let a = from_u64; // 1/2
let b = from_u64; // 1/3
// Arithmetic operations
let sum = &a + &b; // 5/6
let product = &a * &b; // 1/6
// Explicit reduction when needed
let mut result = product;
result.normalize;
// Comparison
assert!;
// Float conversion
let r = from_float.unwrap;
assert_eq!;
RISC Zero zkVM Example
use RatioU512;
use env;
Design Philosophy
Unreduced Operations by Default
Operations like multiplication and addition return unreduced results for performance. Call normalize() explicitly when reduction is needed:
use RatioU256;
let a = from_u64;
let b = from_u64;
// Unreduced: 6/12
let product = &a * &b;
// Reduced: 1/2
let mut reduced = product;
reduced.normalize;
Why? In loops and chained operations, automatic reduction uses too much cycles:
// Bad: Reduces 100 times (expensive!)
let mut sum = zero;
for i in 0..100
// Good: Reduces once at the end
let mut sum = zero;
for i in 0..100
sum.normalize; // Single GCD at the end
Smart Reduction Heuristics
mul_reduced() provides automatic reduction using fast heuristics:
let a = from_u64;
let b = from_u64;
// Smart reduction uses:
// 1. Bit shifts for power-of-2 factors (~10ns)
// 2. Fast u64 GCD for small values (~50ns)
// 3. Skips GCD for large coprime values (~0ns)
let product = a.mul_reduced; // 1/2 (reduced)
Generic Over Integer Width
Choose the size that fits your precision and performance needs: Heads up: Performance decreases from U2048 onwards due to automatic wide types on overflow, fix coming soon.
use ;
use ;
// Small: Fast operations, limited range
let small = from_u64;
// Medium: Good balance (recommended)
let medium = from_u64;
// Large: Maximum precision
let large = from_u64;
// Custom size
let custom = from_u64;
Overflow Handling
Operations automatically use wider types when needed:
use RatioU256;
// U256 operations may internally use U512
let a = from_u64;
let b = from_u64;
// This works! Uses U512 internally, reduces back to U256
let sum = a.add;
Real-World Example: Taylor Series
Computing e^x using Taylor series expansion demonstrates the performance benefits:
use RatioU512;
use U512;
let x = from_float.unwrap;
let e_to_half = exp_approx;
println!;
Trade-offs
Performance vs Automatic Reduction
| Approach | Pros | Cons | Best For |
|---|---|---|---|
| Unreduced (default) | 10-100x faster in loops | Manual normalize() calls |
High-performance code, loops, zkVM |
mul_reduced() |
Smart, usually fast | Some overhead | General use, single operations |
| Always reduce | Simple API | Much slower | Prototyping |
Integer Size Selection
| Size | Range | Speed | Memory | Use Case |
|---|---|---|---|---|
| U256 | ~77 decimal digits | Fastest | 32 bytes | Embedded, simple fractions, zkVM-optimized |
| U512 | ~154 decimal digits | Fast | 64 bytes | Embedded, zkVM-optimized Recommended default |
| U1024 | ~308 decimal digits | Moderate | 128 bytes | High precision |
| U2048+ | ~600+ decimal digits | Slower | 256+ bytes | Specialized applications (performance drops, num-bigint might be faster) |
Performance
Benchmark Overview
Typical performance on modern hardware (U512):
| Operation | Time | Speedup vs num-rational |
|---|---|---|
| Construction | ~15ns | 2-3x faster |
| Addition | ~30ns | 5-10x faster |
| Multiplication (unreduced) | ~25ns | 10-20x faster |
| Multiplication (reduced) | ~80ns | 3-5x faster |
| Comparison | ~35ns | 3-5x faster |
| GCD | ~200ns | 2-3x faster |
| Taylor Series (5 terms) | ~800ns | 8-12x faster |
Running Tests
# Run all tests (2,300+ tests across 9 integer sizes)
# Run tests for specific size
# Run specific test
# Run with output
# Run tests with all features
Running Benchmarks
# Run all benchmarks
# Run benchmarks for specific size
# Run specific operation
# Run specific size + operation
# Generate detailed report with violin plots
# Compare against baseline
Benchmark results are saved in target/criterion/ with detailed HTML reports including:
- Violin plots showing distribution
- Regression analysis
- Historical comparison
- Statistical significance tests
Viewing Benchmark Reports
After running benchmarks, open the HTML report:
# On macOS
# On Linux
# On Windows
API Overview
Construction
// From u64 (with reduction)
let r = from_u64;
// From float
let r = from_float.unwrap;
// Without reduction
let r = new_raw;
// With reduction
let r = new;
Arithmetic
let a = from_u64;
let b = from_u64;
// Basic operations (unreduced)
let sum = &a + &b;
let diff = &a - &b;
let prod = &a * &b;
let quot = &a / &b;
// Negation
let neg = -a;
// Reciprocal
let recip = a.recip;
Reduction
let mut r = from_u64;
// Explicit reduction
r.normalize; // Now 3/4
// Conditional reduction
if r.needs_reduction
// Smart multiplication
let a = from_u64;
let b = from_u64;
let prod = a.mul_reduced; // 1/2 (reduced)
Comparison
let a = from_u64;
let b = from_u64;
assert!;
assert!;
assert!;
assert!;
no_std Support
This crate is no_std compatible (requires alloc for GCD operations):
[]
= { = "0.1", = false }
Perfect for:
- Embedded systems
- RISC Zero zkVM guests
- WebAssembly
- Constrained environments
Minimum Supported Rust Version (MSRV)
Rust 1.65 or later.
License
Licensed under either of:
- Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.
Contributing
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch
- Add tests for new functionality
- Run
cargo testandcargo bench - Ensure
cargo clippypasses - Submit a pull request
Comparison with Alternatives
| Feature | crypto-ratio | num-rational | rug |
|---|---|---|---|
| Pure Rust | ✅ | ✅ | ❌ (GMP) |
| no_std | ✅ | ❌ | ❌ |
| RISC Zero zkVM | ✅ | ❌ | ❌ |
| Generic width | ✅ | ❌ | ✅ |
| Deferred reduction | ✅ | ❌ | ❌ |
| Compile-time size | ✅ | ❌ | ❌ |
| Accelerated precompiles | ✅ | ❌ | ❌ |
| Performance | High | Medium | Very High* |
*GMP performance only available in native code, not in zkVM environments.
Choose crypto-ratio if you need: RISC Zero zkVM support, pure Rust, no_std support, control over reduction, or compile-time size selection.
Choose num-rational if you need: Simple API with automatic reduction, dynamic sizing, don't need zkVM support.
Choose rug if you need: Maximum performance in native environments, don't mind GMP dependency.
Acknowledgments
Built on crypto-bigint by RustCrypto.
Designed for RISC Zero zkVM with support for hardware-accelerated big integer operations.
Inspired by num-rational with focus on stable performance and flexibility.
Resources
Questions? Open an issue or discussion on GitHub.