Skip to main content

wire_codec/
zigzag.rs

1//! Zigzag encoding for signed integers.
2//!
3//! Zigzag maps signed integers to unsigned ones so that small-magnitude negative
4//! values produce small-magnitude varints. Pair these helpers with the
5//! [`varint`][`crate::varint`] functions to encode signed values compactly.
6//!
7//! The mapping mirrors the one used by Protocol Buffers:
8//!
9//! | signed | unsigned |
10//! | ------ | -------- |
11//! |  0     | 0        |
12//! | -1     | 1        |
13//! |  1     | 2        |
14//! | -2     | 3        |
15//! |  2     | 4        |
16
17/// Map a signed `i32` to its zigzag-encoded `u32`.
18///
19/// # Example
20///
21/// ```
22/// use wire_codec::zigzag;
23/// assert_eq!(zigzag::encode_i32(0), 0);
24/// assert_eq!(zigzag::encode_i32(-1), 1);
25/// assert_eq!(zigzag::encode_i32(1), 2);
26/// assert_eq!(zigzag::encode_i32(i32::MIN), u32::MAX);
27/// ```
28#[inline]
29pub const fn encode_i32(value: i32) -> u32 {
30    ((value << 1) ^ (value >> 31)) as u32
31}
32
33/// Recover the signed `i32` from a zigzag-encoded `u32`.
34///
35/// # Example
36///
37/// ```
38/// use wire_codec::zigzag;
39/// assert_eq!(zigzag::decode_i32(0), 0);
40/// assert_eq!(zigzag::decode_i32(1), -1);
41/// assert_eq!(zigzag::decode_i32(u32::MAX), i32::MIN);
42/// ```
43#[inline]
44pub const fn decode_i32(value: u32) -> i32 {
45    ((value >> 1) as i32) ^ -((value & 1) as i32)
46}
47
48/// Map a signed `i64` to its zigzag-encoded `u64`.
49///
50/// # Example
51///
52/// ```
53/// use wire_codec::zigzag;
54/// assert_eq!(zigzag::encode_i64(i64::MIN), u64::MAX);
55/// ```
56#[inline]
57pub const fn encode_i64(value: i64) -> u64 {
58    ((value << 1) ^ (value >> 63)) as u64
59}
60
61/// Recover the signed `i64` from a zigzag-encoded `u64`.
62#[inline]
63pub const fn decode_i64(value: u64) -> i64 {
64    ((value >> 1) as i64) ^ -((value & 1) as i64)
65}
66
67#[cfg(test)]
68mod tests {
69    use super::*;
70
71    #[test]
72    fn i32_round_trip() {
73        for v in [0i32, 1, -1, 2, -2, 100, -100, i32::MAX, i32::MIN] {
74            assert_eq!(decode_i32(encode_i32(v)), v);
75        }
76    }
77
78    #[test]
79    fn i64_round_trip() {
80        for v in [0i64, 1, -1, i64::MAX, i64::MIN, 1 << 40, -(1 << 40)] {
81            assert_eq!(decode_i64(encode_i64(v)), v);
82        }
83    }
84}