C++ как объединить два подписанных 8-битных числа в 16-битный короткий? Необъяснимые результаты

Мне нужно объединить два подписанных 8-битных значения _int8 в подписанное короткое (16-битное) значение. Важно, чтобы знак не потерялся.

мой код:

 unsigned short lsb = -13;
 unsigned short msb = 1;
 short combined = (msb << 8 )| lsb;

результат, который я получаю, -13. Однако я ожидаю, что это будет 499.

для следующих примеров, я получаю правильные результаты с тем же кодом:

msb = -1; lsb = -6; combined = -6;

msb = 1; lsb = 89; combined = 345;

msb = -1; lsb = 13; комбинированный = -243;

однако msb = 1; lsb = -84; combined = -84; где я ожидал бы 428.

Кажется, что если lsb отрицательный, а msb положительный, что-то идет не так! Что не так с моим кодом? Как компьютер добирается до этих неожиданных результатов (Win7, 64 бит и VS2008 c++)? Большое спасибо за любую помощь!

7 ответов


ваш lsb в этом случае содержит 0xfff3. Когда вы или он с 1

попробовать short combined = (msb << 8 ) | (lsb & 0xff);


или с помощью союза:

#include <iostream>

union Combine
{
    short target;
    char dest[ sizeof( short ) ];
};

int main()
{
    Combine cc;
    cc.dest[0] = -13, cc.dest[1] = 1;
    std::cout << cc.target << std::endl;
}

возможно, что lsb автоматически расширяется до 16 бит. Я замечаю, что у вас есть проблема только тогда, когда она отрицательная, а msb положительная, и это то, что вы ожидаете, учитывая, как вы используете оператор or. Хотя, ты явно делаешь здесь что-то очень странное. Что ты на самом деле пытаешься здесь сделать?


raisonanse c complier для STM8 (и, возможно, многих других компиляторов) генерирует уродливый код для классического кода C при записи 16-битных переменных в 8-битные аппаратные регистры. Примечание-STM8 является big-endian, для мало-endian CPUs код должен быть слегка изменен. Порядок байтов чтения / записи также важен.

Итак, стандартная часть кода C:

 unsigned int ch1Sum;
...
     TIM5_CCR1H = ch1Sum >> 8; 
     TIM5_CCR1L = ch1Sum; 

компилируется в:

;TIM5_CCR1H = ch1Sum >> 8; 
         LDW   X,ch1Sum 
         CLR   A 
         RRWA  X,A 
         LD    A,XL 
         LD    TIM5_CCR1,A 
;TIM5_CCR1L = ch1Sum; 
         MOV   TIM5_CCR1+1,ch1Sum+1 

слишком долго, слишком медленно.

мой версия:

     unsigned int ch1Sum;
...
     TIM5_CCR1H = ((u8*)&ch1Sum)[0];
     TIM5_CCR1L = ch1Sum;

это скомпилировано в адекватные два хода

;TIM5_CCR1H = ((u8*)&ch1Sum)[0]; 
       MOV   TIM5_CCR1,ch1Sum 
;TIM5_CCR1L = ch1Sum;
       MOV   TIM5_CCR1+1,ch1Sum+1 

противоположном направлении:

    unsigned int uSonicRange;
...
      ((unsigned char *)&uSonicRange)[0] = TIM1_CCR2H;
      ((unsigned char *)&uSonicRange)[1] = TIM1_CCR2L;

вместо

    unsigned int uSonicRange;
...
      uSonicRange = TIM1_CCR2H << 8;
      uSonicRange |= TIM1_CCR2L;

некоторые вещи, которые вы должны знать о типах данных (un)подпись короче и char:

char является 8-битным значением, это то, что вы где ищете для lsb и msb. короче - 16 бит в длину.

вы также не должны хранить подпись значения без подписи те execpt вы знаете, что вы делаете.

вы можно взглянуть на дополнения two. Он описывает представление отрицательных значений (для целых чисел, а не для значений с плавающей точкой) в C/C++ и многих других языков программирования.

существует несколько версий создания дополнения ваших собственных двух:

int a;
// setting a
a = -a;     // Clean version. Easier to understand and read. Use this one.
a = (~a)+1; // The arithmetical version. Does the same, but takes more steps.
// Don't use the last one unless you need it!
// It can be 'optimized away' by the compiler.

stdint.h (с inttypes.h) больше для того, чтобы иметь точные длины для вашей переменной. Если вам действительно нужна переменная с определенной длиной байта вы должны использовать это (здесь вам это нужно).

вы должны everythime использовать типы данных, которые соответствуют вашим потребностям лучше всего. Поэтому ваш код должен выглядеть так:

signed char  lsb; // signed 8-bit value
signed char  msb; // signed 8-bit value
signed short combined = msb << 8  |  (lsb & 0xFF); // signed 16-bit value

или такой:

#include <stdint.h>
int8_t lsb; // signed 8-bit value
int8_t msb; // signed 8-bit value
int_16_t combined = msb << 8  |  (lsb & 0xFF); // signed 16-bit value

для последнего компилятор будет использовать подписанные 8/16-битные значения каждый раз, независимо от длины int есть на вашей платформе. Википедия получил хорошее объяснение int8_t и int16_t типы данных (и все остальные типы данных).

btw:cppreference.com полезно для поиска ANSI C стандарты и другие вещи, которые стоит знать о C/с++.


вы написали, что вам нужно объединить два 8-разрядных значений. Почему вы используете unsigned short потом? As Dan уже сказал, lsb автоматически расширяется до 16 бит. Попробуйте следующий код:

uint8_t lsb = -13;
uint8_t msb = 1;
int16_t combined = (msb << 8) | lsb;

это дает вам ожидаемый результат: 499.


Если это то, что вы хотите:

msb: 1, lsb: -13, combined: 499
msb: -6, lsb: -1, combined: -1281
msb: 1, lsb: 89, combined: 345
msb: -1, lsb: 13, combined: -243
msb: 1, lsb: -84, combined: 428

используйте этот:

short combine(unsigned char msb, unsigned char lsb) {
    return (msb<<8u)|lsb;
}

Я не понимаю, почему вы хотите, чтобы msb -6 и lsb -1 генерировали -6.