This discussion has been locked.
You can no longer post new replies to this discussion. If you have a question you can start a new discussion

Rounding floats to integers on Cortex-M4

I've encountered an odd problem on STM32F4xx CPU with hardware FPU enabled:

When rounding floats to integers, compiler always uses VCVT (round towards zero) instruction instead of VCVTR (round using rounding mode register settings).

There are compiler options for various IEEE compatibility modes, some of them explicitly define "round to nearest" behaviour, but it seems to be ignored.

Even worse, FLT_ROUNDS in float.h  is defined as floats are rounded, but they're truncated.

fpgetround() and fpsetround() do not work right either.

Any ideas?

  • In C floating point numbers are truncated when they are assigned to an integer. The round function was defined before IEEE was standard and truncates if the fractional part is less than a half. To get IEEE rounding you have to use the nearbyint or rint math functions.

  • To solve the problem, you could make 2 macros; one for rounding towards zero and one for rounding normally by first adding 0.5.

    That would also make sure that switching rounding mode would not affect your results.

    By doing that, you would also be sure that no matter which compiler that is used to generate the binaries, you would get the same results.

    It could be something like the following (for 32-bit unsigned integers):

    #define FLOOR32U(f) ((uint32_t) (f))

    #define ROUND32U(f) FLOOR32U((f) + 0.5)

    If you need signed integers, it would be a good idea to use ABS in the macros.

    Unfortunately, the CEIL32U is a bit more complicated than I first expected, because if the value is already an integer, it should not be incremented.

    Adding 1 after rounding the value up would not work then. Adding 0.9999999 is just not very reliable either.

  • >In C floating point numbers are truncated when they are assigned to an integer.


    No, they are not - behaviour should comply with implementation-defined value of FLT_ROUNDS.

    Also, manual for ARM Compiler v5.04 describing --fpmode=std option (which is default) says:

    IEEE finite values with denormals flushed to zero, round-to-nearest, and no exceptions. This is compatible with standard C and C++ and is the default option.

    Normal finite values are as predicted by the IEEE standard. However:

    • NaNs and infinities might not be produced in all circumstances defined by the IEEE model. When they are produced, they might not have the same sign.

    • The sign of zero might not be that predicted by the IEEE model.

  • It is not the question how to round numbers in C -  I know how to do it and I am using the method to fix the issue.

    The point is that compiler does not behave as it should (or I am doing something wrong - see my reply to above post).

  • I've found that if I use floating points (which I'm not doing for microcontrollers - at least not yet), then there's a difference between when I ...

    #include <math.h>

    ... and when I forget to do that.

    It usually solves my problems when I add the above mentioned #include.

    -You probably already did, but I mention it just in case...

  • See the C programming language standard section 6.3.1.4 Real floating and integer in

    http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1124.pdf

    • When a finite value of real floating type is converted to an integer type other than _Bool, the fractional part is discarded (i.e., the value is truncated toward zero). If the value of the integral part cannot be represented by the integer type, the behavior is undefined

    The bit you were reading is about rounding of the results of arithmetic operations on floating point values (and it should have referred to round to nearest or even not just round to nearest). For instance when two floating point values are added the exact result might occupy too many bits of precision so it needs to be rounded to a representable value e.g. if we had a machine which had 3 significant places of decimal in its floats 1.37 and 0.356 added would give 1.73 after automatic rounding. However assigning 1.73 to an integer would give 1 as the result.

  • OK - understood. I've mixed up something. Now it all makes sense. Thanks for clarification!