关于数学:在C中实现round()的简便方法?

Concise way to implement round() in C?

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

谢谢,
弗雷德


正如许多其他答案所暗示的那样,您可以重新发明轮子。或者,您可以使用其他人的轮子-我建议您使用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版本的代码。如您所见,正确处理所有可能的情况需要一些复杂性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
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
2
3
4
5
6
7
int round(double x)
{
    if (x < 0.0)
        return (int)(x - 0.5);
    else
        return (int)(x + 0.5);
}


1
2
3
4
int round(float x)
{
    return (int)(x + 0.5);
}

注意:仅适用于正数。


IEEE 754建议使用"舍入到偶数"方法:如果d的小数部分为0.5,则舍入到最接近的偶数整数。问题在于,将0.5的小数部分沿相同方向取整会在结果中产生偏差。因此,您必须将一半时间的分数向上舍入为0.5,一半时间的分数向下舍入为零,因此"舍入到最接近的偶数整数"位,舍入到最接近的奇数位也将有效,就像翻转公平硬币来确定采用哪种方式走。

我认为更像这样的东西是IEEE正确的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#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格式(它似乎指定小数部分为0.5的数字应四舍五入为零):

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

并且是我的第一个round_c99()的更紧凑的版本,它通过不依赖x+0.5x-0.5做明智的事情来更好地处理越过56bit尾数边界:

1
2
3
4
5
6
7
8
#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,但是受虐狂有一定的局限性,数字编程确实不是我的事。

浮点计算有足够的空间处理细微的错误,因此简洁可能不是最佳要求。

更好的解决方案是在编译器供应商提供合适的数学库之前,先将他们的面孔和脖子打败。


在远古的系统中,舍入没有很好地定义,我们编写了一个缩放的舍入函数,该函数首先将数字相乘,以便通过截断数字来进行舍入。
要四舍五入到小数点后两位,请乘以100,再加上.5,截断结果再除以100。
当控件无法运行NC程序(除非发现)(死螺母)时,这就是数控机床的操作方式。


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

轻松实现float的原型并全面测试各种可能性,而自产的roundf()则简洁地实现为:

1
2
3
4
5
6
/* 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(),但是由于类似的构造,人们可以确信正确的操作:

1
2
3
4
5
6
/* 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类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
#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()\
"
);
#else // BUILTIN_TRUNC
#if USE_FLOOR_ONLY
    printf ("Testing roundf() implemented via floorf()\
"
);
#else // USE_FLOOR_ONLY
    printf ("Testing roundf() implemented via floorf() and ceilf()\
"
);
#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)\
"
,
                    argi, arg, resi, res, refi, ref);
            return EXIT_FAILURE;
        }
        argi++;
    } while (argi);
    printf ("PASSED\
"
);
    return EXIT_SUCCESS;
}

我的实现是:

1
2
3
4
5
6
7
8
9
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;
    }
}

然后

1
2
3
4
5
6
7
8
9
10
11
12
13
#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;
}

我希望这会有用^^


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

1
2
3
4
5
6
7
8
9
int round(double n){
    int trunc = (int) n;
    double diff = n - (double) trunc;
    if(diff < 0.5){
        return trunc;
    } else {
        return trunc+1;
    }
}

可以使用整数吗?请执行下列操作 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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);
}

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

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

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

1
2
3
4
5
6
float var=1.2345;
int i=0;
var*=100;
i=var;
var=i;
var/=100;