按位运算和转换
我很难理解这段代码的工作方式和原因。 我在这个任务中的合作伙伴完成了这一部分,我无法得到他,以了解它的工作原理和原因。 我已经尝试了一些不同的东西来理解它,但任何帮助将非常感激。 此代码使用2的补码和32位表示。
/* * fitsBits - return 1 if x can be represented as an * n-bit, two's complement integer. * 1 <= n <= 32 * Examples: fitsBits(5,3) = 0, fitsBits(-4,3) = 1 * Legal ops: ! ~ & ^ | + <> * Max ops: 15 * Rating: 2 */ int fitsBits(int x, int n) { int r, c; c = 33 + ~n; r = !(((x <>c)^x); return r; }
c = 33 + ~n;
这计算在使用n
低阶位之后剩余的高阶位数。
((x << c)>>c
这将使用与x
的符号位相同的值填充高阶位。
!(blah ^ x)
这相当于
blah == x
-
在2的补码平台上,
-n
相当于~n + 1
。 因此,在这样的平台上c = 33 + ~n
实际上相当于c = 32 - n
。 该c
旨在表示如果n
较低位被占用,则在32位int
值中保留多少个高阶位。注意此代码中存在两个平台依赖:2的补充平台,32位
int
类型。 -
然后
((x << c) >> c
用于对那些c
高阶位进行符号填充 。符号填充意味着x
在位位n - 1
中具有0
那些值,这些高阶位必须但是对于那些在位置n - 1
中有1
的x
值,这些高阶位必须用1
s填充。这对于使代码适用于x
负值非常重要。这引入了另外两个平台依赖:
<<
运算符在移动负值时表现良好,或者当1
移位到符号位时(forms上是未定义的行为)和>>
运算符在移位负值时执行符号扩展(正式)它是实现定义的) -
如上所述,其余的只是与
x
的原始值进行比较:!(a ^ b)
相当于a == b
。 如果上述变换没有破坏x
的原始值,则x
确实适合2的补码表示的n
低位。
在带符号整数上使用按位补码(一元~
)运算符具有实现定义和未定义的方面 。 换句话说,即使您只考虑两个补码实现,此代码也不可移植。
值得注意的是,即使C中的两个补码表示也可能有陷阱表示。 6.2.6.2p2甚至清楚地说明了这一点:
如果符号位为1,则应以下列方式之一修改该值:
– 符号位0的相应值被否定(符号和幅度);
– 符号位的值为 – (2 M)(二进制补码);
– 符号位的值为 – (2 M – 1)(补码)。
这些适用中的哪一个是实现定义的, 就像符号位1和所有值位零(前两个) ,或符号位和所有值位1(对于补码)一样, 是否为陷阱表示或正常值。
重点是我的。 使用陷阱表示是未定义的行为 。
有些实际的实现将该值保留为默认模式下的陷阱表示 。 值得注意的是OS2200上的Unisys Clearpath Dordado(转到2-29) 。 请注意该文件的日期; 这样的实现不一定是古老的(因此我引用这个)。
根据6.2.6.2p4 ,左移的负值也是未定义的行为。 我还没有对现实中的行为进行过大量的研究,但我会合理地期望可能存在签名扩展的实现,以及没有实现的实现。 这也是形成上述陷阱表示的一种方式,其本质上是不确定的并且因此是不期望的。 从理论上讲(或者可能是在遥远或不远的将来的某个时间),您可能还会面临“对应于计算exception”的信号(这是与SIGSEGV
类似的C标准类别,对应于“分裂”之类的事情通过零“)或其他不稳定和/或不良行为……
总之,问题中的代码工作的唯一原因是巧合的是,您的实现所做出的决策恰好以正确的方式对齐。 如果您使用我列出的实现,您可能会发现此代码对某些值的预期效果不正常。
这种沉重的魔法 (正如评论中所描述的那样)并不是真的必要,而且对我来说并不是那么理想。 如果你想要一些不依赖于魔法的东西(例如可移动的东西)来解决这个问题,可以考虑使用它(实际上,这段代码至少可以用于1 <= n <= 64
):
#include int fits_bits(intmax_t x, unsigned int n) { uintmax_t min = 1ULL << (n - 1), max = min - 1; return (x < 0) * min + x <= max; }