Round arduino ide

round() macro in Arduino.h #76


Koepel commented Mar 20, 2017

The function round() in «math.h» is okay, it returns a floating point number as expected.
The macro round() in «Arduino.h» is a bug. It can not handle large floating point numbers and it returns a long integer.
#define round(x) ((x)>=0?(long)((x)+0.5):(long)((x)-0.5))
It could be renamed to something else, for example ROUND_INT().

The sketch below shows the difference. With the normal round() function it returns four times «1.50», and with the round() macro in «Arduino.h» it returns: «1.00», «1.50», «1.50», «0.00».

The text was updated successfully, but these errors were encountered:

facchinm commented Mar 22, 2017 •

In fact, round in the Arduino world returns the nearest integer for the argument (this also happen with math.h round , see
None of the examples you are giving is wrong:

  • x = round( x) / 10; is a division between integers, the result will be an integer ( 1 ) promoted to a float ( 1.00 )
  • x = round( x) / 10.0; is a division between an integer and a float, so the int gets promoted to a float and the result is a float ( 1.50 )
  • x = 1.5E+20; is bigger than a long ( uint32_t ) in AVR world, so in is expected to fail in funny ways

I’d close it as wontfix any thoughts about that @cmaglie @matthijskooijman ?

Koepel commented Mar 22, 2017

It violates the IEEE standard, making the Arduino floating point unpredictable. It is really a terrible bug.
The round() function is supposed to return a float. The x = round( x) / 10; should be a floating point calculation.
Are you saying that a Arduino single precision floating point may not be larger than the value of a long ?

facchinm commented Mar 22, 2017

It should be a floating point calculation if round(x) would return a float , but since it returns a long it’s an integer division.
However, you are totally right; I didn’t want to defend the macro implementation, which is terribly error prone, but only state that adopting math.h round could have some side effects on older sketches.
I just tested the sketch size occupation and, with my biggest disbelief, the math.h version produces way smaller binaries than the macro version

Some investigation is surely needed at this point, thanks for pointing it out and sorry for the previous response

Koepel commented Mar 22, 2017

The round() function should return a floating point number. Some explanations for other platforms say that it returns the nearest integer, that might be confusing, but those functions still return a floating point.
I don’t care about the macro or older sketches or smaller code. The round() function should be reliable and predictable. The round() function in «math.h» is just that, as it should be. That is however ruined by the macro.

cousteaulecommandant commented May 29, 2017 •

The thing is, Arduino is not standard C++, but some sort of language of its own that is based on C++ but introduces some simplifications. Among other things, it creates its own functions (well, macros) for round , min , max , abs , etc.
If you don’t like the behavior of this «Arduino language» and want to use actual C++, create a new tab in your project and name it main.cpp (or if you prefer C, main.c ), put your code there and leave the main tab empty. You’ll be programming in pure C++ and not get the dreaded Arduino.h .

Alternatively, if you want to use math.h ‘s round() within the Arduino language because you also want to use the rest of Arduino features (e.g. digitalWrite() ), you can use (round)(x) so that round is not interpreted as a macro, or simply write #undef round at the beginning of the .ino file, which I think should do the trick (untested).

Now, some thoughts:

  • Should Arduino have used different names (e.g. Round , Abs ) rather than preexisting ones? Probably, but I guess it’s too late to change that now.
  • Is it really a problem to use a name already defined in C++? I’d say it’s not as long as users are aware that they’re not using pure C++ but something built on top of it.
  • Should this odd behavior on overflow be documented? Definitely, but there doesn’t even seem to be documentation for Arduino’s round() .
  • Is it a good idea to use macros for this? Honestly I don’t think so; I’d rather use templated functions. Otherwise stuff like round(x++) will do weird stuff. (Then again, this problem is documented for min and max .)
  • Is there any advantage on using this implementation over just using the standard round() ? Well I would have thought so, but then @facchinm and his test confused me. (Are you sure the call to round() is not being optimized out or something? Maybe if you call round() with a constant argument the compiler is smart enough to optimize it.)

Koepel commented May 30, 2017

@cousteaulecommandant, I have to disagree with you. There is no good reason to change things for the bad. If you are right, then let’s change ‘ sin ‘, ‘ tan ‘ and ‘ memset ‘ as well, just for the fun of it. There has been put so much effort, for example, into the avr libc math library, and it is a very reliable and fast. Why change something that is perfectly working well ?

Meanwhile I learned that the Java round function returns an integer instead of a floating point number of the nearest whole number. Most people seem to think of that as a mistake when the Java language was created.

Since Arduino uses ‘c’ and ‘c++’ the round function should work as expected. In my opinion it really ruins the math library and it is not a simplification and it has not to do with being pure or not.

PaulStoffregen commented May 30, 2017

Why change something that is perfectly working well ?

Perhaps you are unaware of Arduino’s long 12 year history? Not everything you take for granted as working perfectly well in the modern versions of avr-libc was always so reliable.

cousteaulecommandant commented Jun 2, 2017

Not everything you take for granted as working perfectly well in the modern versions of avr-libc was always so reliable.

In fact, round was not even a standard C++ function until C++11, so Arduino is not at fault for defining a function that did not exist yet. Plus C++ was quite successful even prior to C++11, so I would not say that not being able to use round «really ruins the math library».

(If anything, it’s C++’s fault for defining a function that already existed in Arduino, not Arduino’s fault for defining a function that already existed in C++.)

Koepel commented Jun 2, 2017 •

The c math with the round function (with floating point parameter and floating point return) is at least C99 as far as I know. I hope you don’t mind, but I stick with my strong words: «really ruins the math library». It’s a matter of principle. The math library as a whole should be reliable. In my opinion it is not okay to mess around with standardized math functions.

PaulStoffregen commented Jun 2, 2017

avr-libc still does not fully implement C99, and until only a few years ago it lacked many of the C99 functions it has today. Sticking to your strong words and accusatory tone isn’t helping.

Koepel commented Jun 2, 2017

I’m very sorry if my tone was accusatory. I didn’t mean to be like that. I hope you agree with me that a lot of effort has been put into the math library, and the round function in the math library is perfectly fine.

cousteaulecommandant commented Jun 2, 2017

The math library with the round function (with floating point parameter and floating point return) is at least C99

Well yes, but Arduino is based on C++, not C.

In any case, it is true that it could be considered dropping the round() macro, specially since std::round() seems to work better, as @facchinm pointed out. (And personally I’d get rid of all these macros entirely, or at least implement them as template functions rather than macros.)
C++11 also seems to provide lround() which is the long version of round , so this functionality wouldn’t be lost if Arduino moved to these standard functions.
The only problem is that the name is different, so this could potentially break backwards compatibility: if someone was already using round() in their project and relying on the old (Arduino) behavior, moving to a floating point version might break things. Then again, nowhere in the Arduino documentation does it say anything about how round() behaves.

oqibidipo commented Jun 3, 2017

Well yes, but Arduino is based on C++, not C.

False. It is based on C and C++.

PaulStoffregen commented Jun 3, 2017 •

The round() macro, as it is currently implemented by Arduino, also suffers from evaluating its arguments multiple times. For an explanation, see «More Gotchas» on this page:

One possible fix might look like this:

But perhaps it would be best to just use the C library round(), now that it’s fully supported by the modern AVR toolchain, and as far as I know all the 32 bit toolchains.

Koepel commented Jun 3, 2017 •

Thank you PaulStoffregen.

The origin of this issue is this:
Someone encountered a rounding problem and I thought it must be a joke. So my first reaction was (translated): «Ha ha, april fools day. Wait a moment. it really does go wrong. Yikes! What a nasty bug«.
(I hope I don’t offend someone with it).

As far as I know, in ‘c’ and ‘c++’ the round function returns a floating point number which is rounded to the nearest whole number. I really don’t know any other way. I didn’t know the history of this, but at this point it would be good to confirm with the standard round function.

PaulStoffregen commented Jun 3, 2017 •

This may be stating the overly obvious, but around here arguments about novice user experience and backwards compatibility with the 12 year legacy of Arduino gain a lot more traction than promoting standards compliance, especially C99, C++11, etc.

cousteaulecommandant commented Jun 5, 2017

One possible fix might look like this:

I’d rather use an actual function and end this macro madness:

or do nothing at all, and use the standard round() and lround() functions.


Математические операции


Одной из основных функций микроконтроллера является выполнение вычислений, как с числами напрямую, так и со значениями переменных. Начнём погружение в мир математики с самых простых действий:

  • = присваивание
  • + сложение
  • — вычитание
  • * умножение
  • / деление
  • % остаток от деления

Рассмотрим простой пример:

По поводу последних двух строчек из примера, когда переменная участвует в расчёте своего собственного значения: существуют также составные операторы, укорачивающие запись:

  • += составное сложение: 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

Порядок записи инкремента играет очень большую роль: пост-инкремент var++ возвращает значение переменной до выполнения инкремента. Операция пре-инкремента ++var возвращает значение уже изменённой переменной. Пример:

Как говорилось в предыдущем уроке – локальные переменные нужно инициализировать, иначе в математических операциях получится непредсказуемый результат.

Порядок вычислений

Порядок вычисления выражений подчиняется обычным математическим правилам: сначала выполняются действия в скобках, затем умножение и деление, и в конце – сложение и вычитание.

Скорость вычислений

Математические вычисления выполняются процессором некоторое время, оно зависит от типа данных и типа операции. Вот время выполнения (в микросекундах) не оптимизированных компилятором вычислений для 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 выполняются дольше по той же причине – они реализованы программно, а вот умножение и сложение с вычитанием МК делает аппаратно и очень быстро.

Целочисленное деление

При целочисленном делении результат не округляется по “математическим” правилам, дробная часть просто отсекается, фактически это округление вниз: и 9/10 и 1/10 дадут 0 . При использовании float само собой получится 0.9 и 0.1 . Если нужно целочисленное деление с округлением вверх, его можно реализовать так: вместо x / y записать (x + y — 1) / y . Рассмотренные выше примеры деления на 10 дадут результат 1 .

Для округления по обычным математическим правилам можно использовать функцию round() , но она довольно тяжёлая, так работает с float .

Переполнение переменной

Вспомним предыдущий урок о типах данных: что будет с переменной, если её значение выйдет из допустимого диапазона? Тут всё весьма просто: при переполнении в бОльшую сторону из нового значения вычитается максимальное значение переменной, и у неё остаётся только остаток. Для сравнения представим переменную как ведро. Будем считать, что при наливании воды и заполнении ведра мы скажем стоп, выльем из него всю воду, а затем дольём остаток. Вот так и с переменной, что останется – то останется. Если переполнение будет несколько раз – несколько раз опорожним наше “ведро” и всё равно оставим остаток. Ещё один хороший пример – кружка Пифагора.

При переполнении в обратную сторону (выливаем воду из ведра), будем считать, что ведро полностью заполнилось. Да, именно так =) Посмотрим пример:

Особенность больших вычислений

Для сложения и вычитания по умолчанию используется ячейка 4 байта ( long ), но для умножения и деления – 2 байта ( int ). Если при умножении или делении в текущем действии результат превысит 32768 – ячейка переполнится и мы получим некорректный результат. Для исправления ситуации нужно привести тип переменной к long перед вычислением, что заставит МК выделить дополнительную память. Например a = (long)b * c;

Для цифр существуют модификаторы, делающие то же самое:

  • 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

Помимо медленных вычислений, поддержка работы с float занимает много памяти, т.к. реализована в виде “библиотеки”. Использование математических операций с float однократно добавляет примерно 1.5 кБ в память программы.

С вычислениями есть такая особенность: если в выражении нет float чисел, то вычисления будут иметь целый результат (дробная часть отсекается). Для получения правильного результата нужно писать преобразование (float) перед действием, использовать float числа или float переменные. Также есть модификатор f , который можно применять только к цифрам float . Смысла в нём нет, но такую запись можно встретить. Смотрим:

При присваивании float числа целочисленному типу данных дробная часть отсекается. Если хотите математическое округление – его нужно использовать отдельно:

Следующий важный момент: из за особенности самой модели “чисел с плавающей точкой” – вычисления иногда производятся с небольшой погрешностью. Смотрите (значения выведены через порт):

Казалось бы, val2 должна стать ровно 0.1 после вычитания, но в 8-ом знаке вылезла погрешность! Будьте очень внимательны при сравнении float чисел, особенно со строгими операциями : результат может быть некорректным и нелогичным.

Список математических функций

Математических функций в Arduino довольно много, часть из них являются макросами, идущими в библиотеке Arduino.h, все остальные же наследуются из мощной C++ библиотеки math.h

Функция Описание
cos (x) Косинус (радианы)
sin (x) Синус (радианы)
tan (x) Тангенс (радианы)
fabs (x) Модуль для float чисел
fmod (x, y) Остаток деления x на у для float
modf (x, *iptr) Возвращает дробную часть, целую хранит по адресу iptr
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)
ldexp (x, exp) x*2^exp
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) Округляет указанное значение с плавающей запятой до ближайшего целого значения, используя текущий режим округления и направление
Функция Значение
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) Квадрат числа
Константа Значение Описание
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 Константа перевода рад в град