2
#include <stdio.h>

int main(void)
{
    float r = 31.0f;
    float pi = 3.14f;
    float volume = 4.0f / 3.0f * pi * r * r * r;
    
    printf("volume = %f\n", volume);
    
    float k1 = 4.0f / 3.0f;
    printf("k1 = %f\n", k1);

    float k2 = k1 * pi;
    printf("k2 = %f\n", k2);

    float k3 = k2 * r;
    printf("k3 = %f\n", k3);

    float k4 = k3 * r;
    printf("k4 = %f\n", k4);

    float k5 = k4 * r;
    printf("k5 = %f\n", k5);


    return 0;
}

I'm beginner in C.

I thought that variable 'k5' must equal to 4023.387207 x 31 = 124725.003417

(I have learned so far that float variable is accurate up to six decimal points.)

But the output shows that k5 == 124725.000000

Why is this happening like this?

(My English is not very good, so please understand.)

unknown
  • 33
  • 3
  • 2
    Please see [Is floating point math broken?](http://stackoverflow.com/questions/588004/is-floating-point-math-broken) and [Why Are Floating Point Numbers Inaccurate?](https://stackoverflow.com/questions/21895756/why-are-floating-point-numbers-inaccurate) – Weather Vane Jan 20 '21 at 10:21
  • 6
    when counting digits, you must also count the ones before the decimal point – Walter Tross Jan 20 '21 at 10:22
  • (BTW: "up to six decimal points" should be "up to six decimal digits") – Walter Tross Jan 20 '21 at 10:23
  • When using floating point arithmetic, always work on the assumption that the result will be inexact and when you have `float pi = 3.14f;` it's pointless complaining that the result is off at the *sixth* digit. – Weather Vane Jan 20 '21 at 10:24
  • Floating point variables are designed such that their relative error is small. That means that for a number n error(n)/n is small. This relative error is similar for most floats meaning that for larger n the absolute error also increases. This explains what you are seing and shows that you cannot just assume a static absolute error. If you compare two floats, you should just do that up to some small value which depends on how large your values are. – andrbrue Jan 20 '21 at 10:25
  • 1
    BTW you can use `double` which has more precision than `float` – Jabberwocky Jan 20 '21 at 10:27

4 Answers4

4

Your first expression:

float volume = 4.0f / 3.0f * pi * r * r * r;

even if it is using floats for constants and variables, allows the compiler to automatically promote them into doubles at calculation time, improving accuracy a bit. It's something like:

float volume = (float) ( (double)4.0f / (double)3.0f * 
               (double)pi * (double)r * (double)r * (double)r );

Making all the multiplications and divisions to be performed with double operands.

On the other hand, your second approach, splitting the expression into smaller expressions and storing the intermediate results in float variables, doesn't allow the compiler to do such promotion (it may do it, but the improved result would loose its gained accuracy when you store it in a float for the next step). For example, your first two expressions would look like this:

float k1 = (float) ((double)4.0f / (double)3.0f );
printf("k1 = %f\n", k1);

float k2 = (float) ( (double)k1 * (double)pi );
printf("k2 = %f\n", k2);

See? Even if 4.0 / 3.0 is calculated with double precission, when you store it into k1, it looses such precission so that smaller precission version is the one that is used with pi. In the first expression, the result of 4.0f / 3.0f, double precission, is multiplied by variable pi and doesn't loose precission.

mcleod_ideafix
  • 11,128
  • 2
  • 24
  • 32
4

On most systems a float with value 124725.000000 has the hex value 0x47f39a80

So the next representable float will have the hex value 0x47f39a81 which correspond to the value 124725.0078125000

No value between 124725.000000 and 124725.0078125000 can be held by a float

So yours is rounded to the first, i.e. 124725.000000

Change all float in your program to double and you'll get another result.

You can try this code:

#include <stdio.h>
#include <stdlib.h>

typedef union
{
    unsigned u;
    float f;
} MT;

int main(void)
{
    if (sizeof(float) != sizeof(unsigned)) exit(1);
    
    MT x;
    
    x.f = 124725.0f;
    printf("x.f = %.56f\n", x.f);
    printf("x.u = 0x%08x\n", x.u);

    x.u++;    
    printf("x.f = %.56f\n", x.f);
    printf("x.u = 0x%08x\n", x.u);

    return 0;
}

output on my system

x.f = 124725.00000000000000000000000000000000000000000000000000000000
x.u = 0x47f39a80
x.f = 124725.00781250000000000000000000000000000000000000000000000000
x.u = 0x47f39a81

BTW:

I have learned so far that float variable is accurate up to six decimal points.

Well, that's not true. For some value range float can't even show a single decimal point. For some other value range float can't even show odd numbers.

Support Ukraine
  • 42,271
  • 4
  • 38
  • 63
2

You cannot squeeze an infinite number of real values into a finite number of bits, so most floating point values are only approximations of real values. Any arithmetic on floating-point values will have some amount of error in the least significant digits.

Secondly, there are real values that cannot be represented in a finite number of bits. Just like we can only approximately represent 1/3 as 0.3333333..., values like 1/10 can only be approximately represented in binary as 0.000100110011001100110011001...2

Finally, a single-precision float is only guaranteed to be accurate to 6 decimal digits total, not 6 digits after the decimal point. You can be accurate to 0.123456, 123.456000, 123456.000000, or 123456000.000000.

John Bode
  • 119,563
  • 19
  • 122
  • 198
1

Floats have finite precision. Replace them with doubles or long doubles and observe the difference.

Longer story: https://en.wikipedia.org/wiki/Single-precision_floating-point_format

Roman Pavelka
  • 3,736
  • 2
  • 11
  • 28