在C中实现round()的简洁方法?

时间:2010-12-31 22:25:25

标签: c math floating-point

我正在使用的嵌入式C没有round()函数它是数学库,在C中实现它的简洁方法是什么?我想将它打印成一个字符串,查找小数位,然后在句点之后找到第一个字符,然后如果> = 5则向上舍入,否则向下。想知道是否有更聪明的东西。

谢谢, 佛瑞德

10 个答案:

答案 0 :(得分:17)

你可以重新发明轮子,正如许多其他答案所暗示的那样。或者,您可以使用别人的方向盘 - 我建议使用Newlib,这是BSD许可的,旨在用于嵌入式系统。它正确处理负数,NaN,无穷大和不能表示为整数的情况(由于太大),以及使用指数和屏蔽而不是通常成本较高的浮点运算的有效方式。此外,它经常测试,所以你知道它没有明显的角落错误。

Newlib源可能有点难以导航,所以这里有你想要的位:

浮动版: https://sourceware.org/git/gitweb.cgi?p=newlib-cygwin.git;a=blob;f=newlib/libm/common/sf_round.c;hb=master

双版本: https://sourceware.org/git/gitweb.cgi?p=newlib-cygwin.git;a=blob;f=newlib/libm/common/s_round.c;hb=master

此处定义的字提取宏: https://sourceware.org/git/gitweb.cgi?p=newlib-cygwin.git;a=blob;f=newlib/libm/common/fdlibm.h;hb=master

如果你需要其他文件,那么父目录是这样的: https://sourceware.org/git/gitweb.cgi?p=newlib-cygwin.git;a=tree;f=newlib/libm/common;hb=master

记录中,这是float版本的代码。如您所见,正确处理所有可能的情况需要一些复杂性。

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

答案 1 :(得分:13)

int round(double x)
{
    if (x < 0.0)
        return (int)(x - 0.5);
    else
        return (int)(x + 0.5);
}

答案 2 :(得分:7)

int round(float x)
{
    return (int)(x + 0.5);
}

警告:仅适用于正数。

答案 3 :(得分:7)

IEEE 754建议采用“舍入一半到均匀”的方法:如果d的小数部分为0.5,则舍入到最接近的偶数整数。问题是在相同方向上舍入0.5的小数部分会在结果中引入偏差;所以,你必须将一小部分0.5上半部分时间向下舍入一半时间,因此“舍入到最接近的偶数整数”位,四舍五入到最接近的奇数也会起作用,因为会翻转一个公平的硬币以确定哪条路去。

我认为更像这样的事情将是IEEE纠正的:

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

这个应该是C99-ish(似乎指出0.5的小数部分的数字应该从零开始舍入):

#include <math.h>
double round_c99(double x) {
    return (x >= 0.0) ? floor(x + 0.5) : ceil(x - 0.5);
}

我的第一个round_c99()的更紧凑版本,这个版本更好地处理56位尾数边界,而不依赖于x+0.5x-0.5是明智的事情:

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

如果|int_part| >> 1会出现问题,但使用大指数舍入双精度是没有意义的。我确信三者中都有NaN,但我的受虐狂有限制,数字编程确实不是我的事。

浮点计算有足够的空间容易出现细微错误,因此简明可能不是最佳要求。

一个更好的解决方案是在你的编译器供应商大致面对面之前,直到他们提供一个合适的数学库。

答案 4 :(得分:2)

在古代整个系统中没有明确定义舍入时,我们编写了一个缩放舍入函数,该函数首先乘以数字,以便通过截断数字来完成舍入。
要舍入到2个小数位,乘以100,加上.5,截断结果并除以100 这就是数控机床的工作方式,当控制装置无法运行NC程序时,除非它是现场(死螺母)。

答案 5 :(得分:0)

使用字符串操作的一种方法

float var=1.2345;
char tmp[12]={0x0};
sprintf(tmp, "%.2f", var);
var=atof(tmp);

使用数字运算的另一种方法

float var=1.2345;
int i=0;
var*=100;
i=var;
var=i;
var/=100;

答案 6 :(得分:0)

你能用整数吗?执行以下操作:

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

答案 7 :(得分:0)

这是我对将双精度数舍入为整数的解决方案的解释。当然,这不是C的标准,而是提供了将double舍入到最接近的整数的功能。

int round(double n){
    int trunc = (int) n;
    double diff = n - (double) trunc;
    if(diff < 0.5){
        return trunc;
    } else {
        return trunc+1;
    }
}

答案 8 :(得分:0)

我的实现是:

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

然后

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

我希望这会有用^^

答案 9 :(得分:0)

round()四舍五入到最接近的整数,并且四舍五入关系从零舍入。硬件通常不提供这种舍入模式。可以通过trunc()轻松地模拟它。如果trunc()也不可用,则可以依次通过单独的floor()或结合使用floor()ceil()进行仿真。哪种方法最有效取决于硬件功能以及这些标准C库函数如何映射到硬件指令。

对于float来说,很容易就可以穷尽地对各种可能性进行原型设计和测试,而将自己生成的roundf()简洁地实现为:

/* 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));
}

其中TRUNCFCOPYSIGNF是解析为内置函数或如上所述仿真的宏。有关详细信息,请参见下面的自包含测试应用程序。

以目前的计算机速度,不可能详尽地测试双精度round(),但是由于类似的构造,人们可以确信正确的操作:

/* 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));
}

这里是完整的C测试应用程序,用于对roundf()的设计替代方案进行详尽的测试。假定float映射到IEEE-754 binary32类型。

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