FYI, on Teensy4.1 the compiler does not substitute fabs for abs in case of float

clinker8

Well-known member
Got burned by this. Had a float variable. Used abs on it. No warnings that I saw. (Maybe it was there, but I have a pile of warnings, many from my libraries.) Return value for floats < 0 was 0, not the absolute value. Sample output of using abs
Code:
Zval = -0.7780003, abs(Zval) = +0.0000003
fabs worked correctly.
Code:
Zval = -0.5880003, fabs(Zval) = +0.5880003
Seems the Teensy compiler doesn't check the type of the variable.

Not surprised in retrospect, but caught me, nonetheless.
 
Got burned by this. Had a float variable. Used abs on it. No warnings that I saw. (Maybe it was there, but I have a pile of warnings, many from my libraries.) Return value for floats < 0 was 0, not the absolute value. Sample output of using abs
Code:
Zval = -0.7780003, abs(Zval) = +0.0000003
fabs worked correctly.
Code:
Zval = -0.5880003, fabs(Zval) = +0.5880003
Seems the Teensy compiler doesn't check the type of the variable.

Not surprised in retrospect, but caught me, nonetheless.

The abs, fabs, and fabsf functions are all standard C functions. Until recently, C did not have overloading functions (C++ does, but not C). So if you call abs, the compiler converts the argument to int type, and then does the abs operation (usually for abs, just inline code). Similarly for fabs, the compiler converts the variable to double type and does the abs function. To be type correct, you should always use the 'f' suffix on floating point constants, and use the math functions with a 'f' suffix. I.e. instead of:
Code:
    float f = -5.6;
    float g;
    // ...
    g = abs (f) - 1.23;
    Serial.println (g);

you should write:
Code:
    float f = -5.6f;
    float g;
    // ...
    g = fabsf (f) - 1.23f;
    Serial.println (g);

In particular, on the Teensy 3.5 and Teensy 3.6, the chip only has single precision floating point in hardware. So using fabs will convert the expression to double which has to emulated at runtime. On the Teensy LC and 3.2, there is no hardware floating point, but generally the single precision float support takes less time to execute than the double precision double type. On the Teensy 4.0 and 4.1, it has both single and double precision floating point hardware.
 
The abs, fabs, and fabsf functions are all standard C functions. Until recently, C did not have overloading functions (C++ does, but not C). So if you call abs, the compiler converts the argument to int type, and then does the abs operation (usually for abs, just inline code). Similarly for fabs, the compiler converts the variable to double type and does the abs function. To be type correct, you should always use the 'f' suffix on floating point constants, and use the math functions with a 'f' suffix. I.e. instead of:
Code:
    float f = -5.6;
    float g;
    // ...
    g = abs (f) - 1.23;
    Serial.println (g);

you should write:
Code:
    float f = -5.6f;
    float g;
    // ...
    g = fabsf (f) - 1.23f;
    Serial.println (g);

In particular, on the Teensy 3.5 and Teensy 3.6, the chip only has single precision floating point in hardware. So using fabs will convert the expression to double which has to emulated at runtime. On the Teensy LC and 3.2, there is no hardware floating point, but generally the single precision float support takes less time to execute than the double precision double type. On the Teensy 4.0 and 4.1, it has both single and double precision floating point hardware.

One gets lazy with new processors ;) I do remember having to use abs, fabs and cabs. However, I also did a lot of python and numpy, where the language sorted it all out for you. Kind of strange flipping back and forth between languages. C on embedded platforms seems to be a slow adopter of the more modern versions of C, or at least it seems to me. Remember using C99 in 2015.
 
The compiler can't know that abs wasn't intended. (But agreed, at least it could emit a warning, yes...)
 
I'm very puzzled by this thread. As I have understood it for years, Arduino and Teensyduino are, in fact, C++. What is gained by not overloading abs() so that it works as would be generally expected?
 
I'm very puzzled by this thread. As I have understood it for years, Arduino and Teensyduino are, in fact, C++. What is gained by not overloading abs() so that it works as would be generally expected?

abs() is part of the C standard library. Whichever of these functions you call, the argument is cast to the type in the declaration, and the return type will be the same type.

Code:
int abs( int x );
float fabsf( float x ); (since C99)
double fabs( double x ); 	
long double fabsl( long double x ); (since C99)
 
abs() is part of the C standard library. Whichever of these functions you call, the argument is cast to the type in the declaration, and the return type will be the same type.

Code:
int abs( int x );
float fabsf( float x ); (since C99)
double fabs( double x ); 	
long double fabsl( long double x ); (since C99)

And Arduino seems to be a mixture of C and C++, which makes it confusing as to which standard(s) it supports. But I learned my lesson, at least for now :).
 
And Arduino seems to be a mixture of C and C++, which makes it confusing as to which standard(s) it supports. But I learned my lesson, at least for now :).

Yes. Arduino – and most other current microcontroller development environments – is based on a mix of freestanding C and freestanding C++. This limits the language features that are available. For C, it means the standard library may not be available at all, or is incomplete. For C++, it means things like exceptions are not supported. Arduino provides its own libraries, but also happens to provide a mostly complete C library (typically avr-libc or newlib) for most microcontroller targets. They do not document this well, though. (Not a surprise: the C standard defines freestanding pretty well, but the C++ standard uses quite vague "whatever the provider provides" -like wording. Basically, we're reaching outside well-standardized environments here.)

Besides, Arduino does quite interesting mangling to the C/C++ source .ino files, before feeding them to the compiler!

For Teensyduino:

For Teensy 2.0 (ATmega32u4) and 2.0++ (AT90USB1286), we have avr-libc. The features it provides are listed *here*. Do note that only the parts actually used from the library are included in firmwares.

For Teensy LC, 3.x, 4.x (all 32-bit ARM based ones), the gcc-arm-none-eabi toolchain is used; it includes newlib. The features it provides are listed *here*, but do remember to skip the syscall stuff (they're only relevant when running under an actual operating system, and with Arduino/Teensyduino, we're running on bare hardware instead). There is also some other stuff there that isn't used here, like environment, most of stdio.h, and so on.

Because GCC is used as the compiler, we also have some rather interesting stuff available that it provides. We have function attributes that can help the compiler optimize code better; variable attributes; integer overflow built-ins, and other built-ins – the bit ones can be particularly useful (ffs, clz, popcount, parity). If one generates complex state machines, then the labels-as-values extension GCC provides can be especially useful, as it allows one to obtain labels (goto targets) as void pointers, for use in state machine descriptions (actions or transitions to goto to).

Teensy 4.0 and 4.1 and Micromod also have VFPv5 (VFPv5-SP-D16, I believe), which can do additions and subtractions (including saturating ones) on four 8-bit, two 16-bit, or one 32-bit value stored in a 32-bit register. (I have not checked which version of GCC is needed for these to be available via the vector extensions.) It includes hardware (single precision?) floating point unit, including fused multiply-adds (and -subtracts, -negates), minimum and maximum, and even square root. This means that basic arithmetic (addition, subtraction, multiplication, division, and square root) using the 'float' type is quite efficient.

One can implement the most critical parts of the code in extended asm, too. While this uses assembler (GNU as) syntax, we can leave the exact register selection to the compiler, and thus "seamlessly" join C and assembly code. (I might use them to expose the saturating integer math instructions as C functions one could call, for example.)
 
Back
Top