在C ++中签名从24位扩展到32位

时间:2017-03-01 14:47:05

标签: c++ bit-manipulation bit-shift sign-extension

我有3个无符号字节分别来自电线。

{{1}}

我需要将这些值转换为带符号的32位值,但我不太确定如何处理负值的符号。

我想把字节复制到int32中的高3字节,然后将所有内容移到右边,但我读到这可能会有意想不到的行为。

有没有更简单的方法来处理这个问题?

该表示使用了两个补码。

6 个答案:

答案 0 :(得分:7)

您可以使用:

uint32_t sign_extend_24_32(uint32_t x) {
    const int bits = 24;
    uint32_t m = 1u << (bits - 1);
    return (x ^ m) - m;
}

这是因为:

  • 如果旧符号为1,则XOR将其设为零,减法将设置它并借用所有较高位,同时设置它们。
  • 如果旧符号为0,则XOR将设置它,减法再次重置它并且不借用,所以高位保持为0.

模板版

template<class T>
T sign_extend(T x, const int bits) {
    T m = 1;
    m <<= bits - 1;
    return (x ^ m) - m;
}

答案 1 :(得分:2)

假设两个表示都是两个补码,只需

upper_byte = (Signed_byte(incoming_msb) >= 0? 0 : Byte(-1));

,其中

using Signed_byte = signed char;
using Byte = unsigned char;

upper_byte是表示缺少的第四个字节的变量。

转换为Signed_byte是正式依赖于实现的,但实际上没有一个选项可以做出选择。

答案 2 :(得分:0)

您可以使用bitfield

template<size_t L>
inline int32_t sign_extend_to_32(const char *x)
{
  struct {int32_t i: L;} s;
  memcpy(&s, x, 3);
  return s.i;
  // or
  return s.i = (x[2] << 16) | (x[1] << 8) | x[0]; // assume little endian
}

轻松且未调用任何未定义的行为

int32_t r = sign_extend_to_32<24>(your_3byte_array);

当然将字节复制到int32中的高3字节然后将所有内容移到右边也是个好主意。如果您使用上述memcpy,则没有未定义的行为。另一种选择是C ++中的reinterpret_cast和C中的union,这可以避免使用memcpy。然而,有一个implementation defined behavior,因为右移并不总是符号扩展转换(尽管几乎所有现代编译器都这样做)

答案 3 :(得分:0)

您可以让编译器自己处理符号扩展。假设最低有效字节是byte1而高有效字节是byte3;

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch(item.getItemId()) {
        case R.id.searchicon: 
            // ...
            return true;
        case R.id.listicon:
            // ...
            return true;
        case R.id.gridicon:
            // ...
            return true;
    }

    return super.onOptionsItemSelected(item);
}

int val = (signed char) byte3; // C guarantees the sign extension val << 16; // shift the byte at its definitive place val |= ((int) (unsigned char) byte2) << 8; // place the second byte val |= ((int) (unsigned char) byte1; // and the least significant one 更多是C ++ ish时,我使用了C样式转换,但作为一个古老的恐龙(和Java程序员),我发现C样式对整数转换更具可读性。

答案 4 :(得分:0)

这是一个适用于任何位数的方法,即使它不是8的倍数。这假设您已经将3个字节组合成一个整数value

const int bits = 24;
int mask = (1 << bits) - 1;
bool is_negative = (value & ~(mask >> 1)) != 0;
value |= -is_negative & ~mask;

答案 5 :(得分:0)

这是一个很老的问题,但是我最近不得不做同样的事情(在处理24位音频样本时),并为此编写了自己的解决方案。它使用与this答案类似的原理,但更为通用,并且在编译后可能会生成更好的代码。

template <size_t Bits, typename T>
inline constexpr T sign_extend(const T& v) noexcept {
    static_assert(std::is_integral<T>::value, "T is not integral");
    static_assert((sizeof(T) * 8u) >= Bits, "T is smaller than the specified width");
    if constexpr ((sizeof(T) * 8u) == Bits) return v;
    else {
        using S = struct { signed Val : Bits; };
        return reinterpret_cast<const S*>(&v)->Val;
    }
}

它没有硬编码的数学运算,只是让编译器完成工作并找出对数字进行符号扩展的最佳方法。具有一定的宽度,这甚至可以在程序集中生成本机符号扩展指令,例如x86上的MOVSX

此功能假定您将N位数字复制到要扩展其类型的低N位。例如:

int16_t a = -42;
int32_t b{};
memcpy(&b, &a, sizeof(a));
b = sign_extend<16>(b);

当然,它适用于任意数量的位,并将其扩展到包含数据的类型的整个宽度。