簡體   English   中英

C++ 中從 24 位到 32 位的有符號擴展

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

我有 3 個無符號字節分別通過網絡傳輸。

[byte1, byte2, byte3]

我需要將這些轉換為有符號的 32 位值,但我不太確定如何處理負值的符號。

我想將字節復制到 int32 中的前 3 個字節,然后將所有內容向右移動,但我讀到這可能會出現意外行為。

有沒有更簡單的方法來處理這個問題?

該表示使用二進制補碼。

你可以使用:

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

假設兩個表示都是兩個補碼,簡單地說

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

哪里

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

upper_byte是表示缺少的第四個字節的變量。

轉換為Signed_byte是正式依賴於實現的,但實際上兩個補碼實現沒有選擇權。

您可以使用位域

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 然而,有一個實現定義的行為,因為右移並不總是符號擴展轉換(盡管幾乎所有現代編譯器都這樣做)

您可以讓編譯器自己處理符號擴展。 假設最低有效字節是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

static_cast更多是C ++ ish時,我在這里使用了C樣式,但作為一個古老的恐龍(和Java程序員),我發現C樣式對於整數轉換更具可讀性。

這是一個適用於任何位數的方法,即使它不是8的倍數。這假設您已經將3個字節組合成一個整value

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

這是一個非常古老的問題,但我最近不得不這樣做(在處理24位音頻樣本時),並為此編寫了我自己的解決方案。 它使用與答案類似的原則,但更通用,並且可能在編譯后生成更好的代碼。

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

當然它適用於任意數量的位,將其擴展到包含數據的類型的整個寬度。

假設您的 24 位值存儲在變量 int32_t val 中,您可以通過以下方式輕松擴展符號:

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM