几乎没有充分的理由使用 int* 而不是 sint*。这些额外类型的存在很可能是出于历史的、向后兼容的原因,Protocol Buffers 甚至在其自己的协议版本中都试图维护它们。
我最好的猜测是,在最早的版本中,他们以 2 的补码表示形式对负整数进行了愚蠢的编码,这需要 9 个字节的最大大小的 varint 编码(不包括额外的类型字节)。然后他们坚持使用这种编码,以免破坏已经使用它的旧代码和序列化。因此,他们需要添加一种新的编码类型 sint*,以便在不破坏现有代码的情况下为负数获得更好的可变大小编码。设计师们是如何从一开始就没有意识到这个问题的,这完全超出了我的理解。
varint 编码(没有类型说明,需要多 1 个字节)可以将无符号整数值编码为以下字节数:
[0, 2^7):一个字节
[2^7, 2^14):两个字节
[2^14, 2^21):三个字节
[2^21, 2^28):四个字节
[2^28, 2^35):五个字节
[2^35, 2^42):六个字节
[2^42, 2^49):七个字节
[2^49, 2^56):八个字节
[2^56, 2^64):九个字节
如果您想类似地紧凑地编码小幅度负整数,那么您将需要“用完”一位来指示符号。您可以通过显式符号位(在某个保留位置)和幅度表示来做到这一点。或者,您可以进行 zig zag 编码,它通过将幅度左移 1 位并为负数减去 1 来有效地做同样的事情(因此最低有效位表示符号:偶数是非负的,几率是负的)。
无论哪种方式,正整数需要更多空间的切换点现在都提前了 2 倍:
[0, 2^6):一个字节
[2^6, 2^13):两个字节
[2^13, 2^20):三个字节
[2^20, 2^27):四个字节
[2^27, 2^34):五个字节
[2^34, 2^41):六个字节
[2^41, 2^48):七个字节
[2^48, 2^55):八个字节
[2^55, 2^63):九个字节
为了说明在 sint* 上使用 int* 的情况,负数必须非常罕见,但可能,和/或您期望编码的最常见的正值必须正好落在导致的切点之一附近与 int* 相比,在 sint* 中进行更大的编码(例如 - 2^6 对 2^7 导致 2x 编码大小)。
基本上,如果你要得到一些可能是负数的数字,那么默认使用 sint* 而不是 int*。int* 很少会出类拔萃,而且通常甚至不值得您花费额外的精力来判断它是否值得恕我直言。