Usage Guide

Relevant source files

This guide demonstrates how to use the int_ratio crate in practical applications. It covers the core usage patterns, arithmetic operations, and performance considerations for working with the Ratio type.

For detailed information about the internal architecture and optimization strategies, see Internal Architecture. For comprehensive API documentation, see API Reference.

Creating and Using Ratios

The Ratio type provides efficient rational arithmetic through its optimized mult/(1<<shift) representation. The basic workflow involves creating a ratio from integers and then performing multiplication operations with either truncation or rounding behavior.

Basic Ratio Creation Workflow

flowchart TD
A["Input: numerator, denominator"]
B["Ratio::new()"]
C["Internal optimization"]
D["Ready for calculations"]
E["mul_trunc()"]
F["mul_round()"]
G["inverse()"]
H["Truncated result"]
I["Rounded result"]
J["Inverted ratio"]
K["Special case: Ratio::zero()"]

A --> B
B --> C
C --> D
D --> E
D --> F
D --> G
E --> H
F --> I
G --> J
K --> D

Creating Ratios from Integers

The primary constructor Ratio::new() accepts u32 values for both numerator and denominator:

use int_ratio::Ratio;

// Create a ratio representing 2/3
let ratio = Ratio::new(2, 3);

// Create a ratio representing 625/1000 (0.625)
let fraction = Ratio::new(625, 1000);

// Create unit ratio (1/1)
let unit = Ratio::new(1, 1);

Sources: src/lib.rs(L46 - L84) 

Special Zero Ratio

The Ratio::zero() constructor creates a special 0/0 ratio that behaves safely in all operations, including inversion:

let zero = Ratio::zero();
assert_eq!(zero.mul_trunc(123), 0);
assert_eq!(zero.inverse(), Ratio::zero()); // Safe inversion

Sources: src/lib.rs(L22 - L44) 

Arithmetic Operations

The crate provides two multiplication methods with different rounding behaviors, plus ratio inversion.

Truncation vs Rounding Comparison

OperationMethodBehaviorExample (2/3 × 100)
Truncationmul_trunc()Round down66(from 66.666...)
Roundingmul_round()Round to nearest67(from 66.666...)
Inversioninverse()Swap num/denRatio::new(3, 2)

Truncation Operations

The mul_trunc() method always rounds down to the nearest integer:

let ratio = Ratio::new(2, 3);

// These demonstrate truncation behavior
assert_eq!(ratio.mul_trunc(99), 66);   // 99 × 2/3 = 66.0 exactly
assert_eq!(ratio.mul_trunc(100), 66);  // 100 × 2/3 = 66.666... → 66
assert_eq!(ratio.mul_trunc(101), 67);  // 101 × 2/3 = 67.333... → 67

Sources: src/lib.rs(L103 - L116) 

Rounding Operations

The mul_round() method rounds to the nearest integer using standard rounding rules:

let ratio = Ratio::new(2, 3);

// These demonstrate rounding behavior  
assert_eq!(ratio.mul_round(99), 66);   // 99 × 2/3 = 66.0 exactly
assert_eq!(ratio.mul_round(100), 67);  // 100 × 2/3 = 66.666... → 67
assert_eq!(ratio.mul_round(101), 67);  // 101 × 2/3 = 67.333... → 67

Sources: src/lib.rs(L118 - L132) 

Common Usage Patterns

Pattern: Time and Frequency Conversion

flowchart TD
A["Clock frequency"]
B["Ratio::new(target_freq, source_freq)"]
C["mul_round(time_value)"]
D["Converted time units"]
E["Timer ticks"]
F["Ratio::new(nanoseconds, tick_freq)"]
G["mul_trunc(tick_count)"]
H["Nanosecond duration"]

A --> B
B --> C
C --> D
E --> F
F --> G
G --> H

Example: Converting between time units

// Convert microseconds to nanoseconds (1000:1 ratio)
let us_to_ns = Ratio::new(1000, 1);
let nanoseconds = us_to_ns.mul_trunc(42); // 42000 ns

// Convert CPU cycles to milliseconds (assuming 1GHz CPU)
let cycles_to_ms = Ratio::new(1, 1_000_000); // 1ms per 1M cycles  
let duration_ms = cycles_to_ms.mul_round(2_500_000); // ~3ms

Pattern: Resource Scaling

flowchart TD
A["Available memory"]
B["Ratio::new(allocated_portion, total_memory)"]
C["mul_trunc(request_size)"]
D["Allocated amount"]
E["CPU load"]
F["Ratio::new(used_cores, total_cores)"]
G["mul_round(workload)"]
H["Distributed load"]

A --> B
B --> C
C --> D
E --> F
F --> G
G --> H

Example: Memory allocation ratios

// Allocate 3/4 of available memory to cache
let cache_ratio = Ratio::new(3, 4);
let total_memory = 1024 * 1024 * 1024; // 1GB
let cache_size = cache_ratio.mul_trunc(total_memory); // 768MB

// Scale down resource requests by 2/3
let scale_down = Ratio::new(2, 3);
let reduced_request = scale_down.mul_round(original_request);

Pattern: Rate Limiting and Throttling

// Throttle to 70% of maximum rate
let throttle = Ratio::new(7, 10);
let allowed_requests = throttle.mul_trunc(max_requests_per_second);

// Convert packet rate between different time windows
let per_second_to_per_minute = Ratio::new(60, 1);
let packets_per_minute = per_second_to_per_minute.mul_round(packets_per_second);

Performance Considerations

The Ratio type is optimized for performance-critical code through its mult/(1<<shift) representation.

Performance Benefits Workflow

flowchart TD
A["Traditional: value * num / den"]
B["Expensive division"]
C["Variable performance"]
D["int_ratio: (value * mult) >> shift"]
E["Bit shift operation"]
F["Consistent fast performance"]
G["Construction time"]
H["Pre-compute mult/shift"]
I["Amortized cost over many operations"]

A --> B
B --> C
D --> E
E --> F
G --> H
H --> I

When to Use Each Operation:

Use CaseRecommended MethodRationale
Memory allocationmul_trunc()Never over-allocate
Time calculationsmul_round()Minimize timing errors
Rate limitingmul_trunc()Conservative limiting
Display/UI valuesmul_round()Better visual appearance

Construction Cost vs Runtime Benefit

The Ratio::new() constructor performs optimization work upfront to enable fast runtime operations:

// One-time construction cost
let ratio = Ratio::new(2, 3); // Computes optimal mult/shift values

// Many fast runtime operations  
for value in large_dataset {
    let result = ratio.mul_trunc(value); // Just multiplication + shift
    process(result);
}

Sources: src/lib.rs(L62 - L83) 

Edge Cases and Error Handling

Division by Zero Protection

The constructor panics for invalid ratios but allows the special zero case:

// Panics: invalid ratio
// let invalid = Ratio::new(5, 0); 

// Safe: zero ratio
let zero_num = Ratio::new(0, 5);    // Represents 0/5 = 0
let zero_special = Ratio::zero();   // Special 0/0 case

// Safe inversion of zero ratios
assert_eq!(zero_special.inverse(), Ratio::zero());

Precision Limits

The Ratio type works with u32 inputs and u64 calculation values:

// Maximum values supported
let max_ratio = Ratio::new(u32::MAX, u32::MAX); // Represents 1.0
let tiny_ratio = Ratio::new(1, u32::MAX);       // Very small fraction

// Large value calculations
let large_result = max_ratio.mul_trunc(u64::MAX); // Uses u128 internally

Sources: src/lib.rs(L159 - L185) 

Debug and Inspection

The Debug implementation shows both the original and optimized representations:

let ratio = Ratio::new(625, 1000);
println!("{:?}", ratio); 
// Output: Ratio(625/1000 ~= 5/8)

This format displays:

  • Original numerator/denominator
  • Optimized mult/shift representation as an equivalent fraction

Sources: src/lib.rs(L135 - L146)