考虑 zig zag 映射的另一种方式是,它是对符号和幅度表示的轻微扭曲。
在 zig zag 映射中,映射的最低有效位 (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,而不是特殊的大小写负零。现在,-1 映射到 0x01 并且 -2^63 也具有非特殊情况表示(即 - 幅度 2^63 - 1,左移一位,设置 lsb / 符号位,所有位都设置为 1) .
因此,考虑 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 ) );
}