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.

    speed = abs(measured_encoder_delta_change_over_time);

How is abs() defined?

The Arduino absolute value macro is:

    #define abs(x) ((x)>0?(x):-(x))

This will work on any kind of type.

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:

  1. Once in the condition (leftmost parenthesis).
  2. 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!

Source: https://forum.arduino.cc/index.php?topic=626297.0

Using abs in other compilers

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.

int Iabs(int x) { return x<0 ? -x : x; }

int _abs(int x) {
     if (x == INT16_MIN)
        return INT16_MAX;
     else
        return x<0 ? -x : x;
}

void setup() {
   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 = ");
   int a = -5;
   Serial.println( a );
   Serial.print("abs(a) = ");  Serial.println( abs(a)   );

   Serial.print("\nTest P 1.3. abs macro  of a float. fv = ");
   float fv = -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= ");
   long lv = -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) );

}

void loop() {
  // 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.


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.




Privacy Policy | Contact | About Me

Site Map | Terms of Use