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