【问题标题】:Signed extension from 24 bit to 32 bit in C++C++ 中从 24 位到 32 位的有符号扩展
【发布时间】:2017-07-20 23:00:28
【问题描述】:

我有 3 个无符号字节分别通过网络传输。

[byte1, byte2, byte3]

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

我想将字节复制到 int32 中的高 3 个字节,然后将所有内容向右移动,但我读到这可能会出现意外行为。

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

表示使用二进制补码。

【问题讨论】:

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


    【解决方案1】:

    假设两种表示都是二进制补码,简单地说

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

    在哪里

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

    upper_byte 是一个代表缺失第四个字节的变量。

    Signed_byte 的转换在形式上是依赖于实现的,但实际上,二进制补码实现没有选择。

    【讨论】:

      【解决方案2】:

      您可以使用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,因为右移并不总是符号扩展移位(尽管几乎所有现代编译器都这样做)

      【讨论】:

      • 将一个值放在一个如此小的位字段中,以至于提取的值不相等,这肯定是实现定义的行为。我还是喜欢这个。 :)
      • 你如何编译这个?我收到一些“错误:请求的位域地址”。如果我在 memcpy 中删除 .i24 就可以了,也许这就是你的意思?
      • @harold 是的。这是在没有编译的情况下组成的
      【解决方案3】:

      您可以让编译器自行处理符号扩展。假设最低有效字节为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++ 时,我在这里使用了 C 样式转换,但作为一个老恐龙(和 Java 程序员),我发现 C 样式转换对于整数转换更具可读性。

      【讨论】:

        【解决方案4】:

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

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

        【讨论】:

        • 为什么这么复杂?你可以只用(value ^ m) - mm = 1 &lt;&lt; (bits - 1)
        • @harold 如果您认为自己有更好的答案,请继续自己回答问题。我很难说服自己它有效,但如果有效,你会得到我的 +1。
        • 很公平,我只是觉得可能是有原因的
        【解决方案5】:

        你可以使用:

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

        【讨论】:

        • 以这种方式进行位旋转的另一个好处是您不受限于 32 位 int - 例如,它在 64 位 int 上也能正常工作。我会将类型更改为模板参数,并将bits 也设为函数参数。
        • @MarkRansom 好点,这大概是您的意思吗?
        • 我需要一个签名的 32 不是未签名的
        • @Beto 你可以在这里使用带符号的类型,至少我认为它没有办法破坏(除非bits 是不合理的)。使其余代码更加危险。
        • 完美。我喜欢您将 m 分配分成两部分的方式,以确保转换发生在正确的类型上。
        【解决方案6】:

        这是一个相当古老的问题,但我最近不得不这样做(在处理 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);
        

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

        【讨论】:

          【解决方案7】:

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

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2019-05-23
            • 2020-04-08
            • 2012-09-30
            • 2011-01-17
            • 2011-04-28
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多