Google协议缓冲区:ZigZag编码

时间:2010-12-26 07:27:40

标签: protocol-buffers bit-shift zigzag-encoding

Encoding - Protocol Buffers - Google Code上的“已签名类型”:

  

ZigZag编码将有符号整数映射到无符号整数,因此具有较小绝对值(例如-1)的数字也具有较小的varint编码值。它通过正负整数来回“zig-zags”的方式做到这一点,因此-1被编码为1,1被编码为2,-2被编码为3,依此类推,就像你一样可以在下表中看到:

Signed Original  Encoded As
0                0
-1               1
1                2
-2               3
2147483647       4294967294
-2147483648      4294967295
     

换句话说,每个值n使用

编码      

(n << 1) ^ (n >> 31)

     

表示sint32s,或

     

(n << 1) ^ (n >> 63)

     

表示64位版本。

(n << 1) ^ (n >> 31)如何在表中等同?我明白这对积极因素有用,但是这怎么说呢,-1?不是-1 1111 1111,而(n << 1)1111 1110? (在任何语言中形成的负片上的位移是否有效?)

尽管如此,使用公式并执行(-1 << 1) ^ (-1 >> 31),假设32位int,我得到1111 1111,这是40亿,而表认为我应该有1。

3 个答案:

答案 0 :(得分:31)

将负有符号整数向右移复制符号位,以便

(-1 >> 31) == -1

然后,

(-1 << 1) ^ (-1 >> 31) = -2 ^ -1
                       = 1

这可能更容易以二进制形式显示(这里是8位):

(-1 << 1) ^ (-1 >> 7) = 11111110 ^ 11111111
                      = 00000001

答案 1 :(得分:0)

考虑之字形映射的另一种方法是对符号和幅度表示略有不同。

在锯齿形映射中,映射的最低有效位(lsb)表示值的符号:如果它为0,则原始值为非负,如果为1,则原始值为负。

非负值只是左移一位,为lsb中的符号位腾出空间。

对于负值,您可以对数字的绝对值(幅度)执行相同的一位左移,并且只需让lsb指示符号。例如,-1可以映射到0x03或0b00000011,其中lsb表示它是负数,1的幅度左移1位。

这个符号和幅度表示的丑陋之处是“负零”,映射为0x01或0b00000001。这个零变量“消耗”了我们的一个值,并将我们可以表示的整数范围移动一个。我们可能想要特殊情况图负零到-2 ^ 63,这样我们就可以表示[-2 ^ 63,2 ^ 63]的完整64b 2的补码范围。这意味着我们使用了一个有价值的单字节编码来表示一个非常非常非常少地用于针对小幅度数量进行优化的编码的值,并且我们引入了一个特殊情况,这很糟糕。

这是zig zag在这个符号和幅度表示上发生扭曲的地方。符号位仍然在lsb中,但对于负数,我们从幅度中减去一个而不是特殊的套管负零。现在,-1映射到0x01和-2 ^ 63也具有非特殊情况表示(即 - 幅度2 ^ 63-1,左移一位,lsb /符号位设置,所有位设置为1s)

因此,考虑zig zag编码的另一种方法是它是一个更智能的符号和幅度表示:符号位存储在lsb中,1从负数的幅度中减去,并且幅度左移一位。

使用您发布的无条件逐位运算符来实现这些转换更快,而不是明确地测试符号,处理负值的特殊情况(例如 - 否定和减1或不按位),移位幅度,以及然后显式设置lsb符号位。但是,它们实际上是等效的,这个更明确的符号和数量级别的步骤可能更容易理解我们做这些事情的原因和原因。

我会警告你,虽然C / C ++中的位移位负值不可移植,应该避免。左移一个负值有未定义的行为,右移一个负值有实现定义的行为。即使左移一个正整数也可能有不确定的行为(例如 - 如果你转移到符号位,它可能会导致陷阱或更糟糕的事情)。因此,一般情况下,不要在C / C ++中对带符号类型进行位移。 “拒绝吧。”

首先转换为该类型的无符号版本,以根据标准获得安全,明确定义的结果。这意味着你不会有负值的算术移位 - 只有逻辑移位,所以你需要调整逻辑以解决这个问题。

以下是C语言中64b整数的zig zag映射的安全和可移植版本(注意算术否定):

#include <stdint.h>

uint64_t zz_map( int64_t x )
{
  return ( ( uint64_t ) x << 1 ) ^ -( ( uint64_t ) x >> 63 );
}

int64_t zz_unmap( uint64_t y )
{
  return ( int64_t ) ( ( y >> 1 ) ^ -( y & 0x1 ) );
}

答案 2 :(得分:0)

让我在讨论中加上我的两分钱。正如其他答案所指出的那样,之字形编码可以被认为是符号幅度的扭曲。这个事实可以用来实现适用于任意大小的整数的转换函数。 例如,我在我的Python项目中使用以下代码:

def zigzag(x: int) -> int:
    return x << 1 if x >= 0 else (-x - 1) << 1 | 1

def zagzig(x: int) -> int:
    assert x >= 0
    sign = x & 1
    return -(x >> 1) - 1 if sign else x >> 1

尽管Python int没有固定的位宽,但这些功能仍可正常工作;相反,它动态扩展。但是,这种方法在编译语言中可能效率低下,因为它需要条件分支。