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///
63/// # Example
64///
65/// ```
66/// use wire_codec::zigzag;
67/// assert_eq!(zigzag::decode_i64(3), -2);
68/// assert_eq!(zigzag::decode_i64(u64::MAX), i64::MIN);
69/// ```
70#[inline]
71pub const fn decode_i64(value: u64) -> i64 {
72    ((value >> 1) as i64) ^ -((value & 1) as i64)
73}
74
75#[cfg(test)]
76mod tests {
77    use super::*;
78
79    #[test]
80    fn i32_round_trip() {
81        for v in [0i32, 1, -1, 2, -2, 100, -100, i32::MAX, i32::MIN] {
82            assert_eq!(decode_i32(encode_i32(v)), v);
83        }
84    }
85
86    #[test]
87    fn i64_round_trip() {
88        for v in [0i64, 1, -1, i64::MAX, i64::MIN, 1 << 40, -(1 << 40)] {
89            assert_eq!(decode_i64(encode_i64(v)), v);
90        }
91    }
92}