Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In C, During mathematical operation, why does float behave differently when it is in a variable [duplicate]

Tags:

c

So heres an example.

#include <stdio.h>

int main(void){
    int score1 = 70, score2 = 58, score3 = 99;

    float total = 3;
    printf("Average: %f\n", (score1 + score2 + score3)/total);
}

and it gives me 75.666664. whereas if I replace total with just 3.0 like this code

#include <stdio.h>

int main(void){
    int score1 = 70, score2 = 58, score3 = 99;
    printf("Average: %f\n", (score1 + score2 + score3)/3.0);
}

it gives me 75.666667.

like image 256
Aces Avatar asked Dec 07 '25 07:12

Aces


2 Answers

Everything in C has a type and that includes numeric constants/literals such as 3.0.

  • 3 has type int.
  • 3.0f has type float.
  • 3.0 has type double.

And in turn, float and double may or may not have the same precision on a certain system. Apparently double has higher precision on your system and therefore you notice a difference.


Some notes regarding implicit type promotions:

  • score1 + score2 + score3 are all of type int so no implicit promotions take place and the result is int.

  • This result int placed together in an operation with another operand of type float, means that the int will get promoted to float before the operation (as per "the usual arithmetic conversions", see the above link).

  • Or in case of 3.0, the int result would have been promoted to double.

  • printf and %f do as it happens always expect a double as input (%lf will work too), and in case you hand it a float, it will get implicitly promoted to double by an oddball rule for variadic functions called "the default argument promotions".

    But since this final type promotion happens after the result was calculated, it doesn't affect the precision.


Best practices:

  • Be explicit about types and avoid code containing silent implicit promotions.
  • Avoid mixing int/float/double in the same expression, since that's a well-known recipe for bugs. Stick to one type and explicitly cast operands where needed.

For example I would rewrite your code like this:

(double)(score1 + score2 + score3) / 3.0

The functionality is 100% equivalent, but this is self-documenting code without reliance on implicit promotion saying "I know what I'm doing".

The opposite - code relying on silent implicit promotions - says: "Either I know what I'm doing and rely on implicit promotion, or I don't know what I'm doing and just got this expression right/wrong by (bad) luck".

like image 73
Lundin Avatar answered Dec 08 '25 20:12

Lundin


It doesn't.

Your first example divides the sum by a 32bit float 3.0f and computes the closest approximation to 227/3.0f that single precision floating point computation can represent.

Your second example divides the sum by an (implicit) 64bit double 3.0 and gets the closest approximation to 227/3.0 that double precision can represent.

The distinction would be much clearer if you printed it with a format %26.18g.

I suggest that you put 75.666666666666666666666 into the decimal to binary website Base Convert IEEE754 to see how this works in practical binary FP representations under IEEE754.

Incidentally many modern optimising compilers will translate dividing by a constant at compile time into multiplying by precomputed reciprocal constant value "1.0f/3.0f" because it is much quicker than divide. Check the assembler listing to see if that is the case with your compiler.

like image 25
Martin Brown Avatar answered Dec 08 '25 21:12

Martin Brown