反转 18 位 int 的二进制补码

reversing two's complement for 18bit int

我有一个 18 位整数,它是二进制补码,我想将它转换为有符号数,以便更好地使用它。在我使用的平台上,整数是 4 个字节(即 32 位)。基于此 post:

我尝试了以下方法来转换数字:

using SomeType = uint64_t;
SomeType largeNum = 0x32020e6ed2006400;
int twosCompNum = (largeNum & 0x3FFFF);
int regularNum = (int) ((twosCompNum << 14) / 8192);

我将数字左移 14 位以获得符号位作为最高有效位,然后除以 8192(在二进制中,它是 1,后跟 13 个零)以恢复大小(如 [=20 中所述) =] 以上)。但是,这似乎对我不起作用。例如,输入 249344 得到 -25600,表面上看似乎不正确。我做错了什么?

两个问题:首先你的测试输入不是一个18位的二进制补码。对于 n 位,二进制补码允许 -(2 ^ (n - 1)) <= value < 2 ^ (n - 1)。在 18 位的情况下,即 -131072 <= value < 131071。你说你输入的 249344 超出了这个范围,实际上会被解释为 -12800.

第二个问题是你的二的幂是关闭的。在您引用的答案中,提供的解决方案的形式为

mBitOutput = (mBitCast)(nBitInput << (m - n)) / (1 << (m - n));

对于您的特定问题,您希望

int output = (nBitInput << (32 - 18)) / (1 << (32 - 18));
// or equivalent
int output = (nBitInput << 14) / 16384;

试试这个。

常量8192错误,应该是16384 = (1<<14).

int regularNum = (twosCompNum << 14) / (1<<14);

至此,答案正确,-12800

这是正确的,因为输入的(无符号)数字是 249344 (0x3CE00)。它设置了最高位,因此它是一个负数。我们可以通过从中减去 "max unsigned value+1" 来计算它的有符号值:0x3CE00-0x40000=-12800.

请注意,如果您在一个平台上,右符号移位做正确的事情(比如在 x86 上),那么您可以避免除法:

int regularNum = (twosCompNum << 14) >> 14;

如果编译器没有注意到除法可以完全替换为移位(clang 7 注意到,但 gcc 8 没有),这个版本可能会稍微快一些(但具有实现定义的行为)。

几乎可移植的方法(假设负整数本身是 2s 补码)是简单地检查第 17 位,并使用它有条件地屏蔽符号位:

constexpr SomeType sign_bits = ~SomeType{} << 18;
int regularNum = twosCompNum & 1<<17 ? twosCompNum | sign_bits : twosCompNum;

请注意,这并不取决于您的 int 类型的大小。