按位运算和转换

我很难理解这段代码的工作方式和原因。 我在这个任务中的合作伙伴完成了这一部分,我无法得到他,以了解它的工作原理和原因。 我已经尝试了一些不同的东西来理解它,但任何帮助将非常感激。 此代码使用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中有1x值,这些高阶位必须用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; }