Skip to main content

msrtc_rans_core/
arithmetic.rs

1// Licensed under the MIT license.
2// Author: Riaan de Beer - github.com/infinityabundance - rdebeer.infinityabundance@gmail.com
3
4#![allow(missing_docs)]
5
6//! Arithmetic primitives for rANS.
7//!
8//! This module provides:
9//! - `mul_hi_u64`: High 64 bits of 64x64 multiply (equivalent to `__umulh` / `Mul64Hi`)
10//! - `compute_reciprocal`: Reciprocal frequency preparation for fast division
11//!
12//! The arithmetic here must match the Microsoft C++ implementation exactly.
13
14use crate::Freq;
15
16/// Returns the high 64 bits of the 128-bit product of two 64-bit integers.
17///
18/// Equivalent to `msrtc_rans::details::Mul64Hi(a, b)`.
19#[inline]
20pub fn mul_hi_u64(a: u64, b: u64) -> u64 {
21    // Rust has the native widening_mul on nightly, but we use portable
22    // u128 to avoid nightly dependency.
23    let prod = a as u128 * b as u128;
24    (prod >> 64) as u64
25}
26
27/// Compute the reciprocal shift for a given frequency.
28///
29/// This is `shift = ceil(log2(freq))` from the C++ implementation.
30#[inline]
31pub fn reciprocal_shift(freq: Freq) -> u32 {
32    let mut shift = 1u32;
33    while freq > (1u32 << shift) {
34        shift += 1;
35    }
36    shift
37}
38
39/// Compute the fixed-point reciprocal frequency for a 64-bit state.
40///
41/// This implements the Alverson integer division method used in the Microsoft code
42/// for the uint64_t state path:
43///
44/// ```cpp
45/// // long divide: ((1 << (shift + bits - 1)) + freq-1) / freq
46/// auto x0 = static_cast<state_t>(freq - 1);
47/// auto x1 = static_cast<state_t>(1) << (shift + 31);
48/// auto t1 = x1 / freq;
49/// x0 += (x1 % freq) << 32;
50/// auto t0 = x0 / freq;
51/// m_freq_rcp = t0 + (t1 << 32);
52/// ```
53///
54/// For the 32-bit state path we use the uint64_t extension method.
55#[inline]
56pub fn compute_reciprocal_u64(freq: Freq) -> u64 {
57    // This matches the uint64_t specialization in the C++ source:
58    // auto x0 = static_cast<state_t>(freq - 1);
59    // auto x1 = static_cast<state_t>(1) << (shift + 31);
60    // auto t1 = x1 / freq;
61    // x0 += (x1 % freq) << 32;
62    // auto t0 = x0 / freq;
63    // m_freq_rcp = t0 + (t1 << 32);
64    let x0: u64 = (freq - 1) as u64;
65    let shift = reciprocal_shift(freq);
66    let x1: u64 = 1u64 << (shift + 31);
67    let t1 = x1 / freq as u64;
68    let x0_adj = x0 + ((x1 % freq as u64) << 32);
69    let t0 = x0_adj / freq as u64;
70    t0 + (t1 << 32)
71}
72
73/// Compute the fixed-point reciprocal frequency for a 32-bit state.
74///
75/// For the 32-bit state path:
76/// ```cpp
77/// constexpr auto bits = sizeof(state_t) * CHAR_BIT;
78/// auto nom = (static_cast<uint64_t>(1) << (shift + bits - 1)) + (freq - 1);
79/// m_freq_rcp = static_cast<state_t>(nom / freq);
80/// ```
81#[inline]
82pub fn compute_reciprocal_u32(freq: Freq) -> u32 {
83    let shift = reciprocal_shift(freq);
84    let bits: u32 = 32;
85    let nom: u64 = (1u64 << (shift + bits - 1)) + (freq - 1) as u64;
86    (nom / freq as u64) as u32
87}
88
89/// Compute the frequency-one reciprocal for 64-bit state.
90///
91/// When freq==1, the reciprocal is `~0u64` and shift is 0.
92#[inline]
93pub fn freq_one_reciprocal_u64() -> (u64, u32) {
94    (!0u64, 0)
95}
96
97/// Compute the frequency-one reciprocal for 32-bit state.
98#[inline]
99pub fn freq_one_reciprocal_u32() -> (u32, u32) {
100    (!0u32, 0)
101}
102
103/// Perform fast division by frequency using the reciprocal.
104///
105/// For u64 state: `Mul64Hi(x, freq_rcp) >> rcp_shift`
106/// For u32 state: `(u64(x) * freq_rcp) >> rcp_shift`
107#[inline]
108pub fn fast_quotient_u64(x: u64, freq_rcp: u64, rcp_shift: u32) -> u64 {
109    mul_hi_u64(x, freq_rcp) >> rcp_shift
110}
111
112#[inline]
113pub fn fast_quotient_u32(x: u32, freq_rcp: u32, rcp_shift: u32) -> u32 {
114    ((x as u64 * freq_rcp as u64) >> rcp_shift) as u32
115}
116
117#[cfg(test)]
118mod tests {
119    use super::*;
120
121    #[test]
122    fn test_mul_hi_u64() {
123        // 3 * 5 = 15, high part of small numbers is 0
124        assert_eq!(mul_hi_u64(3, 5), 0);
125        // !0u64 * 1 = 2^64-1, high part is 0
126        assert_eq!(mul_hi_u64(!0u64, 1u64), 0);
127        // 2^63 * 2^63 = 2^126, high 64 bits = 2^62
128        let a: u64 = 1u64 << 63;
129        assert_eq!(mul_hi_u64(a, a), 1u64 << 62);
130        // !0u64 * !0u64 = (2^64-1)^2 = 2^128 - 2*2^64 + 1
131        // high bits: 2^64 - 2
132        assert_eq!(mul_hi_u64(!0u64, !0u64), !0u64 - 1);
133    }
134
135    #[test]
136    fn test_reciprocal_shift() {
137        // C++: shift=1; while (freq > (1<<shift)) shift++;
138        assert_eq!(reciprocal_shift(1), 1);
139        assert_eq!(reciprocal_shift(2), 1); // 2 > 2? no
140        assert_eq!(reciprocal_shift(3), 2); // 3 > 2? yes; 3 > 4? no
141        assert_eq!(reciprocal_shift(4), 2); // 4 > 2? yes; 4 > 4? no
142        assert_eq!(reciprocal_shift(5), 3); // 5 > 2? yes; 5 > 4? yes; 5 > 8? no
143        assert_eq!(reciprocal_shift(8), 3); // 8 > 2? yes; 8 > 4? yes; 8 > 8? no
144        assert_eq!(reciprocal_shift(9), 4);
145    }
146
147    #[test]
148    fn test_compute_reciprocal_u64() {
149        let rcp = compute_reciprocal_u64(3);
150        assert!(rcp > 0);
151        // With freq=3, x=LowerBound (1<<31), quotient should be ~715827882
152        let x: u64 = 1u64 << 31;
153        let q = fast_quotient_u64(x, rcp, reciprocal_shift(3) - 1);
154        // q should be floor(x/freq) = floor(2^31/3) = 715827882
155        assert_eq!(q, (1u64 << 31) / 3);
156    }
157
158    #[test]
159    fn test_fast_division_matches_exact() {
160        // Verify that fast division matches exact division for a range of values
161        // Note: freq=1 uses a special fast path, not compute_reciprocal_u64
162        let freqs = [2u32, 3, 5, 10, 100, 1000, 0x7FFF];
163        for freq in freqs {
164            let shift = reciprocal_shift(freq) - 1;
165            let rcp = compute_reciprocal_u64(freq);
166
167            for x in [1u64, 100, 1u64 << 20, 1u64 << 31, 1u64 << 40, 1u64 << 50] {
168                let fast = fast_quotient_u64(x, rcp, shift);
169                let exact = x / freq as u64;
170                assert_eq!(fast, exact, "freq={} x={}", freq, x);
171            }
172        }
173    }
174
175    #[test]
176    fn test_freq_one_special_case_division() {
177        // For freq=1: q = mul_hi(x, !0) >> 0 = x - 1 (for x > 0)
178        let (rcp, shift) = freq_one_reciprocal_u64();
179        assert_eq!(rcp, !0u64);
180        assert_eq!(shift, 0);
181
182        let x: u64 = 100;
183        let q = fast_quotient_u64(x, rcp, shift);
184        assert_eq!(q, x - 1);
185    }
186}