16

The embedded C I'm using doesn't have a round() function it it's math lib, what would be a concise 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

mu is too short
  • 426,620
  • 70
  • 833
  • 800
fred basset
  • 9,774
  • 28
  • 88
  • 138
  • 2
    Note that `round()` is in C99, so it's not necessarily in all C libraries anyway. – chrisaycock Dec 31 '10 at 22:32
  • 1
    Is floor or ceil functions available? – Cratylus Dec 31 '10 at 22:37
  • 4
    Rounding is quite pointless, only humans care about it. The only ones that get a bit impatient about having to read 7 or 15 digits in the result. The machine doesn't care, it doesn't get tired. Not a problem, printf() rounds quite nicely. Just in case: don't implement an accounting program in C without having a library that calculates in base 10. – Hans Passant Dec 31 '10 at 23:09
  • Out of curiosity, which antediluvian math library are you being subjected to? – Stephen Canon Jan 01 '11 at 03:13
  • 2
    @HansPassant Reality cares about it, if you calculate the times something has to be done or the amount of pieces you need. This is not quite pointless. – Amin Negm-Awad Mar 31 '17 at 06:54
  • @AminNegm-Awad You normally would never round for amount of pieces you need. You should use `ceil` instead. Same for times something has to be done: you'd use `ceil`. For example, say you have 22 items to iterate. And you loop through them in batches of 10. Rounding will give you two loops and that is wrong. You need 3 loops to process all of the items. – slebetman Sep 28 '20 at 22:39

10 Answers10

20
int round(double x)
{
    if (x < 0.0)
        return (int)(x - 0.5);
    else
        return (int)(x + 0.5);
}
dan04
  • 87,747
  • 23
  • 163
  • 198
  • 2
    The C library function `round( )` returns a double, not an int. – Stephen Canon Dec 31 '10 at 23:11
  • 11
    Note also that this implementation has undefined behavior for `x` outside the range of values representable by integers. – Stephen Canon Dec 31 '10 at 23:28
  • 7
    Try the above code on 0.49999999999999994; on implementations that use 64-bit `double` for computations, adding 0.5 to that value will yield 1.0 even though it should round down. – supercat Mar 19 '15 at 19:03
  • @supercat: I believe this is intended. – Daniel May 31 '16 at 10:27
  • 2
    @Daniel: How is `round` specified? Do the specs allow for a value which is definitely below 0.5 to round up to 1.0? – supercat May 31 '16 at 14:32
  • @supercat: I have the same problem at the moment. Some .5 values have .499999... as the nearest matching float value. I am currently trying to implement some logic that makes my round function to consider these value to be rounded up. Else you wouldn't be able to have round(0.005,2)=0.01! Welcome to floating point hell. – Daniel May 31 '16 at 14:39
  • @Daniel: For that sort of thing, I'd suggest rounding, and then computing the lowest value that should round to the given value, and adjusting the result if it's not right. Not the fastest approach, but it should be accurate. – supercat May 31 '16 at 18:39
20

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;
}
LThode
  • 1,843
  • 1
  • 17
  • 28
Brooks Moses
  • 9,267
  • 2
  • 33
  • 57
11
int round(float x)
{
    return (int)(x + 0.5);
}

Caveat: Only works on positive numbers.

nmichaels
  • 49,466
  • 12
  • 107
  • 135
  • 3
    You're forgetting about negative numbers: round(-1.3) = 0 in your case. – Niki Yoshiuchi Dec 31 '10 at 22:43
  • 5
    @Niki: I'm not forgetting about them; I just chose to ignore them. Adding support for them is trivial (see dan04's answer) and my goal was to minimally illustrate a method other than the stringy one the OP described. – nmichaels Dec 31 '10 at 22:45
  • 2
    Since casting will round toward zero, this will only work if x is positive. – splicer Dec 31 '10 at 22:46
  • @splicer: Post edited. See my other comment for justification. Happy now? – nmichaels Dec 31 '10 at 22:48
  • 2
    The C library function round( ) returns a double, not an int. – Stephen Canon Dec 31 '10 at 23:12
  • 2
    And the argument should be `double x` not `float x`, floats would be `float roundf(float x)`. I think you'll also get undefined behavior if `x` is outside the `[INT_MIN,INT_MAX]` interval. – mu is too short Jan 01 '11 at 00:44
8

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.

mu is too short
  • 426,620
  • 70
  • 833
  • 800
  • 1
    `round( )` is fully specified by C99, and is distinct from the IEEE rounding functions. Note also that your function has undefined behavior if `d` is too large to be rounded to an `int`. – Stephen Canon Dec 31 '10 at 23:26
  • @Stephen Canon: C99 specifies "round away from 0 on ties"? I can get around the undefined behavior quite easily if you have a handy even test for doubles, I haven't had to mess around with floating point issues since my data mining days in the 1990s. There's a reason for the last two sentences in my answer. – mu is too short Dec 31 '10 at 23:44
  • I think Stephen Canon's issues are resolved. I still think thrashing the compiler vendor for an incomplete math library is the most desirable approach here. – mu is too short Jan 01 '11 at 00:02
  • +1 for complaining to the compiler vendor. (And for the most correct answer here -- though you still need to tweak it: if `d` is negative, then so is `frac_part`, and the comparison agains `0.5` doesn't do the right thing). – Stephen Canon Jan 01 '11 at 00:06
  • Right, I think I fixed the negative fractional part handling. Now I really want to have a little "discussion" with that compiler vendor, this still isn't as bad as having to edit SCO's system headers back in the day but I think some violence is still justified. – mu is too short Jan 01 '11 at 00:17
  • I updated my original `round_c99()` with a concise version after some discussions with a friend that's up to his eyeballs in numerics work. The old one worked fine but was rather more convoluted than I'd like. – mu is too short Jan 01 '11 at 01:54
  • The new version doesn't work; consider the value `x = 2^52 + 1`. `x + 0.5` is not representable as a double-precision number, and so it is rounded. It happens to be an exact halfway case (the two closest values are 2^52 + 1 and 2^52 + 2. Because ties round to even, the result is 2^52 + 2, which is then preserved by `floor`. The right answer, however is `x` itself. This is easily remedied, of course, by simply returning x if |x| >= 2^52. – Stephen Canon Jan 01 '11 at 02:26
  • I know there are still edge cases but my masochism has limits. Also, if you start dealing with large numbers then rounding makes little sense. And, if you care about numerical correctness you'd be using the IEEE method rather than the C99/banker method or just rounding the mantissa on its own rather than trying to round the entire double. – mu is too short Jan 01 '11 at 02:43
  • 1
    I disagree. One of the chief virtues of IEEE-754 is that it requires reasonable results for all inputs, even unreasonable ones. If you care about numerical correctness and require the behavior of the C99 `round( )` function, you will use said function, and not the IEEE rounding functions. The C standard (rightly) specifies that the function behave sensibly over the entire range, and it is folly not to adhere to that. (Of course, that doesn't mean that every answer on SO need be pedantically complete; the germ of the idea here is spot on, and you're free to leave it at that =) – Stephen Canon Jan 01 '11 at 02:51
  • 2
    Note that this all just goes to show the wisdom of your original suggestion: **beat your compiler vendor roughly about the face and neck until they provide a proper math library.** These are precisely the sort of issues that we library implementors are paid to worry about, and users of our libraries *should not* have to deal with them. – Stephen Canon Jan 01 '11 at 02:59
  • Fair enough, I put a cleaner version of my original `round_c99()` back. Which type of rounding you'd use (IEEE or C99) would depend on how you need to handle bias issues and that depends on your exact circumstances more than on what the standards committees have to say; that's probably why it took until C99 to get `round()` in the standard at all. I agree that one of the nice things about the C standards is that most things are well and fully specified. I'm a mathematician by training and **R** is continuous so `x+0.5` always works :) – mu is too short Jan 01 '11 at 03:02
2

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).

Dave
  • 1,234
  • 13
  • 24
0

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);
}
sampathsris
  • 21,564
  • 12
  • 71
  • 98
Hao
  • 1
  • 1
0

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;
jim mcnamara
  • 16,005
  • 2
  • 34
  • 51
0

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;
    }
}
0

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 ^^

Rabios
  • 77
  • 1
  • 7
0

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;
}
njuffa
  • 23,970
  • 4
  • 78
  • 130