📓 Archive

ZIGZAG

FGJ: Create:2024/10/23 Update: [2024-12-09]

  • Intro(ZigZag-encoding) #

    • 定义描述 #

      正如 varint 编码 中所述,它对于负数来说显得手足无措,心有余而力不足。zigzag 编码 就是用来弥补 varint 的缺陷的。它会将负数映射成正值(但是不需要额外的映射表),比如:(0 = 0, -1 = 1, 1 = 2, -2 = 3, 2 = 4, -3 = 5, 3 = 6 ...),从而能够使用 varint 继续编码。zigzag 编码的大致原理就是采用异或操作,将阻碍压缩的 1 进行消除。

      没有特别说明的都已 32 bit 进行阐述。

      异或操作

      异或运算(XOR | ^),相同为 0,不同为 1。并且异或操作有如下特性:
      1). 任何数(0,1)与 1 进行异或的结果相当于取反,比如 0 ^ 1 = 1, 1 ^ 1 = 0。
      2). 任何数(0,1)与 0 进行异或的结果相当于不变,比如 0 ^ 0 = 0, 1 ^ 0 = 1。

    • 编码过程 #

      过程

      1). 先对值 v(32bit) 左移一位,用来在最低有效位(LSB)保留正负标志位。
      2). 再将值 v(32bit) 的高有效位(MSB),从左至右,扯出一长串 0 或 1,对于正数来说,就是 32 个 0, 对于负数来说就是 32 个 1。
      3). 最后将上述两个值进行异或,得到 zigzag 编码的结果。

      需要注意的是:
      a). 对于正数来说,左移相当于将 v 乘 2 了。因为后面的被异或的值全是 32 个 0,结合异或特性2,就是没有任何作用,所以只是单纯的 *2 了。对于负数说来,那就得慢慢说了。
      b). 负数之所以会被压缩正是因为将负值多余的 1 与 32 个 1 进行异或后变成了可压缩的 0。
      c). 编码后的值最低位保留的是符号位,正零负一。根据这个标志位才可以还原(解码)当前编码对应的实际值。
      d). 解码过程逆向操作就行。

    • 举例演示 #

      • 取值表格 #

        原始值Bin(编码值)Dex(编码值)Dex(解码值)delimiter原始值Bin(编码值)Dex(编码值)Dex(解码值)
        -100000000000000000000000000001001119-1000000000000000000000000000000000000
        -90000000000000000000000000001000117-910000000000000000000000000000001021
        -80000000000000000000000000000111115-820000000000000000000000000000010042
        -70000000000000000000000000000110113-730000000000000000000000000000011063
        -60000000000000000000000000000101111-640000000000000000000000000000100084
        -5000000000000000000000000000010019-5500000000000000000000000000001010105
        -4000000000000000000000000000001117-4600000000000000000000000000001100126
        -3000000000000000000000000000001015-3700000000000000000000000000001110147
        -2000000000000000000000000000000113-2800000000000000000000000000010000168
        -1000000000000000000000000000000011-1900000000000000000000000000010010189
      • python工具编解码 #

    • 编码实现 #

      参考 ZigZag encoding/decoding explained
      protobuf 中使用的逻辑基本一致,但是里面包含了 varint 编码逻辑。如: 32bit 编码32bit 解码。不太好测,方便起见,直接使用 python 的如下实现:

      def zigzag_encode(i):
          # 对应编码过程中的 2 和 1。
          return (i >> 31) ^ (i << 1)
      
      def zigzag_decode(i):
          # 这个稍微有点不太一样,一个一个来。
          # 首先是后面的 -(i & 1) ,就是取当前编码的最低位,正零负一,对应的值也为 0 或者 1。-(0) 当 0 就行,也就是编码过程中的那一长传 0。 -(1) 就是编码过程中的那一长串 1。
          # 前面半部份 (i >> 1),正常逆向的时候都会理解成将当前编码值先与长串 0 或 1 异或后再右移还原。但是此处是先移位然后异或的。之所以这样可以,是跟异或的值有关系,要么全是 0,要么全是 1。
          #    其实完全可以写成 return (i ^ -(i & 1)) >> 1
          return (i >> 1) ^ -(i & 1)
      
      def print_zigzag_results():
          # 打印表头
          print(f"{'Original':>8} | {'Encoded Binary':>16} | {'Encoded Decimal':>16} | {'Decoded':>8}")
          print("-" * 65)
      
          for i in range(-10, 11):  # 从 -10 到 10 的范围
              encoded = zigzag_encode(i)
              decoded = zigzag_decode(encoded)
      
              # 将编码后的值转换为二进制字符串(去掉 '0b' 前缀),并填充到至少32位
              encoded_binary = format(encoded, '032b')
      
              # 打印结果,包括编码后的十进制值
              print(f"{i:>8} | {encoded_binary:>16} | {encoded:>16} | {decoded:>8}")
      
      if __name__ == '__main__':
          print_zigzag_results()
      
  • Reference #


comments powered by Disqus