简体   繁体   English

C++ 中从 24 位到 32 位的有符号扩展

[英]Signed extension from 24 bit to 32 bit in C++

I have 3 unsigned bytes that are coming over the wire separately.我有 3 个无符号字节分别通过网络传输。

[byte1, byte2, byte3]

I need to convert these to a signed 32-bit value but I am not quite sure how to handle the sign of the negative values.我需要将这些转换为有符号的 32 位值,但我不太确定如何处理负值的符号。

I thought of copying the bytes to the upper 3 bytes in the int32 and then shifting everything to the right but I read this may have unexpected behavior.我想将字节复制到 int32 中的前 3 个字节,然后将所有内容向右移动,但我读到这可能会出现意外行为。

Is there an easier way to handle this?有没有更简单的方法来处理这个问题?

The representation is using two's complement.该表示使用二进制补码。

You could use: 你可以使用:

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

This works because: 这是因为:

  • if the old sign was 1, then the XOR makes it zero and the subtraction will set it and borrow through all higher bits, setting them as well. 如果旧符号为1,则XOR使其为零,减法将设置它并借用所有较高位,同时设置它们。
  • if the old sign was 0, the XOR will set it, the subtract resets it again and doesn't borrow so the upper bits stay 0. 如果旧符号为0,则XOR将设置它,减法再次将其重置并且不借用,因此高位保持为0。

Templated version 模板版

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

Assuming both representations are two's complement, simply 假设两个表示都是两个补码,简单地说

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

where 哪里

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

and upper_byte is a variable representing the missing fourth byte. upper_byte是表示缺少的第四个字节的变量。

The conversion to Signed_byte is formally implementation-dependent, but a two's complement implementation doesn't have a choice, really. 转换为Signed_byte是正式依赖于实现的,但实际上两个补码实现没有选择权。

You can use a 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
}

Easy and no undefined behavior invoked 调用简单且没有未定义的行为

int32_t r = sign_extend_to_32<24>(your_3byte_array);

Of course copying the bytes to the upper 3 bytes in the int32 and then shifting everything to the right as you thought is also a good idea. 当然, 将字节复制到int32中的高3字节,然后按照您的想法将所有内容移到右侧也是个好主意。 There's no undefined behavior if you use memcpy like above. 如果您使用上面的memcpy则没有未定义的行为。 An alternative is reinterpret_cast in C++ and union in C, which can avoid the use of memcpy . 另一种方法是使用C ++中的reinterpret_cast和C中的union,这可以避免使用memcpy However there's an implementation defined behavior because right shift is not always a sign-extension shift (although almost all modern compilers do that) 然而,有一个实现定义的行为,因为右移并不总是符号扩展转换(尽管几乎所有现代编译器都这样做)

You could let the compiler process itself the sign extension. 您可以让编译器自己处理符号扩展。 Assuming that the lowest significant byte is byte1 and the high significant byte is byte3; 假设最低有效字节是byte1而高有效字节是byte3;

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

I have used C style cast here when static_cast would have been more C++ish, but as an old dinosaur (and Java programmer) I find C style cast more readable for integer conversions. static_cast更多是C ++ ish时,我在这里使用了C样式,但作为一个古老的恐龙(和Java程序员),我发现C样式对于整数转换更具可读性。

Here's a method that works for any bit count, even if it's not a multiple of 8. This assumes you've already assembled the 3 bytes into an integer value . 这是一个适用于任何位数的方法,即使它不是8的倍数。这假设您已经将3个字节组合成一个整value

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

This is a pretty old question, but I recently had to do the same (while dealing with 24-bit audio samples), and wrote my own solution for it. 这是一个非常古老的问题,但我最近不得不这样做(在处理24位音频样本时),并为此编写了我自己的解决方案。 It's using a similar principle as this answer, but more generic, and potentially generates better code after compiling. 它使用与答案类似的原则,但更通用,并且可能在编译后生成更好的代码。

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;
    }
}

This has no hard-coded math, it simply lets the compiler do the work and figure out the best way to sign-extend the number. 这没有硬编码数学,它只是让编译器完成工作并找出签名扩展数字的最佳方法。 With certain widths, this can even generate a native sign-extension instruction in the assembly, such as MOVSX on x86. 使用某些宽度,甚至可以在程序集中生成本机符号扩展指令,例如x86上的MOVSX

This function assumes you copied your N-bit number into the lower N bits of the type you want to extend it to. 此函数假定您将N位数字复制到要将其扩展到的类型的低N位。 So for example: 例如:

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

Of course it works for any number of bits, extending it to the full width of the type that contained the data. 当然它适用于任意数量的位,将其扩展到包含数据的类型的整个宽度。

假设您的 24 位值存储在变量 int32_t val 中,您可以通过以下方式轻松扩展符号:

val = (val << 8) >> 8;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM