如何在C语言中将浮点数四舍五入为最接近的整数?

12

有没有办法在C语言中对数字进行四舍五入?

我不想使用ceil和floor。还有其他替代方案吗?

当我搜索答案时,我发现了这段代码片段:

(int)(num < 0 ? (num - 0.5) : (num + 0.5))

即使 float num = 4.9,上面的代码行始终打印值为 4。


有许多不同类型的舍入 - 你想使用哪种?请提供所需行为的示例。 - anon
3
我认为问题出在别的地方,对于输入4.9,它应当明确地输出5。 - IVlad
是的,将浮点类型转换为可以表示所需符号和大小的整数类型应该只需截断小数部分即可实现;此代码执行±0.5以使此截断将原始值四舍五入远离零。 - Arkku
1
“ceil”和“floor”有什么问题?另外,参见https://dev59.com/rXRB5IYBdhLWcg3w3K4J。 - kennytm
请参见https://dev59.com/cHE95IYBdhLWcg3wrP6w,只需将`static_cast<int>替换为(int)`。 - Earlz
显示剩余2条评论
11个回答

14

4.9 + 0.5等于5.4,除非你的编译器有严重问题,否则不可能将其舍入为4。

我刚刚确认了搜索到的代码对于4.9给出了正确的答案。

marcelo@macbookpro-1:~$ cat round.c 
#include <stdio.h>

int main() {
    float num = 4.9;
    int n = (int)(num < 0 ? (num - 0.5) : (num + 0.5));
    printf("%d\n", n);
}
marcelo@macbookpro-1:~$ make round && ./round
cc     round.c   -o round
5
marcelo@macbookpro-1:~$

1
比较是因为该语句适用于一般情况(其中num可以是正数或负数),而不是他示例程序中的特定情况。如果问题的整个重点是“如何将4.9四舍五入到5?”,那么我们只需放置float num = 5.0即可完成。 - Dan Story
2
使用./gcc -frip-fabric-of-space-time编译会生成4。Emacs有一个插件默认开启此功能。 - Tim Post
  1. num 超出 int 范围时,此方法将失败。2) 它会对许多其他的 float 失败,除非它使用 double 数学运算。例如:当 num + 0.5f 四舍五入为 float 结果时,(int)(num < 0 ? (num - 0.5f) : (num + 0.5f)) 有许多失败的值。
- chux - Reinstate Monica
2016年嵌入式处理器的部署数量远超其他平台 - 每年数十亿。如此普遍并不奇怪。C语言在这里非常流行,许多进入该领域的人由于假设int为32位而感到困惑。 - chux - Reinstate Monica
@chux 然后使用 int32_t。 - Marcelo Cantos
显示剩余6条评论

13

在 C 语言中,要对 float 进行四舍五入,有三个 <math.h> 函数可供使用。建议使用 rintf()

float nearbyintf(float x);

nearbyint 函数将其参数四舍五入为浮点格式的整数值,使用当前的舍入方向而不引发“不精确”的浮点异常。C11dr §7.12.9.3 2

或者

float rintf(float x);

rint 函数与 nearbyint 函数(7.12.9.3)的区别在于如果结果与参数的值不同,rint函数可能会引发“不精确”浮点异常。 C11dr §7.12.9.4 2

或者

float roundf(float x);

round函数将其参数四舍五入为浮点格式中最接近的整数值,对于四舍五入的中间情况,向远离零的方向舍入,不考虑当前的舍入方向。参见C11dr §7.12.9.6 2。


示例

#include <fenv.h>
#include <math.h>
#include <stdio.h>

void rtest(const char *fname, double (*f)(double x), double x) {
  printf("Clear inexact flag       :%s\n", feclearexcept(FE_INEXACT) ? "Fail" : "Success");
  printf("Set round to nearest mode:%s\n", fesetround(FE_TONEAREST)  ? "Fail" : "Success");

  double y = (*f)(x);
  printf("%s(%f) -->  %f\n", fname,x,y);

  printf("Inexact flag             :%s\n", fetestexcept(FE_INEXACT) ? "Inexact" : "Exact");
  puts("");
}

int main(void) {
  double x = 8.5;
  rtest("nearbyint", nearbyint, x);
  rtest("rint", rint, x);
  rtest("round", round, x);
  return 0;
}

输出

Clear inexact flag       :Success
Set round to nearest mode:Success
nearbyint(8.500000) -->  8.000000
Inexact flag             :Exact

Clear inexact flag       :Success
Set round to nearest mode:Success
rint(8.500000) -->  8.000000
Inexact flag             :Inexact

Clear inexact flag       :Success
Set round to nearest mode:Success
round(8.500000) -->  9.000000
Inexact flag             :Exact

OP的代码存在哪些问题?


(int)(num < 0 ? (num - 0.5) : (num + 0.5))
  1. 如果num的值不接近int范围,那么进行强制转换(int)将导致未定义的行为。

  2. num +/- 0.5得到一个不精确的答案时。这在此处不太可能发生,因为0.5是一个double类型,使得加法以比float更高的精度发生。当num0.5具有相同的精度时,将0.5加到一个数字上可能会产生数值上的四舍五入答案。(这并不是OP帖子中的整数四舍五入。)例如:刚好小于0.5的数字应该按照OP的目标四舍五入为0,但num + 0.5得到的是一个精确的答案,在1.0和小于1.0的最小double之间。由于无法表示精确答案,那个总和会被四舍五入,通常会导致错误的答案。对于大数也会出现类似的情况。


根据OP所述,关于 "即使float num = 4.9,上述行始终将值打印为4" 的困境是无法被解释的。需要提供额外的代码/信息。我怀疑OP可能使用了int num = 4.9;


// avoid all library calls
// Relies on UINTMAX_MAX >= FLT_MAX_CONTINUOUS_INTEGER - 1
float my_roundf(float x) {
  // Test for large values of x 
  // All of the x values are whole numbers and need no rounding
  #define FLT_MAX_CONTINUOUS_INTEGER  (FLT_RADIX/FLT_EPSILON)
  if (x >= FLT_MAX_CONTINUOUS_INTEGER) return x;
  if (x <= -FLT_MAX_CONTINUOUS_INTEGER) return x;

  // Positive numbers
  // Important: _no_ precision lost in the subtraction
  // This is the key improvement over OP's method
  if (x > 0) {
    float floor_x = (float)(uintmax_t) x;
    if (x - floor_x >= 0.5) floor_x += 1.0f;
    return floor_x;
  }

  if (x < 0) return -my_roundf(-x);
  return x; //  x is 0.0, -0.0 or NaN
}

测试了一下 - 有时间时会再次测试。


6

我不确定这是一个好主意。那段代码依赖于类型转换,而我相当肯定精确的截断是未定义的。

float result = (num - floor(num) > 0.5) ? ceil(num) : floor(num);

我认为这是一种更好的方式(基本上就是Shiroko发布的内容),因为它不依赖于任何强制转换。

4

一般解决方法是使用rint()函数,并根据需要设置FLT_ROUNDS舍入模式。


2
我认为您需要的是以下内容: int n = (d - floor(d) > 0.5) ? ceil(d) : floor(d); 这行代码用于四舍五入,如果小数部分大于0.5,则向上取整,否则向下取整。

3
如果提问者真的不想使用ceil和floor,但是真的愿意使用内置的转换为int的方法,那么这个问题可以归类为“古怪的面试风格问题,其中涉及一些不自然的限制,以充分说明只有面试官才能完全理解的某些观点。”不幸的是,这不能用一个标签来概括。 - Steve Jessop
Steve,你说得对。这是面试中经常被问到的问题。 - webgenius

2

谷歌的代码运行正确。其背后的思想是:当小数小于0.5时向下取整,否则向上取整。(int)将浮点数强制转换为整型,从而舍去小数部分。如果你给一个正数加上0.5,它会被舍入到下一个整数。如果你从一个负数中减去0.5,它也会做同样的事情。


0

将值x舍入到精度p,其中0<p<无穷大。(例如0.25、0.5、1、2等)

float RoundTo(float x, float p)
{
  float y = 1/p;
  return int((x+(1/(y+y)))*y)/y;
}

float RoundUp(float x, float p)
{
  float y = 1/p;
  return int((x+(1/y))*y)/y;
}

float RoundDown(float x, float p)
{
  float y = 1/p;
  return int(x*y)/y;
}

您发布的内容在C语言中无法编译,因为此帖子被标记为C语言。也许您是使用其他语言进行编码? - chux - Reinstate Monica
此外,在C++中,如果x*(...)超出了int的范围,则使用int(...)会失败。 - chux - Reinstate Monica
这是Arduino C/C++,如果它不是“真正”的C,请见谅。在那里,int(float x)只返回整数(去除小数部分)。什么是去除小数部分的“正确”方法? - JJussi
float中去除小数的“正确”方法是tuncf(x) - chux - Reinstate Monica
double trunc(double x) 适用于 double。使用 float truncf(float x); 来处理 float()。如果使用 float x; return double trunc(x) 返回一个 float(),这可能会浪费计算资源。 - chux - Reinstate Monica

0
int round(double x)
{
return x >= 0.0 ? int(x + 0.5) : int(x - int(x-1) + 0.5) + int(x-1);
}

相比于使用 ceil 和 floor 的版本,这个版本会更快。


不是C语言解决方案。int(x + 0.5)在C语言中无效。算法对于x接近0.5但小于0.5,以及x不在int范围内的情况会失败。 - chux - Reinstate Monica

0

你可以在fenv.h中使用fesetround()(C99中引入)。可能的参数是宏FE_DOWNWARDFE_TONEARESTFE_TOWARDZEROFE_UPWARD,但请注意,并非所有这些参数都被定义 - 只有平台/实现支持的参数才会被定义。然后,您可以在math.h中使用各种roundrintnearbyint函数(也是C99)。这样,您可以设置所需的舍入行为一次,并调用相同的函数,无论值是正数还是负数。

(例如,对于lround,通常不需要设置舍入方向即可获得所需结果。)


-1

只需将数字加0.5并进行类型转换即可。 然后通过将其强制转换为整数来打印它。 否则,您可以使用round()函数,其中只需将参数传递为相应的数字。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接