Round 37.1-28.75 float calculation correctly to 8.4 instead of 8.3

Viewed 163

I have problem with floating point rounding. I want to calculate floating point numbers and round them to (given) N decimals. In this example I want to round to 1 decimal places.

Calculation 37.1-28.75 will result into floating point 8.349998 (instead of 8.35), which will result printf rounding to 8.3 instead of 8.4 for 1 decimal places.

The actual result in math is 37.10-28.75=8.35000000, but due to floating point imprecision it is converted into 8.349998, which is then converted into 8.3 instead of 8.4 when using 1 decimal place rounding.

Minimum reproducible example:

float a = 37.10;
float b = 28.75;
//a-b = 8.35 = 8.4
printf("%.1f\n", a - b); //outputs 8.3 instead of 8.4

Is it valid to add following to the result:

float result = a - b;

if (result > 0.0f)
{
    result += powf(10, -nr_of_decimals - 1) / 2;
}
else
{
    result -= powf(10, -nr_of_decimals - 1) / 2;
}

EDIT: corrected that I want 1 decimal place rounded output, not 2 decimal places

EDIT2: negative results are needed as well (28.75-37.1 = -8.4)

3 Answers

On my system I do actually get 8.35. It's possible that you have to set the rounding direction to "nearest" first, try this (compile with e.g. gcc ... -lm):

#include <fenv.h>
#include <stdio.h>

int main()
{
  float a = 37.10;
  float b = 28.75;
  float res = a - b;

  fesetround(FE_TONEAREST);

  printf("%.2f\n", res);
}

Binary floating point is, after all, binary, and if you do care about the correct decimal rounding this much, then your choices would be:

  • decimal floating point, or
  • fixed point.

I'd say the solution is to use fixed point, especially if you're on embedded, and forget about everything else.

With

int32_t a = 3710;
int32_t b = 2875;

the result of

a - b

will exactly be

835

every time; and then you just need to have a simple fixed point printing routine for the desired precision, and check the following digit after the last digit to see if it needs to be rounded up.

If you want to round to 2 decimals, you can add 0.005 to the result and then offset it with floorf:

float f = 37.10f - 28.75f;
float r = floorf((f + 0.005f) * 100.f) / 100.f;

printf("%f\n", r);

The output is 8.350000

Why are you using floats instead of doubles?

Regarding your question:

Is it valid to add following to the result:

        float result = a - b;

        if (result > 0.0f)
        {
            result += powf(10, -nr_of_decimals - 1) / 2;
        }
        else
        {
            result -= powf(10, -nr_of_decimals - 1) / 2;
        }

It doesn't seem so, on my computer I get 8.350498 instead of 8.350000.

After your edit:

Calculation 37.1-28.75 will result into floating point 8.349998, which will result printf rounding to 8.3 instead of 8.4.

Then

float r = roundf((f + (f < 0.f ? -0.05f : +0.05f)) * 10.f) / 10.f;

is what you are looking for.

Related