如何在C语言中将浮点数值限制为小数点后两位?

268

我该如何在C语言中将浮点数(例如37.777779)四舍五入至小数点后两位(37.78)?


15
由于 float(和 double)不是十进制浮点数,而是二进制浮点数,因此无法正确对数字本身四舍五入到小数位, 因此将它们四舍五入到小数位置没有意义。 但可以对输出进行四舍五入。 - Pavel Minaev
76
不是毫无意义,而是不太准确。两者有很大的区别。 - Brooks Moses
2
你期望哪种舍入方式?四舍五入还是最近偶数舍入? - Truthseeker Rangwan
17个回答

4
使用float roundf(float x)
"round函数将其参数四舍五入为最接近的浮点格式整数值,无论当前的舍入方向如何,都会将中间情况远离零。" C11dr §7.12.9.5
#include <math.h>
// When |x| is less than FLT_MAX/100
float y = roundf(x * 100.0f) / 100.0f; 

根据你的浮点数实现方式,看似是一半的数字实际上并不是。浮点数通常是基于二进制的。此外,在所有“一半”情况下精确地四舍五入到最近的0.01是非常具有挑战性的。
void r100(const char *s) {
  float x, y;
  sscanf(s, "%f", &x);
  y = round(x*100.0)/100.0;
  printf("%6s %.12e %.12e\n", s, x, y);
}

int main(void) {
  r100("1.115");
  r100("1.125");
  r100("1.135");
  return 0;
}

 1.115 1.115000009537e+00 1.120000004768e+00  
 1.125 1.125000000000e+00 1.129999995232e+00
 1.135 1.134999990463e+00 1.139999985695e+00

尽管"1.115"在1.11和1.12之间是"中间值",但当转换为浮点数时,其值变为"1.115000009537...",不再是"中间值",而更接近于1.12,并四舍五入为最接近的浮点数"1.120000004768..."。
"1.125"在1.12和1.13之间是"中间值",但当转换为浮点数时,其值恰好为"1.125",仍然是"中间值"。由于遵循偶数规则,它向1.13进行四舍五入,并四舍五入为最接近的浮点数"1.129999995232..."。
尽管"1.135"在1.13和1.14之间是"中间值",但当转换为浮点数时,其值变为"1.134999990463...",不再是"中间值",而更接近于1.13,并四舍五入为最接近的浮点数"1.129999995232..."。
如果使用代码:
y = roundf(x*100.0f)/100.0f;

尽管"1.135"在1.13和1.14之间是"中间值",但当转换为float类型时,其值变为"1.134999990463...",不再是"中间值",而更接近于1.13,但由于float类型相对于double类型的精度更有限,错误地四舍五入为"1.139999985695..."的float类型。根据编码目标的不同,这个错误的值可能被视为正确。

4

代码定义:

#define roundz(x,d) ((floor(((x)*pow(10,d))+.5))/pow(10,d))

结果:

a = 8.000000
sqrt(a) = r = 2.828427
roundz(r,2) = 2.830000
roundz(r,3) = 2.828000
roundz(r,5) = 2.828430

3
我制作了这个用于四舍五入浮点数的宏。 将其添加到您的头文件或文件的开头即可。
#define ROUNDF(f, c) (((float)((int)((f) * (c))) / (c)))

这里有一个例子:

float x = ROUNDF(3.141592, 100)

x等于3.14 :)


1
这会截断小数,但问题要求四舍五入。此外,在浮点运算中它容易出现舍入误差。 - Eric Postpischil

3
double f_round(double dval, int n)
{
    char l_fmtp[32], l_buf[64];
    char *p_str;
    sprintf (l_fmtp, "%%.%df", n);
    if (dval>=0)
            sprintf (l_buf, l_fmtp, dval);
    else
            sprintf (l_buf, l_fmtp, dval);
    return ((double)strtod(l_buf, &p_str));

}

这里的 n 表示小数点后的位数。
例如:
double d = 100.23456;

printf("%f", f_round(d, 4));// result: 100.2346

printf("%f", f_round(d, 2));// result: 100.23

-1 有四个原因:1)缺乏解释,2)易受缓冲区溢出攻击的影响 - 如果 dval 很大,它将会溢出并可能导致崩溃,3)奇怪的 if / else 块,在每个分支中都做了完全相同的事情,4)过于复杂的使用 sprintf 来构建第二个 sprintf 调用的格式说明符;更简单的方法是只使用 .* 并将双精度值和小数位数作为参数传递给同一个 sprintf 调用。 - Mark Amery

2

或者您可以老派地不使用任何库来完成:

float a = 37.777779;

int b = a; // b = 37    
float c = a - b; // c = 0.777779   
c *= 100; // c = 77.777863   
int d = c; // d = 77;    
a = b + d / (float)100; // a = 37.770000;

当然,如果你想从数字中删除额外信息。

a远超出int范围时会失败。当c *= 100在某些情况下产生舍入时也会失败。 - chux - Reinstate Monica

2
让我先试着为加入这个问题的答案辩护。在理想情况下,舍入并不是一个大问题。然而,在实际系统中,你可能需要应对几个问题,这些问题可能导致舍入结果与你所期望的不同。例如,你可能正在进行金融计算,最终结果会四舍五入并显示为2位小数;这些相同的值以固定精度存储在数据库中,该数据库可能包括超过2位小数(由于各种原因;没有最佳保留位数...取决于每个系统必须支持的具体情况,例如价格为每个单位的一分钱的微小物品);还有,在值上执行浮点运算时,结果为正/负 epsilon。多年来,我一直在面对这些问题并发展自己的策略。我不会声称我已经面对了每种情况或者拥有最好的答案,但以下是我的方法的一个示例,可以克服这些问题:
假设6位小数被视为对浮点/双精度计算的足够精度(对于特定应用程序的任意决定),使用以下舍入函数/方法:
double Round(double x, int p)
{
    if (x != 0.0) {
        return ((floor((fabs(x)*pow(double(10.0),p))+0.5))/pow(double(10.0),p))*(x/fabs(x));
    } else {
        return 0.0;
    }
}

将结果保留两位小数以进行展示可以通过以下方式实现:
double val;
// ...perform calculations on val
String(Round(Round(Round(val,8),6),2));

对于val = 6.825,结果如预期的一样为6.83

对于val = 6.824999,结果为6.82。这里的假设是计算结果确切为6.824999,并且第7个小数位为零。

对于val = 6.8249999,结果为6.83。在这种情况下,第7个小数位为9导致Round(val,6)函数给出了预期的结果。对于此案例,可能会有任意数量的后续9

对于val = 6.824999499999,结果为6.83。首先进行到第8个小数位的四舍五入,即Round(val,8),以解决一个令人讨厌的情况——即计算得到的浮点结果计算为6.8249995,但在内部表示为6.824999499999...

最终,问题中的示例... val = 37.777779 的结果是 37.78

这种方法可以进一步概括为:

double val;
// ...perform calculations on val
String(Round(Round(Round(val,N+2),N),2));

N是浮点数/双精度浮点数进行所有中间计算时需要保留的精度。这也适用于负值。我不知道这种方法在所有情况下是否在数学上是正确的。


-2

此函数接受数字和精度参数,并返回四舍五入后的数字。

float roundoff(float num,int precision)
{
      int temp=(int )(num*pow(10,precision));
      int num1=num*pow(10,precision+1);
      temp*=10;
      temp+=5;
      if(num1>=temp)
              num1+=10;
      num1/=10;
      num1*=10;
      num=num1/pow(10,precision+1);
      return num;
}

它通过将小数点左移并检查大于五的条件来将浮点数转换为整数。

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