Rounding float to nearest integer in C
I may have a noob question here, but searching the site has stuck nothing together. I am learning to program in C and I am trying to build a function from scratch that the rounds float to the nearest integer without using math.h. Here's my code:
void main()
{
float b;
for(b = 0; b <= 2; b = b + 0.1)
{
printf("%f ", b);
printf("%i ", (int)b);
printf("%f ", b - (int)b);
printf("Nearest: ");
if((b - (int)b)<0.5)
printf("%i ", (int)b);
else
printf("%i ", (int)b + 1);
printf("Function: %i ", round_near(b));
printf("\n");
}
getchar();
}
int round_near(float b)
{
if((b - (int)b)<0.5)
return(int)b;
else
return (int)b + 1;
}
My results look like this:
Some of the code is superfluous and is only intended to show you some of the individual steps in my function. What gives? Is there some kind of float manipulation that I am not aware of?
source to share
You don't have a prototype for int round_near(float b)
, so you rely on implicit declarations.
Try adding this to your code.
int round_near (float b); // Prototype
int main(void) // Nitpick: main returns an int!
The use of implicit declarations for round_near(b)
is b
promoted to double. But the definition assumes it is a float which has a different binary layout, so you get crazy random results.
You need to make sure your code compiles without any warnings to avoid this kind of thing. The only reason implicit declaration is in the language is for backward compatibility, but every compiler in the last decade or two has warned you that this is bad at compilation.
source to share
When I tried to compile this under gcc I got the following error:
/tmp/x1.c:23: error: conflicting types for ‘round_near’
/tmp/x1.c:23: note: an argument type that has a default promotion can’t match an empty parameter name list declaration
/tmp/x1.c:16: error: previous implicit declaration of ‘round_near’ was here
The funny results you get are that your compiler didn't know the definition round_near
at the time of its first encounter and assumed it was int round_near()
. So this resulted in undefined behavior.
If you move round_near
above the main or place your ad above the main, you should get the expected results.
source to share
@QuestionC answered well the OP's immediate operation problem: the implied function signature is int round_near(...)
incompatible with int round_near(float b)
and invoking round_near(b)
that passes b
as a double
.
Easy solution: function prototype.
Some questions about round_near()
-
Casting to
int
severely narrows the legal range. Better to uselong long
. -
Common wrong functions with negative numbers. @Eugene Sh. The code should be checked for the presence of a sign.
Below is a solution that uses a range long long
as it is usually larger than a continuous range of integers a float
. Alternatively OP can replace my_roundf()
with round_near()
and use this code for testing. round_near()
doesn't work about 40% of the time.
#include <limits.h>
#include <stdio.h>
float my_roundf(float x) {
// Large `float`s typically have no fractional portion to round
if (x > LLONG_MAX / 2) return x;
if (x < LLONG_MIN / 2) return x;
return x > 0 ? (long long) (x + 0.5f) : (long long) (x - 0.5f);
}
float rand_float(void) {
union {
unsigned char uc[sizeof(float)];
float f;
} u;
do {
unsigned i;
for (i = 0; i < sizeof(float); i++) {
u.uc[i] = rand();
}
} while (u.f != u.f); // re-do if NaN encountered
return u.f;
}
void my_roundf_test(void) {
unsigned n = 100000;
while (n-- > 0) {
float x = rand_float();
float ymath = roundf(x);
// float ymy = round_near(x);
float ymy = my_roundf(x);
// Exact half-way cases may fail
if (ymath != ymy) {
printf("x:% .9e math:% .9e my:% .9e\n", x, ymath, ymy);
}
}
}
Note. For a complete answer, there are exact half cycle cases for different floating point rounding modes, negative zero, etc. But leave it for another day.
source to share
Output like -241 ... instead of 1 or 2 usually denotes uninitialized integers ... However, your code is compiled with the GNU C compiler (gcc) on Linux only after moving a function round_near
before, int main()
or just inserting an empty definition of that function (like int round_near(float b);
) before int main()
- this is "prototyping".
Otherwise your function will be "seen" as int round_near()
(see no argument definition) and therefore uninitialized integers printed by the program.
On the other hand, this practice will not create portable code, so without the changes below, your (actually C) code can compile in Visual Studio ... but not with other compilers.
Another non-topic: don't use floats in loops for
. Fel floats!
source to share