我正在查看一些c ++代码,我看到了:
byte b = someByteValue;
// take twos complement
byte TwosComplement = -b;
此代码是否采用了b的两个补码?如果没有,它在做什么?
答案 0 :(得分:11)
这个代码肯定会在stdint.h
定义uint8_t
的任何实现上计算8位二进制数的二进制补码:
#include <stdint.h>
uint8_t twos_complement(uint8_t val)
{
return -(unsigned int)val;
}
这是因为,如果 uint8_t
可用,它必须是一个正好是8位宽的无符号类型。转换为unsigned int
是必要的,因为uint8_t
肯定比int
更窄。如果没有转换,该值将在被否定之前提升为int
,因此,如果您使用的是非二进制补码机器,则不会使用二进制补码。
更一般地说,这段代码使用任何无符号类型计算值的二进制补码(使用C ++结构进行说明 - 一元减号的行为在两种语言中都相同,假设没有用户 - 定义的重载):
#include <cstdint>
#include <type_traits>
template <typename T>
T twos_complement(T val,
// "allow this template to be instantiated only for unsigned types"
typename std::enable_if<std::is_unsigned<T>::value>::type* = 0)
{
return -std::uintmax_t(val);
}
因为一元减号被定义为在应用于无符号类型时采用二进制补码。我们仍然需要强制转换为不比int
窄的无符号类型,但现在我们需要它至少与任何可能T
一样宽,因此uintmax_t
。
但是,一元减去不必然会计算类型为 signed 的值的二进制补码,因为C(和C ++)仍然明确允许基于CPU的实现不对签名数量使用二进制补码。据我所知,至少20年内没有制造过这样的CPU,所以继续为它们提供的服务有点傻,但确实如此。如果你想计算一个值的二进制补码,即使它的类型恰好是有符号的,你必须这样做:(再次使用C ++)
#include <type_traits>
template <typename T>
T twos_complement(T val)
{
typedef std::make_unsigned<T>::type U;
return T(-uintmax_t(U(val)));
}
即。转换为相应的无符号类型,然后转到uintmax_t
,然后应用一元减号,然后反向转换为可能签名的类型。 (对U的强制转换必须确保该值为零,而不是从其自然宽度进行符号扩展。)
(如果您发现自己这样做了,请停止并将相关类型更改为无符号。未来的自我会感谢您。)
答案 1 :(得分:6)
正确的表达式将是
byte TwosComplement = ~b + 1;
注意:前提是字节i定义为unsigned char
答案 2 :(得分:2)
在补丁机上,否定计算两个补码,是的。
关于Unisys的东西 - 希望现在已经死了并埋葬了(但几年前仍然存在),不是签名类型。
C和C ++支持两个补码,一个有符号整数的补码和符号和幅度表示,只有两个补码才能做出否定的二进制补码
将byte
作为无符号类型否定加上转换为byte
会产生二进制补码位模式,而不管整数表示,因为无符号和无符号算术的转换是模2 < sup> n 其中 n 是值表示位的数量。
也就是说,在使用-x分配或初始化之后得到的值是2 n - x这是x的两个补码。
这并不意味着否定本身必然会计算两个补码位模式。要理解这一点,请注意byte
定义为unsigned char
,sizeof(int)
&gt;在图1中,byte
值在否定之前被提升为int
,即否定操作是用签名类型完成的。但是将得到的负值转换为无符号字节,根据定义创建了两个补码的bitpattern,并且模数运算的C ++保证和转换为无符号类型。
2&lt;补充形式的有用性来自2 n - x = 1 +((2 n - 1) - x),其中最后一个括号是全一位的模式减去 x ,即x
的简单按位反转。