2

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)

phuclv
  • 37,963
  • 15
  • 156
  • 475
Tmas
  • 21
  • 6
  • 1
    Sounds like you need `roundf`. – Lundin Nov 24 '20 at 07:52
  • @user3386109 sorry, added example! This actually revealed critical mistake in my question. – Tmas Nov 24 '20 at 08:29
  • Yup, it makes sense now. That subtraction results in a number (8.35) that's exactly half way between the two possible numbers with one digit after the decimal (8.3 and 8.4). So the slightest error in the calculation will affect how the number is rounded. – user3386109 Nov 24 '20 at 08:36
  • 2
    Re “Calculation 37.1-28.75”: No such calculation is possible in a binary-based `float` format, as 37.1 is not representable in the format. **Before** your calculation even begins, `float a = 37.10;` results in `a` being set to 37.09999847412109375, in the format commonly used for `float`. It is **impossible** to represent numbers with two decimal digits in binary floating-point, other than those ending in .00, .25, .50, and .75, and therefore binary-based floating-point is not a correct format to use for such work. – Eric Postpischil Nov 24 '20 at 12:14

3 Answers3

2

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);
}
Peter
  • 2,919
  • 1
  • 16
  • 35
  • Sorry I asked question wrong! I wanted 1 decimal place output, not 2. Thanks for that info anyway. – Tmas Nov 24 '20 at 08:30
  • @Tmas: Do you actually want to modify the result or just the output of printf? – Peter Nov 24 '20 at 08:33
  • Effectively it will be outputted using printf. This is embedded system and I will output result to buffer using snprintf (with rounded to N number of decimals). To my knowledge these printf variations should behave the same in regards to rounding. – Tmas Nov 24 '20 at 08:54
2

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.

Peter O.
  • 32,158
  • 14
  • 82
  • 96
1

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.

David Ranieri
  • 39,972
  • 7
  • 52
  • 94