Это должно быть педантично правильным и работать также на платформах, которые используют знак бита или 1 дополнения представления вместо обычного 2 дополнения . Предполагается, что входные байты находятся в дополнении 2.
int le16_to_cpu_signed(const uint8_t data[static 2]) {
unsigned value = data[0] | ((unsigned)data[1] << 8);
if (value & 0x8000)
return -(int)(~value) - 1;
else
return value;
}
Из-за ветвления это будет дороже, чем другие варианты.
Это позволяет избежать любых предположений о том, как представление int
связано с представлением unsigned
на платформе. Приведение к int
требуется для сохранения арифметического значения c для любого числа, которое будет соответствовать целевому типу. Поскольку инверсия гарантирует, что старший бит 16-разрядного числа будет равен нулю, значение будет соответствовать. Тогда унарный -
и вычитание 1 применяют обычное правило для отрицания дополнения 2. В зависимости от платформы, INT16_MIN
может по-прежнему переполняться, если не соответствует типу int
на цели, и в этом случае следует использовать long
.
Разница с исходной версией в вопрос приходит во время возврата. В то время как оригинал всегда вычитал 0x10000
и дополнение 2, позволяющее переполнению со знаком, обернуть его в диапазон int16_t
, эта версия имеет явный if
, который позволяет избежать перетаскивания со знаком (то есть undefined ).
Сейчас на практике почти все платформы, используемые сегодня, используют представление дополнения 2. Фактически, если у платформы есть стандартное соответствие stdint.h
, которое определяет int32_t
, она должна использовать дополнение 2 для этого. Иногда этот подход оказывается полезным при использовании некоторых языков сценариев, которые вообще не имеют целочисленных типов данных - вы можете изменить операции, показанные выше для чисел с плавающей запятой, и это даст правильный результат.