如何在c ++中取一个字节的二进制补码

时间:2014-09-09 21:49:53

标签: c++ byte twos-complement

我正在查看一些c ++代码,我看到了:

 byte b = someByteValue;
 // take twos complement
 byte TwosComplement = -b;

此代码是否采用了b的两个补码?如果没有,它在做什么?

3 个答案:

答案 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 charsizeof(int)&gt;在图1中,byte值在否定之前被提升为int,即否定操作是用签名类型完成的。但是将得到的负值转换为无符号字节,根据定义创建了两个补码的bitpattern,并且模数运算的C ++保证和转换为无符号类型。


2&lt;补充形式的有用性来自2 n - x = 1 +((2 n - 1) - x),其中最后一个括号是全一位的模式减去 x ,即x的简单按位反转。