Как 2 байта могут создать отрицательное число?
Я пытаюсь использовать LoRAWAN и отправлять значения температуры меньшим количеством байт. Я нашёл этот способ и протестировал его. Он работает отлично: отрицательные и положительные числа можно отправлять двумя байтами, а не «float» (4 байта) или «double» (8 байтов), но я всё ещё не могу понять, как этот код создаёт отрицательные числа типа int celciusInt = (bytes[0] & 0x80 ? 0xFFFF0000 : 0) | bytes[0] << 8 | bytes[1];. В результате для отрицательных чисел получается 4 байта, но сеть TTN всё равно воспринимает это как два байта, и это просто невероятно.
ТЕСТОВЫЙ код:
byte bytes[2];
int derece = -3556;
void setup() {
// put your setup code here, to run once:
Serial.begin(115200);
delay(3000);
}
void loop() {
// put your main code here, to run repeatedly:
bytes[0] = derece >> 8;
bytes[1] = derece & 0xFF;
int celciusInt = (bytes[0] & 0x80 ? 0xFFFF0000 : 0) | bytes[0] << 8 | bytes[1];
Serial.println(celciusInt);
delay(3000);
}
ВЫХОД:
-3556
В результате число -3556 возвращает значение 0xFFFFF0E4, и как это приводит к отрицательным числам, я до сих пор не понимаю. Сколько раз я читал chatgpt и т.п., но так и не понял.
Можете ли вы объяснить мне это так, как вы бы объяснили это пятилетнему ребенку?
@mehmet, 👍-1
Обсуждение2 ответа
Лучший ответ:
Я не буду объяснять это так, как будто тебе 5 лет, потому что это заняло бы много времени. много усилий, ты взрослый и умеешь читать статьи себя. Так что начните с того, что отложите ChatGPT и вместо этого прочтите Статья в Википедии Дополнительный код. В смысле, читайте внимательно, уделяя столько времени, сколько нужно для достижения полного понимания.
Теперь число −3556 можно записать в двоичной системе как
11110010.00011100
или
11111111.11111111.11110010.00011100
в зависимости от того, использует ли ваш Arduino 16-битные или 32-битные целые числа (это
различается в зависимости от платы Arduino). Назначение bytes[0] и
bytes[1] извлекает два наименее значимых байта этого числа:
bytes[0] = 11110010 (in binary)
bytes[1] = 00011100 (in binary)
Присваивание celciusInt восстанавливает число путем объединения его с помощью операции ИЛИ
три термина:
11111111.11111111.00000000.00000000 = (bytes[0] & 0x80 ? 0xFFFF0000 : 0)
00000000.00000000.11110010.00000000 = bytes[0] << 8
00000000.00000000.00000000.00011100 = bytes[1]
───────────────────────────────────
11111111.11111111.11110010.00011100 → assigned to celciusInt
Обратите внимание, что первый член в расширении знака (вы знаете, что это такое, потому что вы прочитали статью, о которой я упомянул, не так ли?). Это всего лишь Это необходимо, если вы хотите получить результат в виде 32-битного целого числа. Это было бы проще. отказаться от расширения знака и вместо этого сохранить результат в 16-битном формате целое число:
int16_t celciusInt = uint16_t(bytes[0]) << 8 | bytes[1];
Обратите внимание, что код может работать даже без приведения bytes[0] к
uint16_t: это приведение необходимо только для предотвращения переполнения знакового целого числа,
что в C++ является неопределенным поведением.
Редактировать: Обратите внимание, что еще более простой способ разделить и реконструировать
Число – это использовать объединение. Это позволяет получить 16-битное целое число и
Массив из двух байтов использует одну и ту же память. Вы можете записать данные в
целое число и прочитать байты или наоборот:
// 16-битное целое число и пара байтов, совместно использующих свою память.
union {
int16_t value;
uint8_t bytes[2];
} packet;
// Разделить 16-битное целое число на два байта.
packet.value = -3556;
Serial.println(packet.bytes[0]); // -> 28
Serial.println(packet.bytes[1]); // -> 242
// Восстановить 16-битное целое число из его байтов.
packet.bytes[0] = 214;
packet.bytes[1] = 255;
Serial.println(packet.value); // -> -42
Остерегайтесь порядка байтов: в отличие от предыдущего кода, bytes[0]
Вот младший байт. Практически все Arduino и
Современные компьютеры используют систему с прямым порядком байтов, так что это может не быть проблемой.
Однако существуют некоторые протоколы связи, которые используют принцип «старший байт», поэтому
Вы должны знать об этой проблеме.
Re: int16_t celciusInt = uint16_t(bytes[0]) << 8 | bytes[1]; Даже при приведении типа все еще существует вероятность переполнения, это просто переполнение, которое происходит при *сохранении* результата в int16_t celciusInt, а не при вычислении результата побитового ИЛИ., @timemage
@timemage: Нет. Сохранение значения, выходящего за пределы диапазона, в целом числе технически не считается переполнением и совершенно допустимо. На самом деле, это [определялось реализацией до C++20](https://en.cppreference.com/w/cpp/language/implicit_conversion#Integral_conversions), но GCC, как и любой нормальный компилятор, делает [единственно разумное решение](https://gcc.gnu.org/onlinedocs/gcc/Integers-implementation.html)., @Edgar Bonet
@EdgarBonet теперь это имеет смысл, @jsotola
@EdgarBonet, похоже, я просто отнес его к категории «непереносимых». Но приятно, что они закрепили общее поведение в новом стандарте., @timemage
@EdgarBonet Думаю, union не работает в JavaScript. Например, я отправляю данные с Arduino в сеть Lorawan (TTN), там есть форматировщик полезной нагрузки. Что бы я ни пробовал, union не определён. Поэтому я использую для преобразования в TTN var celciusInt = uint16_t(bytes[16]) << 8 | bytes[15];, @mehmet
@mehmet: Если вы хотите декодировать двоичный пакет в JavaScript, вам подойдёт [DataView](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView). Он позволяет извлекать числа из любого стандартного типа C (uint8_t, int16_t...) с произвольного смещения в буфере с нужным вам порядком байтов. Например, let response = await fetch(data_url); let buffer = await response.arrayBuffer(); let view = new DataView(buffer); let celciusInt = view.getInt16(15, true);, @Edgar Bonet
Подозреваю, вы перепутали с порядком байтов. Я видел, как многие люди ошибаются, когда имеют дело с Lora.
Для связи с Lora данные отправляются в формате Big Endian, в то время как Arduino (и почти все современные компьютеры) хранят данные в формате Little Endian.
Таким образом, если вы отправляете целое число 7410 (что в шестнадцатеричном формате равно 0x1cf2) через Lora, первым отправляется 0x1c, а затем 0xf2. Однако при сохранении в Arduino, если вы сохраняете данные в соответствии с последовательностью передачи, вы в конечном итоге сохраните 0x1c как первый байт в data[0], а 0xfc — как второй байт в data[1]. Таким образом, при извлечении из Arduino в режиме Little Endian данные будут выглядеть как 0xf21c, поскольку 0xf21c — это двойное дополнение числа -3556 в знаковом целом числе!
- Использование массивов, двоичных данных и битового чтения
- Изменение одного бита в байтовом массиве
- Lora SX1278 через короткое время перестает принимать
- Код застрял после инициализации с помощью RFM69
- Использование библиотеки Arduino LMIC с возможностью прерывания
- Последовательный порт не читает буфер с целочисленными символами больше 43
- Как использовать SPI на Arduino?
- Как решить проблему «avrdude: stk500_recv(): programmer is not responding»?
как бы вы закодировали отрицательные числа? Например, знак в первом бите?, @Juraj
На самом деле компьютеры не различают числа, только биты (старшие и младшие). Затем на процессоре можно добавлять некоторые арифметические операции, но с хорошо известной битовой кодировкой (и это зависит от данных). Целые числа (со знаком) можно кодировать с помощью знакового бита, дополнения до единицы, дополнения до двойки (и, конечно же, другими методами). У чисел с плавающей точкой совершенно другая кодировка, а двоично-десятичный код (в настоящее время встречается редко) — это просто смесь. На YouTube Бен Итан рассказывает об основах электроники вплоть до создания простого процессора (думаю, в одном-двух видео также рассказывается об арифметике и кодировке). Серия очень длинная (но вы получите всё, ничего скрытого)., @Giacomo Catenazzi
все еще поражает, если вы думаете: «Как 2 байта могут представлять отрицательное число?», @jsotola
@jsotola Всё ещё читаю ;) Читаю задание, которое мне дал Эдгар Бонет. Если число знаковое, первый бит указывает, отрицательное оно или положительное. Если число беззнаковое, первый бит считается обычным числом. Максимальное значение для int16_t — 32767, а для uint16_t — 65535. Короче говоря, число не становится отрицательным; оно становится положительным или отрицательным в зависимости от того, как мы определяем тип числа., @mehmet
... Продолжаем. Например, число -55 в двоичной системе счисления равно 11001001, но когда мы вводим это двоичное значение в калькулятор, он выводит 201. Почему? Потому что калькулятор всегда предполагает, что число положительное., @mehmet
это может помочь ... https://www.rapidtables.com/convert/number/hex-to-decimal.html?x=8000 .... https://www.rapidtables.com/convert/number/hex-to-decimal.html?x=FF00, @jsotola