32bit arithmetic, abs() is wrong

Hi, here is a further curious effect with 32bit arithmetic:

This sketch on an ATtiny84

#include <SoftwareSerial.h>

SoftwareSerial mySerial(5, 6);  // RX, TX

uint32_t baudrt;
uint16_t lcnt = 0;
uint8_t val8;
uint16_t val16;
uint32_t val32;
int32_t valm32;
int16_t valm16;

void setup() {
  // put your setup code here, to run once:
  baudrt = 38400;
  mySerial.begin(baudrt);
  delay(50);
  mySerial.println();
  mySerial.print("Baudrate=");
  mySerial.println(baudrt);

  valm32 = -1234567890;
  mySerial.print(valm32);
  val32 = abs(valm32);
  mySerial.print(" "); mySerial.println(val32);
  valm32 = -234567890;
  mySerial.print(valm32);
  val32 = abs(valm32);
  mySerial.print(" "); mySerial.println(val32);
  
  valm32 = -12345;
  mySerial.print(valm32);
  val32 = abs(valm32);
  mySerial.print(" "); mySerial.println(val32);
  valm16 = -12345;
  mySerial.print(valm16);
  val16 = abs(valm16);
  mySerial.print(" "); mySerial.println(val16);
}

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

}

generates the following output:

Baudrate=38400
-1234567890 722
-234567890 14546
-12345 12345
-12345 12345

Oviously the abs() function is wrong with 32bit values.

This works correctly on an Arduino Uno:

uint32_t val32;
int32_t valm32;

void setup() {
  Serial.begin(115200);
  delay(50);

  valm32 = -1234567890;
  Serial.print(valm32);
  val32 = abs(valm32);
  Serial.print(" "); Serial.println(val32);  //prints -1234567890 1234567890
}

void loop() {}

Generally, use the L suffix on literals to indicate Long integers to prevent these being treated (on 8 bit processors) as 16bit integers.

abs() is, incidentally, defined in Arduino.h

Fine, but not on an ATtiny84!

Is 32 bit arithmetic supported by Arduino, for the ATTiny84? Which core are you using?

You could post on an issue to the core developer.

@6v6gt has a good point. Don't assume that a naked decimal constant is interpreted as you might expect.

I have the core from Spence Konde and just tried it with

valm32 = -1234567890L;

But again the result is wrong.

I have the core from Spence Konde

And what do the docs say about 32 bit arithmetic?

This gives a hint:


int16_t val16;
void setup() {
  Serial.begin(115200);
  delay(50);
  val16 = -1234567890;
  Serial.println(val16);  //prints -722
}
void loop() {}

I can't test it but maybe reduce the sketch to the minimum which shows the problem.

a) with the @DrAzzy core, you do not need to include Software Serial explicitly,

from: https://github.com/SpenceKonde/ATTinyCore/blob/master/avr/extras/TinySoftSerialBaud.md

If there is a general problem with 32bit arithmetic on those cores, that would be rather sad for any time library.

The abs function takes an int which is 16 bits wide. Use labs for long (32-bit) values.

(I personally think it's a mistake for Arduino to redefine (but apparently only in some cores) standard functions like abs with slightly different behavior than what is defined by the standards.)

1 Like

Thank you, that works.
I didn't know the function labs().

Thanks! I didn't know about labs() either, and mistakenly assumed abs() was overloaded.

Does labs() pertain to only the attiny? I was under the impression the Arduino.h used a define for abs() that was not type specific.

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

Interesting. When I compile this for the board ATTINY84:

void setup() {
  // put your setup code here, to run once:
  uint32_t a = abs( -1234567890 ) ;
  Serial.print( a )  ;
}

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

}

I get this in the preprocessor output file ctags_target_for_gcc_minus_e.cpp

# 1 "C:\\Users\\6v6gt\\Documents\\Arduino\\sketch_feb22b\\sketch_feb22b.ino"

void setup() {
  // put your setup code here, to run once:
  uint32_t a = ((-1234567890)>0?(-1234567890):-(-1234567890)) ;
  Serial.print( a ) ;
}

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

}

Which indicates that the Arduino.h file is being used to handle abs() :

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

which I guess would generate the correct output.

have you tried with importing SoftwareSerial ?

at the end of Spence Konde's SoftwareSerial.h you find this undefine

IMO, the math "functions" on Arduino are a train wreck.
Creating overriding macros for functions like min(), max(), abs(), round(), etc...
are ALWAYS problematic.
They can create silent bugs that are sometimes not obvious like in the case of integer overflow or other issues when parameters not simple variables for the macros that use the parameter more than once like min(), max(), abs(), constrain(), round(), sq(), etc...

We played these types of macros games back in the 80s and early 90s because back in those days it could generate significantly better/smaller/faster code with an understanding of the limitations. But often we used all upper case names like MIN(), MAX(), ABS(), etc.. to very clearly differentiate between the macro version and the real function.

In Arduino with various modules undefining these and potentially re-defining them, the behavior of these functions becomes unpredictable, unless you explicitly undefining them yourself.
But explicitly undefining them can break other code since some code maybe be unfortunately relying on behavior that is outside the way the function is specified to work for C.

The time for creating & using these types of macros faded away at least 20 years ago since the newer compilers can generate just as good of code without having to resort to using such problematic macros.

And now with C++11 <cmath> there should be absolutely no need to ever create these macros when using C++ as cmath creates all overloads for C++ using templates so you don't have to mess with using different functions names like labs(), llabs(), fabs(), fmin(), etc... when using C++
You can just call the common name abs(), min(), max(), etc....

IMO, Arduino really should remove all these math macros and let the compiler tools work as intended.

--- bill

1 Like

That is definitely the difference.

If SoftwareSerial is explicitly included:

#include <SoftwareSerial.h>

SoftwareSerial mySerial(5, 6);  // RX, TX

void setup() {
  // put your setup code here, to run once:
  uint32_t a = abs( -1234567890L ) ;
  mySerial.print( a )  ;
}

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

}

Then the preprocessor output is now :

# 1 "C:\\Users\\6v6gt\\Documents\\Arduino\\sketch_feb22b\\sketch_feb22b.ino"
# 2 "C:\\Users\\6v6gt\\Documents\\Arduino\\sketch_feb22b\\sketch_feb22b.ino" 2

SoftwareSerial mySerial(5, 6); // RX, TX

void setup() {
  // put your setup code here, to run once:
  uint32_t a = abs( -1234567890L ) ;
  mySerial.print( a ) ;
}

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

}

indicating that the abs() defined in Arduino.h is not used.

thanks for trying... I guess we know the culprit now :slight_smile:

Indeed. But even worse is the unwanted function prototype generation which causes no end of grief and agony for the unwary.

The standard is sacred: https://www.cplusplus.com/reference/cstdlib/abs/
That means it works with int and the wrong result in ATtiny84 is actually the correct output for very old 'C', but there is a long int abs() in modern C++.

The GCC compiler for the AVR flavor has only int abs and long labs(): https://www.nongnu.org/avr-libc/user-manual/group__avr__stdlib.html

[EDIT] Sorry for editing this after posting it.

we are using a C++ compiler, so indeed we have

          int abs (          int n);
     long int abs (     long int n);
long long int abs (long long int n);