C ++ Как объединить два 8-битных числа со знаком в короткую 16-битную?Необъяснимые результаты - PullRequest
11 голосов
/ 24 ноября 2011

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

Мой код:

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

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

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

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

msb = 1;lsb = 89;комбинированный = 345;

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

Однако, msb = 1;lsb = -84;комбинированный = -84;где я ожидал бы 428.

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

Ответы [ 7 ]

22 голосов
/ 24 ноября 2011

Ваш lsb в этом случае содержит 0xfff3. Когда вы ИЛИ с 1 << 8, ничего не меняется, потому что в этой битовой позиции уже есть 1. </p>

Попробуйте short combined = (msb << 8 ) | (lsb & 0xff);

7 голосов
/ 24 ноября 2011

Или используя объединение:

#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;
}
2 голосов
/ 24 ноября 2011

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

1 голос
/ 31 мая 2014

Некоторые сведения о типах данных ( un ) со знаком short и char :

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

Вы также не должны хранить значения со знаком в без знака значения, за исключением того, что вы знаете, что делаете.

Вы можете взглянуть на дополнение к двум . Он описывает представление отрицательных значений (для целых чисел, а не для значений с плавающей запятой) в 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) больше для точной длины переменной. Если вам действительно нужна переменная определенной длины в байтах, вы должны использовать ее (здесь она вам нужна).

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

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 / C ++.

1 голос
/ 16 марта 2013

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

Итак, стандартный фрагмент кода 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;

Это компилируется в адекватные два MOVE

;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;
0 голосов
/ 24 ноября 2011

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

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.

0 голосов
/ 24 ноября 2011

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

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

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

...