简体   繁体   English

C++ 24 位到 32 位转换

[英]24-bit to 32-bit conversion in C++

I need to convert a 24-bit integer (2s compliment) to 32-bit integer in C++.我需要在 C++ 中将 24 位整数(2s 补码)转换为 32 位整数。 I have found a solution here , which is given as在这里找到了一个解决方案,如下所示

int interpret24bitAsInt32(unsigned char* byteArray)
 {     
    return (  
        (byteArray[0] << 24)
    |   (byteArray[1] << 16)
    |   (byteArray[2] << 8)
    ) >> 8;  
}

Though I found it is working, I have the following concern about the piece of code.虽然我发现它正在工作,但我对这段代码有以下担忧。 byteArray[0] is only 8-bits, and hence how the operations like byteArray[0] << 24 will be possible? byteArray[0]只有 8 位,因此像byteArray[0] << 24这样的操作将如何可能? It will be possible if the compiler up-converts the byteArray to an integer and does the operation.如果编译器将 byteArray 上转换为整数并执行操作,则有可能。 This may be the reason it is working now.这可能是它现在工作的原因。 But my question is whether this behaviour is guaranteed in all compilers and explicitly mentioned in the standard?但我的问题是这种行为是否在所有编译器中都得到保证并在标准中明确提到? It is not trivial to me as we are not explicitly giving the compiler any clue that the target is a 32-bit integer!这对我来说并不重要,因为我们没有明确地向编译器提供目标是 32 位整数的任何线索!

Also, please let me know any improvisation like vectorization is possible to improve the speed (may be using C++11), as I need to convert huge amount of 24-bit data to 32-bit.另外,请让我知道任何像矢量化这样的即兴创作都可以提高速度(可能使用 C++11),因为我需要将大量 24 位数据转换为 32 位。

int32_t interpret24bitAsInt32(unsigned char* byteArray)
{     
    int32_t number =
        (((int32_t)byteArray[0]) << 16)
    |   (((int32_t)byteArray[1]) << 8)
    |   byteArray[2];
    if (number >= ((int32_t)1) << 23)
        //return (uint32_t)number | 0xFF000000u;
        return number - 16777216;
    return number;
}

this function should do what you want without invoking undefined behavior by shifting a 1 into the sign bit of int .此函数应该通过将1移入int的符号位来执行您想要的操作,而不会调用未定义的行为。
The int32_t cast is only necessary if sizeof(int) < 4 , otherwise the default integer promotion to int happens.仅当sizeof(int) < 4时才需要int32_t ,否则会发生默认的整数提升为int

If someone does not like the if : It does not get translated to a conditional jump by the compiler (gcc 9.2): https://godbolt.org/z/JDnJM2如果有人不喜欢if :它不会被编译器(gcc 9.2)翻译成条件跳转: https : //godbolt.org/z/JDnJM2
It leaves a cmovg .它留下了一个cmovg

Integral promotions [conv.prom] are performed on the operands of a shift expression [expr.shift]/1 .积分提升[conv.prom]在移位表达式[expr.shift]/1的操作数上执行。 In your case, that means that your values of type unsigned char will be converted to type int before << is applied [conv.prom]/1 .在您的情况下,这意味着您的unsigned char类型值将在应用<< [conv.prom]/1之前转换为int类型。 Thus, the C++ standard guarantees that the operands be "up-converted".因此,C++ 标准保证操作数被“向上转换”。

However, the standard only guarantees that int has at least 16 Bit.但是,标准只保证int至少有 16 位。 There is also no guarantee that unsigned char has exactly 8 Bit (it may have more).也不能保证unsigned char正好有 8 位(它可能有更多)。 Thus, it is not guaranteed that int is always large enough to represent the result of these left shifts.因此,不能保证int总是足够大来表示这些左移的结果。 If int does not happen to be large enough, the resulting signed integer overflow will invoke undefined behavior [expr]/4 .如果int碰巧不够大,则产生的有符号整数溢出将调用未定义的行为[expr]/4 Chances are that int has 32 Bit on your target platform and, thus, everything works out in the end.有可能int在您的目标平台上有 32 位,因此,最后一切正常。

If you need to work with a guaranteed, fixed number of Bits, I would generally recommend to use fixed-width integer types , for example:如果您需要使用有保证的固定位数,我通常建议使用固定宽度的整数类型,例如:

std::int32_t interpret24bitAsInt32(const std::uint8_t* byteArray)
{     
    return
        static_cast<std::int32_t>(
            (std::uint32_t(byteArray[0]) << 24) | 
            (std::uint32_t(byteArray[1]) << 16) | 
            (std::uint32_t(byteArray[2]) <<  8)
        ) >> 8;
}

Note that right shift of a negative value is currently implementation-defined [expr.shift]/3 .请注意,负值的右移当前是实现定义的[expr.shift]/3 Thus, it is not strictly guaranteed that this code will end up performing sign extension on a negative number.因此,不能严格保证此代码最终会对负数执行符号扩展。 However, your compiler is required to document what exactly right-shifting a negative integer does [defns.impl.defined] (ie, you can go and make sure it does what you need).但是,您的编译器需要记录[defns.impl.defined]右移负整数究竟做了什么(即,您可以去确保它满足您的需要)。 And I have never heard of a compiler that does not implement right shift of a negative value as an arithmetic shift in practice.而且我从未听说过编译器在实践中不会将负值右移作为算术移位来实现。 Also, it looks like C++20 is going to mandate arithmetic shift behavior…此外,看起来C++20 将强制要求算术移位行为......

[expr.shift]/1 The operands shall be of integral or unscoped enumeration type and integral promotions are performed. [expr.shift]/1操作数应为整数或无作用域枚举类型,并执行整数提升。 The type of the result is that of the promoted left operand...结果的类型是提升的左操作数的类型...

[conv.prom] 7.6 Integral promotions [conv.prom] 7.6 积分促销

1 A prvalue of an integer type other than bool , char16_t , char32_t , or wchar_t whose integer conversion rank (7.15) is less than the rank of int can be converted to a prvalue of type int if int can represent all the values of the source type; 1boolchar16_tchar32_twchar_t之外的整数类型的纯右bool ,其整数转换等级 (7.15) 小于int的等级,如果 int 可以表示源的所有值,则可以将其转换为int类型的纯右值类型; otherwise, the source prvalue can be converted to a prvalue of type unsigned int .否则,源纯右值可以转换为unsigned int类型的纯右值。

So yes, the standard requires that an argument of a shift operator, that has the type unsigned char , be promoted to int before the evaluation.所以是的,标准要求在评估之前将具有unsigned char类型的移位运算符的参数提升为int


That said, the technique in your code relies on int a) being 32 bits large, and b) using two's-complement to represent negative values.也就是说,您代码中的技术依赖于int a) 是 32 位大,并且 b) 使用二进制补码来表示负值。 Neither of which is guaranteed by the standard, though it's common with modern systems.尽管这在现代系统中很常见,但标准都不能保证这两者。

A version without branch;一个没有分支的版本; but multiplication:但乘法:

int32_t interpret24bitAsInt32(unsigned char* bytes) {
  unsigned char msb = UINT8_C(0xFF) * (bytes[0] >> UINT8_C(7));
  uint32_t number =
        (msb << UINT32_C(24))
      | (bytes[0] << UINT32_C(16)))
      | (bytes[1] << UINT32_C(8)))
      |  bytes[2];
  return number;
}

You need to test if omitting the branch really gives you a performance advantage, though!不过,您需要测试省略分支是否真的能给您带来性能优势!

Adapted from older code of me which did this for 10 bit numbers.改编自我的旧代码,该代码针对 10 位数字执行此操作。 Test before use!使用前测试!

Oh, and it still relies upon implementation defined behaviour with regards to the conversion uint32_t to int32_t .哦,它仍然依赖于实现定义的关于uint32_tint32_t转换的行为。 If you want to go down that rabbit hole, have fun but be warned .如果你想进入那个兔子洞,玩得开心,但要注意

Or, much more simple: Use the trick from mchs answer .或者,更简单:使用mchs answer 中的技巧。 And also use shifts instead of multiplication:并且还使用移位而不是乘法:

int32_t interpret24bitAsInt32(unsigned char* bytes) {
  int32_t const number =
        (bytes[0] << INT32_C(16))
      | (bytes[1] << INT32_C(8))
      |  bytes[2];
  int32_t const correction = 
     (bytes[0] >> UINT8_C(7)) << INT32_C(24);
  return number - correction;
}

Test case测试用例

There is indeed Integral_promotion for type smaller than int for operator_arithmetic对于operator_arithmetic来说,确实有小于int类型的Integral_promotion

So assuming sizeof(char) < sizeof(int)所以假设sizeof(char) < sizeof(int)

in

byteArray[0] << 24

byteArray is promoted in int and you do bit-shift on int . byteArray在促进int和你做位移位int

First issue is that int can only be 16 bits.第一个问题是int只能是 16 位。

Second issue (before C++20), int is signed , and Bitwise shift can easily lead to implementation-defined or UB (And you have both for negative 24 bits numbers).第二个问题(在 C++20 之前), intsigned ,并且按位移位很容易导致实现定义或 UB (并且对于负 24 位数字,您都有)。

In C++20, behavior of Bitwise shift has been simplified (behavior defined) and the problematic UB has been removed too.在 C++20 中,Bitwise shift 的行为已被简化(行为定义)并且有问题的 UB 也已被删除。

The leading 1 of negative number are kept in neg >> 8 .负数的前导1保留在neg >> 8

So before C++20, you have to do something like:所以在 C++20 之前,你必须做一些类似的事情:

std::int32_t interpret24bitAsInt32(const unsigned char* byteArray)
{
    const std::int32_t res =
        (std::int32_t(byteArray[0]) << 16)
      | (byteArray[1] << 8)
      | byteArray[2];
    const std::int32_t int24Max = (std::int32_t(1) << 24) - 1;
    return res <= int24Max ?
               res : // Positive 24 bit numbers
               int24Max - res; // Negative number
}

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

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