Arduino Absolute Value: Has One
Problem. It should should be a simple enough it's trivial to
understand! So why is it not working correctly in your
code? - and how to fix it.
Arduino absolute value:
Is it a macro or a function?
This sounds trivial - but it is crucial to how abs operates.
Find out what effect this has on your code.
This annoying problem will let some code work fine - but for other code you get odd results!
The abs() function finds the magnitude of its argument.
It really just returns positive values from any input value (either negative or positive). Mathematically it is:
x = |x|
if x = 300 then |300| = 300, or abs(300) == 300
if x = -300 then |-300| = 300, or abs(-300) == 300
That's all fine and simple but the devil is in the details!
Usage Example
An example would be measuring the speed of a robot. Assuming that one
of the wheels has a directional encoder attached e.g. a rotating
optically encoded disc that returns positive values for forward motion and
negative values for backward motion.
You don't want negative speed only positive speed values or the absolute value.
In C code the absolute value operation is a function.
int abs( int x );
In fact in C the abs function is only defined to work on integer values.
Warning: Arduino defines abs() differently to the C environment.
In C++ there are usually overloads of abs to deal with floats and other types (check documentation).
Note: Other functions: fabs, labs and llabs for float, long, and long long.
What's the problem?
The problem with macros is that they expand code and will not work
for all cases. They are fine for single element variables or constant
values but problems can occur if you place a multi-operation in the argument of a macro.
Here's the abs() macro working fine
Here's an illustration for the Arduino absolute value MACRO working correctly:
1.1. Arduino absolute value of a constant:
x = abs(-5);
Result x is 5.
1.2. Arduino absolute value of an integer variable:
int a = -5;
x = abs(a);
Result x is 5.
1.3. Arduino absolute value of a float variable:
float fv = -45.22
x = abs(fv);
Result x is 45.22.
Here's the abs() macro failing
Here's where the Arduino absolute value MACRO fails:
2.1. Extra operation within macro
a = -5;
x = abs(a++);
Result x is 4 <<<< ERROR
Serial.print("a after");Serial.println(a);
Result x is -3 <<<< ERROR
In fact the result you expect to get is 5 and the value of 'a' after the "function call" should -4 but instead it is -3.
The code seems to show the right action but when you write a "normal"
C code the post increment operator is supposed to take effect at the
end of an operation, not at the beginning. Here it has executed ++ at
the beginning. The reason is that the macro is expanded into different C
code than you get when you call a function - no function is called.
Lets compare that MACRO abs() to the function version of abs. Here I've re-created it as Iabs():
Function definition Iabs
int Iabs(int x) { return x<0 ? -x : x; }
2.2. Extra operation within abs function
This example uses a multi-operation in the argument. Here it is post incrementing the argument variable.
a = -5;
x = Iabs(a++);
Result x is 5 <<<< CORRECT
Serial.println(a);
Result a is -4 <<<< CORRECT
To understand why this happens you need to expand the macro:
The macro is:
#define abs(x) ((x)>0?(x):-(x))
Expand it as the compiler does
a = -5;
x = abs(a++); is replaced by:
((a++)>0?(a++):-(a++))
On two occasions the variable 'a' can be incremented:
Once in the condition (leftmost parenthesis).
Once in one of the selected results (one of the rightmost parenthesis).
Warning: Use the abs macro only for single variables, no operations.
The same problem will occur if you place a function into the argument of the abs() macro. It will be called twice!
Problems with abs()
Problem with Arduino absolute value macro
Anytime you place anything other than a single constant or variable
into the Arduino absolute value macro it will go wrong giving unexpected
results (see above).
This means code written for Arduino C/C++ may not work in other
compilers because you might take the absolute value of a floating point
type in a .ino file but that would cause an error in another compiler
because the standard version is only written for integers.
Also, as shown above, the Arduino absolute value macro executes the
argument twice causing more serious problems - because they occur at run
time (the complier will not complain for you!).
Problems with Arduino absolute value function
Even though the function version of abs works better by not
repeatedly executing the argument there is one other problem with it.
That is when you use integers and integer mathematics it will return the
wrong value. This could also be different on different compilers
(depending on the implementation).
This is due to the two's complement method of creating positive and negative numbers.
When you use an extreme value (INT16_MIN defined in inttypes.h) it will return the wrong thing:
Consider
a = -32768;
x = Iabs(a)
Result x is -32768 <<<< ERROR
TIP: A similar problem is in labs() as two's complement is also used.
This GNU documentation indicates that when INT_MIN is used as an input to Iabs() then the output is undefined.
You can either live with it and avoid extreme values or detect the minimum value and output the maximum:
int _abs(int x) {
if (x == INT16_MIN)
return INT16_MAX;
else
return x<0 ? -x : x;
}
Now
a = -32768;
x = _abs(a)
Result x is 32767 <<<< NEARLY CORRECT
However because of the way two's complement works, the corrected
Arduino absolute value of INT16_MIN (-32768) will return 32767. But at
least it is not wildly the opposite of what you want i.e. it is only 1
value out.
Different Arduino implementations
It looks like the Arduino Mega, ESP8266 and ESP32 implementation use
the standard implementation using a function and not a macro so be aware
that there is a difference! It is described as not working with float
so the function is int only for these chips!
It is part of the standard c library and in 'normal' compilers you
have to add the include directive to enable its use:
#include <stdlib.h>
The Arduino
environment makes it easier by scanning for known functions in your code
and automatically including the relevant header file (so you don't need the line above).
When
dealing with microcontrollers used at different companies they will
often use a chip that has its own compiler so you will need that
#include otherwise the compiler will complain. Because it is such a
commonly used function it is defined in
stdlib.h - the standard library header file.
Arduino absolute value test sketch
The following code example goes through the various abs options and shows you the problems as described above.
intIabs(intx){returnx<0?-x:x;}
int_abs(intx){
if(x==INT16_MIN)
returnINT16_MAX;
else
returnx<0?-x:x;
}
voidsetup(){
Serial.begin(115200);
Serial.println("abs() test");
Serial.println("\nTest P 1.1 abs macro of a constant. value -5 ");
Serial.print("abs(-5) = ");Serial.println(abs(-5));
Serial.print("\nTest P 1.2. abs macro of an integer. a = ");
inta=-5;
Serial.println(a);
Serial.print("abs(a) = ");Serial.println(abs(a));
Serial.print("\nTest P 1.3. abs macro of a float. fv = ");
floatfv=-45.22;
Serial.println(fv);
Serial.print("abs(fv) = ");Serial.println(abs(fv));
Serial.print("\nTest F 2.1. abs macro extra operation (FAIL). a = ");
a=-5;
Serial.println(a);
Serial.print("abs(a++) = ");Serial.println(abs(a++));
Serial.print("a after = ");Serial.println(a);
Serial.print("\nTest F 2.2. abs fn extra operation (PASS). a = ");
a=-5;
Serial.println(a);
Serial.print("Iabs(a++) = ");Serial.println(Iabs(a++));
Serial.print("a after = ");Serial.println(a);
Serial.print("\nTest F 4.1. does fabs work? fv= ");
fv=-45.22;
Serial.println(fv);
Serial.print("fabs(fv) = ");Serial.println(fabs(fv));
Serial.print("\nTest F 4.2. does labs work? fv= ");
longlv=-32123213123L;
Serial.println(lv);
Serial.print("labs(lv) = ");Serial.println(labs(lv));
/////////////////////////////// LONG
Serial.print("\n==================== LONG");
Serial.println("\nTest F 4.0. 32 bit min max ");
Serial.println(INT32_MIN);
Serial.println(INT32_MAX);
Serial.print("\nTest F 4.3. extreme values a =(INT32_MIN+1) = ");
Serial.println(INT32_MIN+1);
lv=INT32_MIN+1;
Serial.print("labs(a) = ");Serial.println(labs(lv));
Serial.print("\nTest F 4.4. extreme values INT32_MIN = ");
Serial.println(INT32_MIN);
lv=INT32_MIN;
Serial.print("labs(a) = ");Serial.println(labs(lv));
///////////////////////////// INT
Serial.println("\n==================== INT");
Serial.print("\nTest F 4.0. 16 bit min max ");
Serial.println(INT16_MIN);
Serial.println(INT16_MAX);
Serial.print("\nTest F 3.1. extreme values a =(INT16_MIN+1) = ");
Serial.println(INT16_MIN+1);
a=INT16_MIN+1;
Serial.print("Iabs(a) = ");Serial.println(Iabs(a));
Serial.print("\nTest F 3.2. extreme values INT16_MIN = ");
Serial.println(INT16_MIN);
a=INT16_MIN;
Serial.print("Iabs(a) = ");Serial.println(Iabs(a));
///////////////////////////// new abs
Serial.println("\n==================== new abs");
Serial.print("\nTest F 5.1. extreme values INT16_MIN = ");
Serial.println(INT16_MIN);
a=INT16_MIN;
Serial.print("_abs(a) = ");Serial.println(_abs(a));
}
voidloop(){
// put your main code here, to run repeatedly:
}
Sketch output
The output from this sketch on an Arduino Uno is:
abs() test
Test P 1.1 abs macro of a constant. value -5
abs(-5) = 5
Test P 1.2. abs macro of an integer. a = -5
abs(a) = 5
Test P 1.3. abs macro of a float. fv = -5.22
abs(fv) = 5.22¸abs() test
Test P 1.1 abs macro of a constant. value -5
abs(-5) = 5
Test P 1.2. abs macro of an integer. a = -5
abs(a) = 5
Test P 1.3. abs macro of a float. fv = -45.22
abs(fv) = 45.22
Test F 2.1. abs macro extra operation (FAIL). a = -5
abs(a++) = 4
a after = -3
Test F 2.2. abs fn extra operation (PASS). a = -5
Iabs(a++) = 5
a after = -4
Test F 4.1. does fabs work? fv= -45.22
fabs(fv) = 45.22
Test F 4.2. does labs work? fv= -2058442051
labs(lv) = 2058442051
==================== LONG
Test F 4.0. 32 bit min max
-2147483648
2147483647
Test F 4.3. extreme values a =(INT32_MIN+1) = -2147483647
labs(a) = 2147483647
Test F 4.4. extreme values INT32_MIN = -2147483648
labs(a) = -2147483648
==================== INT
Test F 4.0. 16 bit min max -32768
32767
Test F 3.1. extreme values a =(INT16_MIN+1) = -32767
Iabs(a) = 32767
Test F 3.2. extreme values INT16_MIN = -32768
Iabs(a) = -32768
==================== new abs
Test F 5.1. extreme values INT16_MIN = -32768
_abs(a) = 32767
Conclusions
The Arduino version of the Arduino absolute value abs() is a macro (but is a function on the MEGA, EPS8266 and ESP32):
Advantages
Fast (no overhead of a function call).
Can accept a value of any type (and returns the same type).
Disadvantages
Will go wrong if multi-operational or functions are used in the argument.
Is inconsistent with standard C programming.
In summary, the abs() macro provided by Arduino has both benefits and
limitations compared to a traditional function implementation.
The main advantage is that it avoids the overhead of a function call,
making it very efficient especially for single-operation cases. By
working as a simple macro expansion, it also allows abs() to accept any
data type as input and return the same type.
However, its macro nature is also the source of problems. Because it
directly replaces code rather than making a call, more complex
expressions containing multi-step operations or nested functions will
not work as expected. Because of this subtle problem, you may get
confusing bugs that are difficult to fix.
Additionally, as abs() behaves differently from its C/C++ counterpart,
code written for Arduino may encounter issues when ported to other
platforms. Beginners especially may be confused between the macro and
traditional function behaviors.
To resolve these issues, one approach is to supplement the built-in
abs() with custom type-specific versions like fabs() for floats. This
regains the consistency and safety of a function while keeping the
efficient signature of abs().
Going forward, Arduino developers may also consider migrating to a
function implementation for abs() across all boards to match standard
conventions. But maintaining macro compatibility is important for
existing code bases.
In the end, being aware of abs()'s true nature is key to using it
appropriately on Arduino and avoiding potential gotchas down the line.
Defining custom functions when needed adds an extra layer of protection.
Written by John Main who has a degree in Electronic Engineering.
Note: Parts of this page were written using chatgpt
as a research assistant.
How to get accurate DHT22 digital humidity sensor readings with an Arduino. Did you know it also measures temperature as Well? Find out why, in this page...
A PIR sensor lets your Arduino sense movement without contact. This tutorial covers PIR sensor basics, connecting one to an Arduino board and coding a motion detector.
Arduino Hall Effect Sensor: Add magnetic sensing superpowers to your Arduino projects with an easy-to-use hall effect sensor. With full code and layout...
Comments
Have your say about what you just read! Leave me a comment in the box below.
Don’t see the comments box? Log in to your Facebook account, give Facebook consent, then return to this page and refresh it.