Математические операции на Arduino
Одной из основных функций микроконтроллера является выполнение вычислений, как с числами напрямую, так и со значениями переменных. Начнём погружение в мир математики с самых простых действий:
- = присваивание
- % остаток от деления
- * умножение
- / деление
- + сложение
- – вычитание
Рассмотрим простой пример:
По поводу последних двух строчек из примера, когда переменная участвует в расчёте своего собственного значения: существуют также составные операторы, укорачивающие запись:
- += составное сложение: a += 10 равносильно a = a + 10
- -= составное вычитание: a -= 10 равносильно a = a — 10
- *= составное умножение: a *= 10 равносильно a = a * 10
- /= составное деление: a /= 10 равносильно a = a / 10
- %= прибавить остаток от деления: a %= 10 равносильно a = a + a % 10
С их использованием можно сократить запись последних двух строчек из предыдущего примера:
Очень часто в программировании используется прибавление или вычитание единицы, для чего тоже есть короткая запись:
- ++ (плюс плюс) инкремент: a++ равносильно a = a + 1
- — (минус минус) декремент: a— равносильно a = a — 1
Порядок записи инкремента играет очень большую роль: пост-инкремент var++ возвращает значение переменной var до выполнения этого инкремента. Операция пре-инкремента ++var возвращает значение уже изменённой переменной. Пример:
Как говорилось в предыдущем уроке – переменные желательно инициализировать, иначе они могут иметь случайное значение и в математических операциях получится непредсказуемый результат. Если переменная на момент начала выполнения программы, или после вызова функции (локальная переменная) должна иметь значение 0 – инициализируйте её как 0!
Порядок вычислений
Порядок вычисления выражений подчиняется обычным математическим правилам: сначала выполняются действия в скобках, затем умножение и деление, и в конце – сложение и вычитание
Скорость вычислений
Производимые вычисления занимают у процессора некоторое время, оно зависит от типа действия и от типа данных, с которым действие производится. Нужно понимать, что не все во всех случаях действия тратят столько времени, сколько будет рассказано дальше: компилятор старается по возможности оптимизировать вычисления, как он это делает можно попробовать поискать в интернете. Оптимизированные вычисления занимают ничтожно мало времени по сравнению с не оптимизированными. Сложно сказать, будет ли оптимизировано отдельно взятое вычисление в вашем коде, поэтому нужно всегда готовиться к худшему и знать, как лучше делать. А именно:
- Arduino (на AVR) не имеет “хардверной” поддержки вычислений с плавающей точкой (float), и эти вычисления производятся при помощи отдельных инструментов и занимают гораздо больше времени, чем с целочисленными типами
- Чем “массивнее” тип данных, тем дольше производятся вычисления, т.е. действия с 1-байтными переменными производятся быстрее, чем с 4-х байтными
- Деление (и поиск остатка от деления) производится отдельными инструментами (как операции с float), поэтому эта операция занимает больше времени, чем сложение/вычитание/умножение. Для оптимизации скорости вычислений есть смысл заменять деление умножением на обратное число (даже на float)
Резюмируя всё вышесказанное хочу показать вам вот такую табличку, в которой показано время не оптимизированных компилятором вычислений разных типов данных, время указано в микросекундах (мкс) для тактовой частоты 16 МГц. Операция деление также соответствует операции “остаток от деления”, %:
Тип данных | Время выполнения, мкс | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Сложение и вычитание | Умножение | Деление, остаток | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
i nt8_t | 0.44 | 0.625 | 14.25 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
uint8_t | 0.44 | 0.625 | 5.38 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
int16_t | 0.89 | 1.375 | 14.25 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
uint16_t | 0.89 | 1.375 | 13.12 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
int32_t | 1.75 | 6.06 | 38.3 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
uint32_t | 1.75 | 6.06 | 37.5 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
float | 8.125 | 10 | 31.5 Эта информация дана чисто для ознакомления и париться о скорости вычислений не нужно, т.к. большинство из них будут оптимизированы. Когда могут возникнуть проблемы с недостатком скорости вычисления? Я столкнулся с этим только один раз в проекте “LED кубик“, где Ардуина вычисляла закон движения пары десятков точек по наклонной плоскости по подробной физической модели. Вот там да, я заметил, как микросекунды вычислений превращались в миллисекунды. Для простых проектов без тысяч вычислений, честно, не парьтесь =) Переполнение переменнойРаз мы начали говорить о действиях, увеличивающих или уменьшающих значение переменной, стоит задуматься и о том, что будет с переменной, если её значение выйдет из допустимого диапазона? Тут всё весьма просто: при переполнении в бОльшую сторону из нового большого значения отсекается максимальное значение переменной, и у неё остаётся только остаток. Для сравнения представим переменную как ведро. Будем считать, что при наливании воды и переполнении ведра мы скажем стоп, выльем из него всю воду, и дольём остаток. Вот так и с переменной, что останется – то останется. Если переполнение будет несколько раз – несколько раз опорожним наше “ведро” и всё равно оставим остаток. Ещё один хороший пример – кружка Пифагора. При переполнении в обратную сторону, т.е. в минус, выливаем воду, будем считать, что ведро полностью заполнилось. Да, именно так =) Посмотрим пример: Особенность больших вычисленийДля сложения и вычитания по умолчанию используется ячейка long (4 байта), но при умножении и делении используется int (2 байта), что может привести к непредсказуемым результатам! Если при умножении чисел результат превышает 32’768, он будет посчитан некорректно. Для исправления ситуации нужно писать (тип данных) перед умножением, что заставит МК выделить дополнительную память для вычисления (например (long)35 * 1000 ). Также существую модификаторы, делающие примерно то же самое.
Посмотрим, как это работает на практике: Особенность работы с floatArduino поддерживает работу с числами с плавающей точкой (десятичные дроби). Этот тип данных не имеет аппаратной поддержки, а реализован программно, поэтому вычисления с ним производятся в несколько раз дольше, чем с целочисленным типом, вы могли видеть это из таблицы выше. Помимо медленных вычислений, поддержка работы с float занимает память, т.к. она реализована в виде “библиотеки”. Использование математических операций с float ( * / + – ) добавляет примерно 1000 байт во flash память, однократно, просто подключается инструмент для выполнения действий. Arduino поддерживает три типа ввода чисел с плавающей точкой:
С вычислениями есть такая особенность: если в выражении нет float чисел, то вычисления будут иметь целый результат (дробная часть отсекается). Для получения правильного результата нужно писать преобразование (float) перед действием, использовать float числа или float переменные. Также есть модификатор f , который можно применять только к цифрам float . Смысла в нём нет, но такую запись можно встретить. Смотрим: При присваивании float числа целочисленному типу данных дробная часть отсекается. Если хотите математическое округление – его нужно использовать отдельно: Следующий важный момент: из за особенности самой модели “чисел с плавающей точкой” – вычисления иногда производятся с небольшой погрешностью. Смотрите (значения выведены через порт): Казалось бы, val2 должна стать ровно 0.1 после вычитания, но в 8-ом знаке вылезла погрешность! Будьте очень внимательны при сравнении float чисел, особенно со строгими операциями == , >= и : результат может быть некорректным и нелогичным. Список математических функцийМатематических функций Arduino поддерживает очень много, малая часть из них являются макро функциями, идущими в комплекте с Arduino.h, все остальные же наследуются из мощной C++ библиотеки math.h Математические функции из math.h
Ардуино — функции
Математические константы
Видео Подписаться авторизуйтесь 0 Комментарий Старые |