Bad Programming or Glitch abs((int)-32,768) fails to convert to positive

I usually never post questions but I am at a loss... Is this a bad programming practice or is this a glitch that I just discovered, and How would you recommend handling this situation?
Googled:

A 16-bit integer can store 2**16** (or 65,536) distinct values. In an unsigned representation, these values are the integers between 0 and 65,535; using two's complement, possible values range from −32,768 to 32,767. Hence, a processor with16-bit memory addresses can directly access 64 KB of byte-addressable memory.

I see that it would be impossible to convert an int from a -32768 to positive 32768 as it doesn't exist in a 16-bit integer.

What're your recommendations?
And should there be some kind of warnings in the Arduino Reference?

Test code:

void setup() {
  // put your setup code here, to run once:
  // initialize serial communication
  Serial.begin(115200);
  while (!Serial); // wait for Leonardo enumeration, others continue immediately

  int16_t a = -32768 ;
  int b = -32768 ;
  int32_t c = -32768 ;
  long d = -32768 ;

  Serial.print("abs a = ");
  Serial.println(abs(a));
  Serial.print("abs b = ");
  Serial.println(abs(b));
  Serial.print("abs c = ");
  Serial.println(abs(c));
  Serial.print("abs d = ");
  Serial.println(abs(d));
}

void loop() {
  // put your main code here, to run repeatedly:
}

Results:

abs a = -32768
abs b = -32768
abs c = 32768
abs d = 32768

Thanks for your advice in advance :slight_smile:

Z

I don't see this as an Arduino problem. It is just a feature of a 16 bit signed value.

Presumably the solution is to promote the variable to a long?

...R

Robin2:
I don't see this as an Arduino problem. It is just a feature of a 16 bit signed value.

Presumably the solution is to promote the variable to a long?

...R

My reading comes from an MPU6050 and I only need it to test error total from the accel sensors so my solution at this time is

unsigned long eSum; 
eSum += ((Reading)>0?(Reading):-(min(-32767,Reading))); // abs() replacement

I did this as abs() is a macro

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

and it made sense to just rewrite it to do what I needed.

There's no signed integral type in C++ where abs() will produce anything useful from the most negative value of that type. You just have to avoid that particular value.

It's just the nature of twos complement math - there are an even number of different values, and one of them is zero. Therefore, there will be one extra value left over that has to go to either the positive values or the negative values, and the nature of twos complement dictates that the negative numbers get the extra value.

If you need to use the full range of an int for some reason, then you'd need to use a long instead of an int to store the values, and just accept that the long is bigger than an int.

The C++ specification says that calling abs() on these kinds of values is undefined behavior. That means that the compiler is allowed to do absolutely anything it wants when it runs into these values - maybe it returns the original value, maybe it returns 0, maybe it calls abort(), whatever. It is always bad practice to rely on undefined behavior. It's bad for portability, and there's no guarantee that even the next version of the compiler will behave the same way.

MHotchin:
There's no signed integral type in C++ where abs() will produce anything useful from the most negative value of that type. You just have to avoid that particular value.

It's just the nature of twos complement math - there are an even number of different values, and one of them is zero. Therefore, there will be one extra value left over that has to go to either the positive values or the negative values, and the nature of twos complement dictates that the negative numbers get the extra value.

If you need to use the full range of an int for some reason, then you'd need to use a long instead of an int to store the values, and just accept that the long is bigger than an int.

The C++ specification says that calling abs() on these kinds of values is undefined behavior. That means that the compiler is allowed to do absolutely anything it wants when it runs into these values - maybe it returns the original value, maybe it returns 0, maybe it calls abort(), whatever. It is always bad practice to rely on undefined behavior. It's bad for portability, and there's no guarantee that even the next version of the compiler will behave the same way.

Thank you.
undefined behavior
Makes sense now lol :o

This is the Arduino version of abs()

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

And this I found somewhere else: link

int abs (int i){
  return i < 0 ? -i : i;
}

I guess there are several ways to do it.
I decided to just do it in a way that is most effective for this instance as I now realize that it is too unpredictable to rely on the default functionality of abs();

zhomeslice:
I decided to just do it in a way that is most effective for this instance as I now realize that it is too unpredictable to rely on the default functionality of abs();

The 'default functionality of abs()' ?

That implies that abs() could somehow respond in some other way when called with an invalid value.

srnet:
The 'default functionality of abs()' ?

That implies that abs() could somehow respond in some other way when called with an invalid value.

yes absolutely!
abs() is unpredictable
try this:

int x = 0
Serial.print abs(++x);

or

int x = 0
Serial.print abs(--x);

lets also try

int x = -2
Serial.print abs(++x);

What is your prediction?
:slight_smile:

If you store the result in an 'unsigned' variable it comes out fine:

void setup() {
  // put your setup code here, to run once:
  // initialize serial communication
  Serial.begin(115200);
  while (!Serial); // wait for Leonardo enumeration, others continue immediately


  int b = -32768 ;
  long d = -32768 ;


  unsigned int abs_b = abs(b);
  unsigned long abs_d = abs(d);


  Serial.print("abs(b) = ");
  Serial.println(abs(b));
  Serial.print("abs_b = ");
  Serial.println(abs_b);
  Serial.print("abs(d) = ");
  Serial.println(abs(d));
  Serial.print("abs_d = ");
  Serial.println(abs_d);
}


void loop() {}

Results:
abs(b) = -32768
abs_b = 32768
abs(d) = 32768
abs_d = 32768

Is this "problem" with abs() any different from the "problem" caused by trying to add (say) 26,789 and 15,678 when both are signed 16 bit values?

...R

zhomeslice:
abs() is unpredictable

So how should abs() respond, or what value should it return, when called with an invalid value ?

zhomeslice:
yes absolutely!
abs() is unpredictable
try this:

int x = 0

Serial.print abs(++x);




or 


int x = 0
Serial.print abs(--x);




lets also try


int x = -2
Serial.print abs(++x);




What is your prediction?
:)

This is the ugly side of macro, not abs(), if you implement abs() as a function instead, there's no confusion.

srnet:
So how should abs() respond, or what value should it return, when called with an invalid value ?

I have now determined that I will take each instance I need to change the value from negative to positive and create my solution to fit its purpose.
For example, my current code uses this:

eSum += ((Reading)>0?(Reading):-(min(-32767,Reading))); // abs() replacement

I could have easily did this

eSum += abs((long)Reading)

because my reading is maxed out 1 less didn't matter

What did was that abs() is a macro and this is what it translates to:

eSum += (((long)Reading)>0?((long)Reading):-((long)Reading);

my version translates into:

eSum += (Reading > 0) ? Reading : -((-32767 > Reading) ? -32767 : Reading); // abs() replacement

As I think about it this would be better and faster:

eSum += (Reading > 0) ? Reading : ((-32767 > Reading) ? -32767 : -Reading); // abs() replacement

:slight_smile: Improved my code more!
Z