Concise way to implement round() in C?
The embedded C I'm using doesn't have a round() function it it's math lib, what would be a conci开发者_如何学Gose way to implement this in C? I was thinking of printing it to a string, looking for the decimal place, then finding the first char after the period, then rounding up if >= 5, else down. etc. Was wondering if there's something more clever.
Thanks, Fred
You could re-invent the wheel, as many other answers suggest. Alternately, you could use someone else's wheel -- I'd suggest Newlib's, which is BSD-licensed and intended for use on embedded systems. It properly handles negative numbers, NaNs, infinities, and cases which are not representable as integers (due to being too large), as well as doing so in an efficient manner that uses exponents and masking rather than generally-costlier floating-point operations. In addition, it's regularly tested, so you know it doesn't have glaring corner-case bugs in it.
The Newlib source can be a bit awkward to navigate, so here are the bits you want:
Float version: https://sourceware.org/git/gitweb.cgi?p=newlib-cygwin.git;a=blob;f=newlib/libm/common/sf_round.c;hb=master
Double version: https://sourceware.org/git/gitweb.cgi?p=newlib-cygwin.git;a=blob;f=newlib/libm/common/s_round.c;hb=master
Word-extraction macros defined here: https://sourceware.org/git/gitweb.cgi?p=newlib-cygwin.git;a=blob;f=newlib/libm/common/fdlibm.h;hb=master
If you need other files from there, the parent directory is this one: https://sourceware.org/git/gitweb.cgi?p=newlib-cygwin.git;a=tree;f=newlib/libm/common;hb=master
For the record, here's the code for the float version. As you can see, there's a bit of complexity required to deal with all the possible cases correctly.
float roundf(x)
{
int signbit;
__uint32_t w;
/* Most significant word, least significant word. */
int exponent_less_127;
GET_FLOAT_WORD(w, x);
/* Extract sign bit. */
signbit = w & 0x80000000;
/* Extract exponent field. */
exponent_less_127 = (int)((w & 0x7f800000) >> 23) - 127;
if (exponent_less_127 < 23)
{
if (exponent_less_127 < 0)
{
w &= 0x80000000;
if (exponent_less_127 == -1)
/* Result is +1.0 or -1.0. */
w |= ((__uint32_t)127 << 23);
}
else
{
unsigned int exponent_mask = 0x007fffff >> exponent_less_127;
if ((w & exponent_mask) == 0)
/* x has an integral value. */
return x;
w += 0x00400000 >> exponent_less_127;
w &= ~exponent_mask;
}
}
else
{
if (exponent_less_127 == 128)
/* x is NaN or infinite. */
return x + x;
else
return x;
}
SET_FLOAT_WORD(x, w);
return x;
}
int round(double x)
{
if (x < 0.0)
return (int)(x - 0.5);
else
return (int)(x + 0.5);
}
int round(float x)
{
return (int)(x + 0.5);
}
Caveat: Only works on positive numbers.
IEEE 754 recommends the "round half to even" approach: if the fractional part of d
is 0.5 then round to the nearest even integer. The problem is that rounding a fractional part of 0.5 the same direction introduces bias in the results; so, you have to round a fractional 0.5 up half the time and down half the time, hence the "round to the nearest even integer" bit, rounding to the nearest odd would also work as would flipping a fair coin to determine which way to go.
I think something more like this would be IEEE-correct:
#include <math.h>
int is_even(double d) {
double int_part;
modf(d / 2.0, &int_part);
return 2.0 * int_part == d;
}
double round_ieee_754(double d) {
double i = floor(d);
d -= i;
if(d < 0.5)
return i;
if(d > 0.5)
return i + 1.0;
if(is_even(i))
return i;
return i + 1.0;
}
And this one should be C99-ish (which appears to specify that numbers with fractional parts of 0.5 should be rounded away from zero):
#include <math.h>
double round_c99(double x) {
return (x >= 0.0) ? floor(x + 0.5) : ceil(x - 0.5);
}
And a more compact version of my first round_c99()
, this one handles crossing the 56bit mantissa boundary better by not relying on x+0.5
or x-0.5
being sensible things to do:
#include <math.h>
double round_c99(double d) {
double int_part, frac_part;
frac_part = modf(d, &int_part);
if(fabs(frac_part) < 0.5)
return int_part;
return int_part > 0.0 ? int_part + 1.0 : int_part - 1.0;
}
This will have problems if |int_part| >> 1
but rounding a double with a large exponent is pointless. I'm sure there are NaN in all three as well but my masochism has limits and numerical programming really isn't my thing.
Floating point computation has ample room for subtle errors so concise may not be the best requirement.
An even better solution would be to beat your compiler vendor roughly about the face and neck until they provide a proper math library.
In the ancient days when rounding was not well defined across systems, we wrote a scaled rounding function that first multiplied the number so that the rounding was done by truncating the number.
To round to 2 decimal places, multiply by 100, add .5, truncate the results and divide by 100.
This is how it was done for Numerical Control machine tools when the controls couldn't run a NC program unless it was spot on (dead nuts).
One way using a string operation
float var=1.2345;
char tmp[12]={0x0};
sprintf(tmp, "%.2f", var);
var=atof(tmp);
Another way using numeric operations
float var=1.2345;
int i=0;
var*=100;
i=var;
var=i;
var/=100;
Can you use integer ? do the following :
int roundup(int m, int n)
{
return (m + n - 1) / n ;
}
int rounddown(int m, int n)
{
return m / n;
}
int round(int m, int n)
{
int mod = m % n;
if(mod >= (n + 1) / 2)
return roundup(m, n);
else
return rounddown(m, n);
}
Here is my interpretation of the solution to rounding a double to an integer. This, of course, is not the standard for C but provides the functionality of rounding a double to the nearest integer.
int round(double n){
int trunc = (int) n;
double diff = n - (double) trunc;
if(diff < 0.5){
return trunc;
} else {
return trunc+1;
}
}
My implementation is:
int round(double x) {
double diff = +x - (int) +x;
if (x == 0) return 0;
if (x < 0) {
return (int) (+diff >= 0.5) ? x + (1 - diff) : x + (-1 - diff);
} else {
return (int) (diff >= 0.5) ? x + (1 - diff) : x - diff;
}
}
Then
#include <stdio.h>
int round(double x);
int main() {
printf("%d", round(0.6)); // returns 1
printf("%d", round(-0.6)); // returns 0
printf("%d", round(0.4)); // returns 0
printf("%d", round(-0.4)); // returns -1
return 0;
}
I hope this will be useful ^^
round()
rounds to the nearest integer, with ties rounded away from zero. This rounding mode is often not provided by hardware. It can be easily emulated via trunc()
. Where trunc()
is also not available, it can in turn be emulated via floor()
alone, or floor()
and ceil()
in combination. Which approach is the most efficient will depend on hardware capabilities and how these standard C library functions are mapped to hardware instructions.
It is easy to prototype and test the various possibilities exhaustively for float
, with the home-grown roundf()
implemented concisely as:
/* Round to nearest, ties away from zero */
float my_roundf (float a)
{
const float rndofs = 0.499999970f; // 0x1.fffffep-2
return TRUNCF (a + COPYSIGNF (rndofs, a));
}
where TRUNCF
and COPYSIGNF
are macros that either resolve to built-in functions or are emulated as discussed above. For full details see the self-contained test app below.
At present computer speeds, it is not possible to test double-precision round()
exhaustively , but one can be confident of correct operation due to the analogous construction:
/* Round to nearest, ties away from zero */
double my_round (double a)
{
const double rndofs = 0.49999999999999994; // 0x1.fffffffffffffp-2
return trunc (a + copysign (rndofs, a));
}
Here is the full C test app for exhaustive test of the design alternatives for roundf()
. It assumes that float
is mapped to the IEEE-754 binary32
type.
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <math.h>
#define BUILTIN_TRUNC (1) // truncf() is available
#define USE_FLOOR_ONLY (0) // how to construct truncf() if not available
#define BUILTIN_COPYSIGN (1) // copysignf() is available
#if BUILTIN_TRUNC
#define TRUNCF(a) truncf(a)
#else // BUILTIN_TRUNC
#define TRUNCF(a) my_truncf(a)
#endif // BUILTIN_TRUNC
#if BUILTIN_COPYSIGN
#define COPYSIGNF(a,b) copysignf((a),(b))
#else // BUILTIN_COPYSIGN
#define COPYSIGNF(a,b) copysignf_pos((a),(b))
#endif // BUILTIN_COPYSIGN
/* re-interpret the bit pattern of a float (IEEE-754 binary32) as a uint32_t */
float uint32_as_float (uint32_t a)
{
float r;
memcpy (&r, &a, sizeof r);
return r;
}
/* re-interpret the bit pattern of a uint32_t as a float (IEEE-754 binary32) */
uint32_t float_as_uint32 (float a)
{
uint32_t r;
memcpy (&r, &a, sizeof r);
return r;
}
/* Forces the sign of b onto non-negative a */
float copysignf_pos (float a, float b)
{
uint32_t ia = float_as_uint32 (a);
uint32_t ib = float_as_uint32 (b);
return uint32_as_float (ia | (ib & 0x80000000));
}
float my_truncf (float a)
{
#if USE_FLOOR_ONLY
return COPYSIGNF (floorf (fabsf (a)), a);
#else // USE_FLOOR_ONLY
return (a < 0.0f) ? ceilf (a) : floorf (a);
#endif // USE_FLOOR_ONLY
}
/* Round to nearest, ties away from zero */
float my_roundf (float a)
{
const float rndofs = 0.499999970f; // 0x1.fffffep-2
return TRUNCF (a + COPYSIGNF (rndofs, a));
}
/* Round to nearest, ties away from zero */
double my_round (double a)
{
const double rndofs = 0.49999999999999994; // 0x1.fffffffffffffp-2
return trunc (a + copysign (rndofs, a));
}
int main (void)
{
uint32_t argi, resi, refi;
float arg, res, ref;
#if BUILTIN_TRUNC
printf ("Testing roundf() implemented via truncf()\n");
#else // BUILTIN_TRUNC
#if USE_FLOOR_ONLY
printf ("Testing roundf() implemented via floorf()\n");
#else // USE_FLOOR_ONLY
printf ("Testing roundf() implemented via floorf() and ceilf()\n");
#endif // USE_FLOOR_ONLY
#endif // BUILTIN_TRUNC
argi = 0;
do {
arg = uint32_as_float (argi);
res = my_roundf (arg);
ref = roundf (arg);
/* compare bit pattern for identity */
resi = float_as_uint32 (res);
refi = float_as_uint32 (ref);
if (resi != refi) {
printf ("FAILED @ %08x (% 15.8e): res = %08x (% 15.8e) res = %08x (% 15.8e)\n",
argi, arg, resi, res, refi, ref);
return EXIT_FAILURE;
}
argi++;
} while (argi);
printf ("PASSED\n");
return EXIT_SUCCESS;
}
精彩评论