formualizer_eval/engine/
packed_coord.rs

1/// Bit-packed coordinate representation for memory efficiency
2///
3/// Layout (64 bits):
4/// [63:44] Reserved (20 bits) - MUST BE ZERO
5/// [43:24] Row (20 bits) - 0 to 1,048,575 (zero-based)
6/// [23:10] Col (14 bits) - 0 to 16,383 (zero-based)
7/// [9:0]   Reserved (10 bits) - MUST BE ZERO
8#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
9pub struct PackedCoord(u64);
10
11impl PackedCoord {
12    pub const INVALID: Self = Self(u64::MAX);
13    pub const RESERVED_MASK: u64 = 0xFFFFF00000000000 | 0x3FF; // Bits [63:44] and [9:0]
14
15    /// Creates a new PackedCoord from row and column indices
16    ///
17    /// # Safety Invariants
18    /// - Row must be <= 1,048,575 (20 bits)
19    /// - Column must be <= 16,383 (14 bits)
20    /// - Reserved bits [63:44] and [9:0] MUST remain zero
21    pub fn new(row: u32, col: u32) -> Self {
22        assert!(row <= 0x000FFFFF, "Row {row} exceeds 20 bits");
23        assert!(col <= 0x00003FFF, "Col {col} exceeds 14 bits");
24        Self((row as u64) << 24 | (col as u64) << 10)
25    }
26
27    #[inline(always)]
28    pub fn row(self) -> u32 {
29        ((self.0 >> 24) & 0x000FFFFF) as u32
30    }
31
32    #[inline(always)]
33    pub fn col(self) -> u32 {
34        ((self.0 >> 10) & 0x00003FFF) as u32
35    }
36
37    #[inline(always)]
38    pub fn as_u64(self) -> u64 {
39        self.0
40    }
41
42    pub fn is_valid(self) -> bool {
43        self.0 != u64::MAX
44    }
45
46    // For serialization - ensures reserved bits are zero
47    pub fn normalize(self) -> Self {
48        Self(self.0 & !Self::RESERVED_MASK)
49    }
50}
51
52#[cfg(test)]
53mod tests {
54    use super::*;
55
56    #[test]
57    fn test_packed_coord_roundtrip() {
58        let coord = PackedCoord::new(1_048_575, 16_383); // Max Excel coords
59        assert_eq!(coord.row(), 1_048_575);
60        assert_eq!(coord.col(), 16_383);
61        // Row: 0xFFFFF << 24, Col: 0x3FFF << 10
62        let expected = (0xFFFFF_u64 << 24) | (0x3FFF_u64 << 10);
63        assert_eq!(coord.as_u64(), expected);
64    }
65
66    #[test]
67    fn test_packed_coord_zero() {
68        let coord = PackedCoord::new(0, 0);
69        assert_eq!(coord.row(), 0);
70        assert_eq!(coord.col(), 0);
71        assert_eq!(coord.as_u64(), 0);
72    }
73
74    #[test]
75    fn test_packed_coord_invalid() {
76        let invalid = PackedCoord::INVALID;
77        assert!(!invalid.is_valid());
78        assert_eq!(invalid.as_u64(), u64::MAX);
79    }
80
81    #[test]
82    fn test_packed_coord_normalize() {
83        let coord = PackedCoord::new(100, 200);
84        let normalized = coord.normalize();
85        assert_eq!(normalized.row(), 100);
86        assert_eq!(normalized.col(), 200);
87        // Verify reserved bits are zero
88        assert_eq!(normalized.as_u64() & PackedCoord::RESERVED_MASK, 0);
89    }
90
91    #[test]
92    #[should_panic(expected = "Row 1048576 exceeds 20 bits")]
93    fn test_packed_coord_row_overflow() {
94        PackedCoord::new(1_048_576, 0); // 2^20
95    }
96
97    #[test]
98    #[should_panic(expected = "Col 16384 exceeds 14 bits")]
99    fn test_packed_coord_col_overflow() {
100        PackedCoord::new(0, 16_384); // 2^14
101    }
102}