这是因为,* 如果 * uint8_t是可用的,它必须是一个无符号类型,正好是8位宽。转换到unsigned int是必要的,因为uint8_t肯定比int窄。如果没有转换,值将在求反之前提升到int,所以,如果你在一个非二进制补码的机器上,它将不取二进制补码。 更一般地说,这段代码计算一个 any unsigned类型的值的二进制补码(使用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);
}
4条答案
按热度按时间zpgglvta1#
stdint.h
定义uint8_t
的任何实现中,都可以计算8位二进制数的二进制补码:这是因为,* 如果 *
uint8_t
是可用的,它必须是一个无符号类型,正好是8位宽。转换到unsigned int
是必要的,因为uint8_t
肯定比int
窄。如果没有转换,值将在求反之前提升到int
,所以,如果你在一个非二进制补码的机器上,它将不取二进制补码。更一般地说,这段代码计算一个 any unsigned类型的值的二进制补码(使用C++结构来说明--一元减号的行为在两种语言中是相同的,假设没有用户定义的重载):
因为一元减号被定义为在应用于无符号类型时取二进制补码。我们仍然需要强制转换为不窄于
int
的无符号类型,但现在我们需要它至少与任何可能的T
一样宽,因此是uintmax_t
。但是,一元减号 * 不 * 一定计算类型为 signed 的值的二进制补码,因为C(和C++)仍然明确允许基于CPU的实现,这些CPU * 不 * 对有符号量使用二进制补码。据我所知,至少20年来没有制造过这样的CPU,所以继续提供它们有点愚蠢,但事实就是这样,如果你想计算一个值的二进制补码,即使它的类型碰巧是有符号的,你必须这样做:(还是C++)
例如,转换为相应的无符号类型,然后转换为
uintmax_t
,* 然后 * 应用一元减号,然后反向转换为可能有符号的类型。(需要强制转换为U,以确保值为零,而不是从其自然宽度进行符号扩展。)(If不过,如果你发现自己在这样做,那就停下来,把有问题的类型改为无符号的。你未来的自己会感谢你的。)
hmtdttj42#
正确的表达式如下
注:假定字节定义为
unsigned char
7xzttuei3#
在二进制补码上,机器求反运算计算二进制补码,是的。
在Unisys上,希望现在已经死了,埋了(但几年前仍然存在),没有签名的类型。
C和C支持二进制补码、一进制补码和有符号整数的符号和大小表示,并且只有在二进制补码的情况下,求反才能做二进制补码。
以
byte
作为无符号类型,求反加上到byte
的转换产生二进制补码位模式,而不管整数表示,因为到无符号以及无符号算术的转换是模2n,其中 n 是值表示位的数目。也就是说,用-x赋值或初始化后得到的值是2n - x,它是x的二进制补码。
这并不意味着求反运算本身必须计算二进制补码位模式。要理解这一点,请注意,如果
byte
定义为unsigned char
,并且sizeof(int)
〉1,则在求反运算之前,byte
值将提升为int
,即求反运算是使用有符号类型完成的。但将结果负值转换为无符号字节时,通过定义以及C对模运算和无符号类型转换的保证创建二进制补码位模式。2的补码形式的有用性由2n - x = 1 +((2n - 1)- x)得出,其中最后一个括号是全1位模式减去 x,即
x
的简单逐位求逆。gopyfrb34#
字节二进制数的twos_complement代码: