【问题标题】:Zig Zag Decoding锯齿形解码
【发布时间】:2010-02-05 22:32:11
【问题描述】:

在 google 协议缓冲区encoding overview 中,他们引入了一种称为“Zig Zag Encoding”的东西,它采用小幅度的有符号数字,并创建一系列小幅度的无符号数字。

例如

Encoded => Plain
0 => 0
1 => -1
2 => 1
3 => -2
4 => 2
5 => -3
6 => 3

等等。他们为此提供的编码功能相当巧妙,它是:

(n << 1) ^ (n >> 31) //for a 32 bit integer

我了解它的工作原理,但是,我终其一生都无法弄清楚如何将其反转并将其解码回有符号的 32 位整数

【问题讨论】:

    标签: language-agnostic bit-manipulation protocol-buffers bitfoo zigzag-encoding


    【解决方案1】:

    试试这个:

    (n >> 1) ^ (-(n & 1))
    

    编辑:

    我正在发布一些示例代码以供验证:

    #include <stdio.h>
    
    int main()
    {
      unsigned int n;
      int r;
    
      for(n = 0; n < 10; n++) {
        r = (n >> 1) ^ (-(n & 1));
        printf("%u => %d\n", n, r);
      }
    
      return 0;
    }
    

    我得到以下结果:

    0 => 0
    1 => -1
    2 => 1
    3 => -2
    4 => 2
    5 => -3
    6 => 3
    7 => -4
    8 => 4
    9 => -5
    

    【讨论】:

    • 我知道必须有办法绕过乘法。赞一个!
    • 嗯,它适用于我和 ergosys,所以它也应该适用于你......你能告诉我你得到了什么结果吗?
    • 很可能是我用错了。我有一个用于 n 的 UInt32,然后我将返回的结果转换为 Int32。这听起来像是一种合乎逻辑的做事方式......
    • 我认为这可能是一个语言问题,几乎直接将其翻译成 C# 会导致错误,否定 UInt32 会导致 long,long 和 UInt32 的 xor 未定义。我将尝试为 C# 修复它
    • return ((int)(u >> 1)) ^ ((int)(-(u & 1)));铸造的力量已经解决了它。那么,问题仍然存在,我应该使用哪个,从 ergosys 上面所说的,我会假设由于缺乏乘法,这会更快?
    【解决方案2】:

    怎么样

    (n>>1) - (n&1)*n
    

    【讨论】:

      【解决方案3】:

      这里还有另一种方法,只是为了解释目的(您显然应该使用 3lectrologos 的单线)。

      您只需要注意您与一个全 1(相当于按位非)或全 0(相当于什么都不做)的数字进行异或。这就是 (-(n &amp; 1)) 产生的结果,或者 google 的“算术移位”评论所解释的结果。

      int zigzag_to_signed(unsigned int zigzag)
      {
          int abs = (int) (zigzag >> 1);
      
          if (zigzag % 2)
              return ~abs;
          else
              return abs;
      }
      
      unsigned int signed_to_zigzag(int signed)
      {
          unsigned int abs = (unsigned int) signed << 1;
      
          if (signed < 0)
              return ~abs;
          else
              return abs;
      }
      

      所以为了在最重要的位置有很多 0,zigzag 编码使用 LSB 作为符号位,其他位作为绝对值(实际上仅适用于正整数,而绝对值 -1 表示负数由于到 2 的补码表示)。

      【讨论】:

      • zigZag_to_signed 没有返回原始值。
      • @SalarKhalilzadeh 谢谢,已修复。运算符优先级错误,先转换后移位丢失了“zigzag”的第一位。
      • 与 3lectrologos 答案的不同之处在于你使用了一个测试。测试~禁用流水线操作,比没有测试和分支的计算慢。
      • @chmike 是的,这个答案是为了说明会发生什么,而不是为了性能,正如第一行所承认的那样。
      • 谢谢。这个版本可以很容易直观地看出编码方案为何有效。
      【解决方案4】:

      在摆弄 3lectrologos 提出的公认答案后,我无法在以 unsigned longs 开头(在 C# 中 -- 编译器错误)时让它工作。我想出了类似的东西:

      ( value >> 1 ) ^ ( ~( value & 1 ) + 1 )
      

      这适用于任何在 2 的补码中表示负数的语言(例如 .NET)。

      【讨论】:

        【解决方案5】:

        我找到了解决办法,可惜不是我所希望的单线美:

        uint signMask = u << 31;
        int iSign = *((Int32*)&signMask);
        iSign >>= 31;
        signMask = *((UInt32*)&iSign);
        
        UInt32 a = (u >> 1) ^ signMask;
        return *((Int32*)&a);
        

        【讨论】:

          【解决方案6】:

          我确信有一些超高效的按位运算可以更快地执行此操作,但功能很简单。这是一个python实现:

          def decode(n):
            if (n < 0):
              return (2 * abs(n)) - 1
            else:
              return 2 * n
          
          >>> [decode(n) for n in [0,-1,1,-2,2,-3,3,-4,4]]
          [0, 1, 2, 3, 4, 5, 6, 7, 8]
          

          【讨论】:

          • 谢谢,但不幸的是,这是在游戏的网络编码系统中,这个特殊的解码功能在每个数据包中使用了很多次,每秒很多次 - 它必须快速
          • 您可以使用一个简单的位操作来加快速度。移动 1 乘以 2。
          • -1:此函数将带符号的数字编码为编码的无符号数字。原始问题已经具有执行此操作的功能。最初的问题要求一个函数将那些无符号数解码回原来的有符号数:我们希望 decode(3) 返回 -2,但这个函数使 decode(3) 返回 6。跨度>
          猜你喜欢
          • 1970-01-01
          • 2011-05-16
          • 2019-02-23
          • 1970-01-01
          • 2011-11-05
          • 1970-01-01
          • 2014-07-14
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多