Математические операции
Математика
Одной из основных функций микроконтроллера является выполнение вычислений, как с числами напрямую, так и со значениями переменных. Начнём погружение в мир математики с самых простых действий:
-
=присваивание
-
+сложение
-
-вычитание
-
*умножение
-
/деление
-
%остаток от деления
Рассмотрим простой пример:
По поводу последних двух строчек из примера, когда переменная участвует в расчёте своего собственного значения: существуют также составные операторы, укорачивающие запись:
-
+=составное сложение:a += 10равносильноa = a + 10
-
-=составное вычитание:a -= 10равносильноa = a - 10
-
*=составное умножение:a *= 10равносильноa = a * 10
-
/=составное деление:a /= 10равносильноa = a / 10
-
%=остаток от деления:a %= 10равносильноa = a % 10
С их использованием можно сократить запись последних двух строчек из предыдущего примера:
Очень часто в программировании используется прибавление или вычитание единицы, для чего тоже есть короткая запись:
-
++(плюс плюс) инкремент:a++равносильноa = a + 1
-
--(минус минус) декремент:a--равносильноa = a - 1
Порядок записи инкремента играет очень большую роль: пост-инкремент
Как говорилось в предыдущем уроке - локальные переменные нужно инициализировать, иначе в математических операциях получится непредсказуемый результат.
Порядок вычислений
Порядок вычисления выражений подчиняется обычным математическим правилам: сначала выполняются действия в скобках, затем умножение и деление, и в конце - сложение и вычитание.
Скорость вычислений
Математические вычисления выполняются процессором некоторое время, оно зависит от типа данных и типа операции. Вот время выполнения (в микросекундах) не оптимизированных компилятором вычислений для Arduino Nano 16 МГц:
Тип данных | Время выполнения, мкс | ||
Сложение и вычитание | Умножение | Деление, остаток | |
int8_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 |
- Нужно понимать, что не все во всех случаях математические операции занимают ровно столько времени, так как компилятор их оптимизирует. Можно помочь ему в этом, подробнее читайте в уроке по оптимизации кода.
- Операции с
floatвыполняются гораздо дольше целочисленных, потому что в AVR нет аппаратной поддержки чисел с плавающей точкой и она реализована программно как сложная библиотека. В некоторых микроконтроллерах есть FPU - специальный аппаратный блок для вычислений сfloat.
- Операции целочисленного деления на AVR выполняются дольше по той же причине - они реализованы программно, а вот умножение и сложение с вычитанием МК делает аппаратно и очень быстро.
Целочисленное деление
При целочисленном делении результат не округляется по "математическим" правилам, дробная часть просто отсекается, фактически это округление вниз: и
Для округления по обычным математическим правилам можно использовать функцию
Переполнение переменной
Вспомним предыдущий урок о типах данных: что будет с переменной, если её значение выйдет из допустимого диапазона? Тут всё весьма просто: при переполнении в бОльшую сторону из нового значения вычитается максимальное значение переменной, и у неё остаётся только остаток. Для сравнения представим переменную как ведро. Будем считать, что при наливании воды и заполнении ведра мы скажем стоп, выльем из него всю воду, а затем дольём остаток. Вот так и с переменной, что останется - то останется. Если переполнение будет несколько раз - несколько раз опорожним наше "ведро" и всё равно оставим остаток. Ещё один хороший пример - кружка Пифагора.
При переполнении в обратную сторону (выливаем воду из ведра), будем считать, что ведро полностью заполнилось. Да, именно так =) Посмотрим пример:
Особенность больших вычислений
Для сложения и вычитания по умолчанию используется ячейка 4 байта (
Для цифр существуют модификаторы, делающие то же самое:
-
Uилиu- перевод вuint16_t(от 0 до 65'535). Пример:36000u
-
Lилиl- перевод вint32_t(-2 147 483 648… 2 147 483 647). Пример:325646L
-
ULилиul- перевод вuint32_t(от 0 до 4 294 967 295). Пример:361341ul
Посмотрим, как это работает на практике:
Особенности float
Помимо медленных вычислений, поддержка работы с
С вычислениями есть такая особенность: если в выражении нет
При присваивании
Следующий важный момент: из за особенности самой модели "чисел с плавающей точкой" - вычисления иногда производятся с небольшой погрешностью. Смотрите (значения выведены через порт):
Казалось бы,
Список математических функций
Математических функций в Arduino довольно много, часть из них являются макросами, идущими в библиотеке Arduino.h, все остальные же наследуются из мощной C++ библиотеки math.h
[su_spoiler title="Математические функции из math.h" open="no" style="fancy" icon="arrow"]
Функция | Описание |
cos (x)
|
Косинус (радианы) |
sin (x)
|
Синус (радианы) |
tan (x)
|
Тангенс (радианы) |
fabs (x)
|
Модуль для float чисел |
fmod (x, y)
|
Остаток деления x на у для float |
modf (x, *iptr)
|
Возвращает дробную часть, целую хранит по адресу iptr http://cppstudio.com/post/1137/ |
modff (x, *iptr)
|
То же самое, но для float |
sqrt (x)
|
Корень квадратный |
sqrtf (x)
|
Корень квадратный для float чисел |
cbrt (x)
|
Кубический корень |
hypot (x, y)
|
Гипотенуза ( корень(x*x + y*y) ) |
square (x)
|
Квадрат ( x*x ) |
floor (x)
|
Округление до целого вниз |
ceil (x)
|
Округление до целого вверх |
frexp (x, *pexp)
|
http://cppstudio.com/post/1121/ |
ldexp (x, exp)
|
x*2^exp http://cppstudio.com/post/1125/ |
exp (x)
|
Экспонента (e^x) |
cosh (x)
|
Косинус гиперболический (радианы) |
sinh (x)
|
Синус гиперболический (радианы) |
tanh (x)
|
Тангенс гиперболический (радианы) |
acos (x)
|
Арккосинус (радианы) |
asin (x)
|
Арксинус (радианы) |
atan (x)
|
Арктангенс (радианы) |
atan2 (y, x)
|
Арктангенс (y / x) (позволяет найти квадрант, в котором находится точка) |
log (x)
|
Натуральный логарифм х ( ln(x) ) |
log10 (x)
|
Десятичный логарифм x ( log_10 x) |
pow (x, y)
|
Степень ( x^y ) |
isnan (x)
|
Проверка на nan (1 да, 0 нет) |
isinf (x)
|
Возвр. 1 если x +бесконечность, 0 если нет |
isfinite (x)
|
Возвращает ненулевое значение только в том случае, если аргумент имеет конечное значение |
copysign (x, y)
|
Возвращает x со знаком y (знак имеется в виду + -) |
signbit (x)
|
Возвращает ненулевое значение только в том случае, если _X имеет отрицательное значение |
fdim (x, y)
|
Возвращает разницу между x и y, если x больше y, в противном случае 0 |
fma (x, y, z)
|
Возвращает x*y + z |
fmax (x, y)
|
Возвращает большее из чисел |
fmin (x, y)
|
Возвращает меньшее из чисел |
trunc (x)
|
Возвращает целую часть числа с дробной точкой |
round (x)
|
Математическое округление |
lround (x)
|
Математическое округление (для больших чисел) |
lrint (x)
|
Округляет указанное значение с плавающей запятой до ближайшего целого значения, используя текущий режим округления и направление |
[/su_spoiler] [su_spoiler title="Arduino - функции" open="no" style="fancy" icon="arrow"]
Функция | Значение |
min(a, b)
|
Возвращает меньшее из чисел
a
и
b
|
max(a, b)
|
Возвращает большее из чисел |
abs(x)
|
Модуль числа |
constrain(val, min, max)
|
Ограничить диапазон числа
val
между
min
и
max
|
map(val, min, max, newMin, newMax)
|
Перевести диапазон числа
val
(от
min
до
max
) в новый диапазон (от
newMin
до
newMax
).
val = map(analogRead(0), 0, 1023, 0, 100);
- получить с аналогового входа значения 0-100 вместо 0-1023. Работает только с целыми числами! |
round(x)
|
Математическое округление |
radians(deg)
|
Перевод градусов в радианы |
degrees(rad)
|
Перевод радиан в градусы |
sq(x)
|
Квадрат числа |
[/su_spoiler][su_spoiler title="Математические константы" open="no" style="fancy" icon="arrow"]
Константа | Значение | Описание |
INT8_MAX
|
127 | Макс. значение char, int8_t |
UINT8_MAX
|
255 | Макс. значение byte, uint8_t |
INT16_MAX
|
32767 | Макс. значение int, int16_t |
UINT16_MAX
|
65535 | Макс. значение unsigned int, uint16_t |
INT32_MAX
|
2147483647 | Макс. значение long, int32_t |
UINT32_MAX
|
4294967295 | Макс. значение unsigned long, uint32_t |
M_E
|
2.718281828 | Число e |
M_LOG2E
|
1.442695041 | log_2 e |
M_LOG10E
|
0.434294482 | log_10 e |
M_LN2
|
0.693147181 | log_e 2 |
M_LN10
|
2.302585093 | log_e 10 |
M_PI
|
3.141592654 | pi |
M_PI_2
|
1.570796327 | pi/2 |
M_PI_4
|
0.785398163 | pi/4 |
M_1_PI
|
0.318309886 | 1/pi |
M_2_PI
|
0.636619772 | 2/pi |
M_2_SQRTPI
|
1.128379167 | 2/корень(pi) |
M_SQRT2
|
1.414213562 | корень(2) |
M_SQRT1_2
|
0.707106781 | 1/корень(2) |
NAN
|
__builtin_nan(“”) | nan |
INFINITY
|
__builtin_inf() | infinity |
PI
|
3.141592654 | Пи |
HALF_PI
|
1.570796326 | пол Пи |
TWO_PI
|
6.283185307 | два Пи |
EULER
|
2.718281828 | Число Эйлера е |
DEG_TO_RAD
|
0.01745329 | Константа перевода град в рад |
RAD_TO_DEG
|
57.2957786 | Константа перевода рад в град |
[/su_spoiler]