унарный оператор в avr: неопределенное поведение?

#c #avr #avr-gcc #unary-operator #8-bit

#c #avr #avr-gcc #унарный оператор #8-разрядный

Вопрос:

У меня была проблема с тем, что

voltage = voltage*2/3; и voltage *= 2/3;

дали разные результаты. Переменная находится uint16_t и работает на 8-битном микроконтроллере AVR. Первый оператор дал правильный результат, второй оператор всегда возвращал 0.

Некоторые мои друзья сказали мне, что унарные операторы вообще не следует использовать, что заставило меня задуматься, так как я также использую такие вещи, как PORTC amp;= ~(1 lt;lt; csBit); . Для компиляции я использую avr-gcc, если это может дать вам представление.

Заранее спасибо за вашу помощь

правка№1:

Хорошо, я понял, что = не является унарным оператором. Также основное различие заключается в том, что «‘=»‘ начинается справа, а»»*, / «» начинается слева.

Я предполагаю, что для uints оба утверждения неверны, и мне пришлось бы написать voltage = (uint16_t) ((плавающее)напряжение*(плавающее)2/3)

и спасибо @Lundin за то, что указал, как правильно реагировать на ответы

Комментарии:

1. voltage *= 2/3; эквивалентно voltage = voltage * (2/3) . Это *= не унарный оператор, это двоичный оператор, принадлежащий к группе операторов сложного назначения .

2. Кстати 1 lt;lt; ... , это почти всегда ошибка на 8 и 16-разрядных микроконтроллерах, потому что вы можете в конечном итоге перенести данные в знаковый бит int типа, созданного 1 . Никогда не выполняйте побитовую арифметику для операндов со знаком, замените 1 на 1u .

3. Термин для операторов присваивания, таких как *= или = , является составным присвоением , в отличие от оператора прямого присвоения = .

Ответ №1:

voltage = voltage*2/3 умножает voltage на 2, делит на 3 и сохраняет результат voltage .

voltage *= 2/3 делит 2 на 3, умножает результат на voltage и сохраняет результат этого в voltage . Целочисленное деление усекается, поэтому 2/3 получается ноль.

Ни один из них не является унарным оператором.

Ответ №2:

Вас укусила комбинация различных порядков операций и целочисленной арифметики.

Арифметические операторы являются левоассоциативными, поэтому

 voltage = voltage * 2 / 3;  

анализируется как

 voltage = (voltage * 2) / 3;  

вы делите результат voltage * 2 на 3 , в то время как

 voltage *= 2 / 3;  

эквивалентно

 voltage = voltage * (2 / 3);  

вы умножаете voltage на результат 2/3 , который есть 0 .

Проблема не столько в *= том , проблема в том, что

 (a * b) / c != a * (b / c)  

Ответ №3:

разница в том, что при напряжении = напряжение * 2/3 напряжение умножается на 2, а результат делится на 3:

 voltage = 5  5 * 2 = 10  10 / 3 = 3  

в то время как в напряжении * = 2/3, так как вы используете uint16_t, и поэтому десятичные дроби усекаются, сначала выполняется 2/3, а результат умножается на напряжение:

 votage = 5  2 / 3 = 0  voltage = 5 * 0 = 0  

чтобы избежать этого, вам следует, например, выполнить расчет с плавающей запятой до того, как он будет присвоен напряжению, например, добавив где-нибудь «.0».:

 voltage = voltage * 2.0 / 3 = 3  voltage *= 2.0 / 3 = 3  

Комментарии:

1. Использование плавающей запятой и деления на 8-битном AVR-очень плохой совет, так как у него нет FPU и он будет выдавать крайне плохой машинный код из ваших примеров. Просто придерживайтесь правильной версии кода. Если требуется более высокая точность, это должно быть решено с помощью арифметики с фиксированной точкой.

2. Конкретный пример voltage *= 2.0 / 3 = 3 неверен — он сгенерирует более точный нецелочисленный результат (3,333…). Не уверен, чего хотел ОП; вероятно, не этого. Но идея в любом случае хороша (для кого-то другого, кроме ОП).

3. а, ладно. тогда просто напряжение формы *= 2/3; его нельзя использовать.

4. @anatolyg: это неправильно. как только результат будет присвоен напряжению, десятичные дроби будут усечены (автоматическое приведение).

5. @jkbs1337: напряжение = (uint16_t)((плавающее)напряжение*(плавающее)2/3); эти изменения не нужны, достаточно формы: напряжение = напряжение * 2,0 / 3; (по крайней мере, это для gcc).