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


215

如何在C中将浮点值(例如37.777779)四舍五入到小数点后两位(37.78)?


15
您无法正确舍入数字本身,因为float(和double)不是十进制浮点数-它们是二进制浮点数-因此舍入到十进制位置是没有意义的。但是,您可以舍入输出。
帕维尔·米纳夫09年

63
这不是没有意义的。这是不精确的。有很大的不同。
布鲁克斯·摩西”于2009年

2
您期望什么样的舍入?上半场或舍入到最接近的整数?
Truthseeker Rangwan 2014年

Answers:


407

如果您只是想舍入数字以便输出,那么"%.2f"格式字符串确实是正确的答案。但是,如果您实际上想舍入浮点值以进行进一步的计算,则可以执行以下操作:

#include <math.h>

float val = 37.777779;

float rounded_down = floorf(val * 100) / 100;   /* Result: 37.77 */
float nearest = roundf(val * 100) / 100;  /* Result: 37.78 */
float rounded_up = ceilf(val * 100) / 100;      /* Result: 37.78 */

请注意,您可能要选择三种不同的舍入规则:向下舍入(即,在两个小数位后截断),四舍五入到最接近和四舍五入。通常,您想四舍五入到最接近的位置。

正如其他几个人指出的那样,由于浮点数表示的怪异,这些四舍五入的值可能不完全是“显而易见的”十进制值,但它们将非常接近。

有关舍入的更多(更多!)更多信息,尤其是关于四舍五入到最接近的平局决胜规则的更多信息,请参见Wikipedia上有关舍入的文章


4
可以对其进行修改以支持四舍五入到任意精度吗?

1
@slater当您说“任意精度”时,您是要四舍五入到例如三位而不是两位小数,还是使用实现无限精度十进制值的库?如果是前者,我希望对常数100做出明显的调整。否则,请使用您正在使用的任何多精度库,进行与上述完全相同的计算。
Dale Hagglund

2
@DaleHagglung前者,谢谢。是否用pow(10,(int)desiredPrecision)替换100的调整?

3
是的 要在k位小数点后四舍五入,请使用10 ^ k的比例因子。如果您手动写出一些十进制值并乘以10的倍数,这应该很容易看清楚。假设您正在使用值1.23456789,并将其舍入到小数点后三位。您可以使用的运算将四舍五入为整数。那么,如何移动前三个小数位,使其位于小数点的左边?希望您乘以10 ^ 3显然。现在,您可以将该值舍入为整数。接下来,通过除以10 ^ 3将三个低位数字放回去。
Dale Hagglund

1
我能以doubles某种方式使这项工作吗?似乎并没有做的工作我想:((使用floorceil)。
女士没人


42

假设您正在谈论打印价值,那么Andrew ColesonAraK的答案是正确的:

printf("%.2f", 37.777779);

但是请注意,如果您打算将该数字四舍五入到37.78以供内部使用(例如,与另一个值进行比较),那么由于浮点数的工作方式,这不是一个好主意:您通常不这样做想要对浮点进行相等比较,而是使用目标值+/-一个sigma值。或将数字编码为具有已知精度的字符串,然后进行比较。

请参阅格雷格·休吉尔(Greg Hewgill)对相关问题的解答中的链接,该链接还涵盖了为什么不应该使用浮点数进行财务计算。


1
因解决该问题背后的问题(或该问题背后的问题!)而致敬。这是很重要的一点。
Brooks Moses

实际上37.78可以精确地用浮点表示。浮点数的精度为11到12位数字。这应该足以解决3778 377.8或所有4个十进制数字。
2012年

@HaryantoCiu是的,我已经稍微修改了答案。
约翰·卡特

动态精度:printf("%.*f", (int)precision, (double)number);
Minhas Kamal

24

这个怎么样:

float value = 37.777779;
float rounded = ((int)(value * 100 + .5) / 100.0);

4
-1:a)这不适用于负数(好的,例子是正数,但仍然是)。b)您没有提到不可能将精确的十进制值存储在浮点中
约翰·卡特

32
@therefromhere:(a)你是对的(b)这是什么?高中考试?
丹尼尔,2009年

1
为什么加上0.5?
muhammad tayyab

1
必须遵循舍入规则。
Daniil

1
四舍五入规则在@Daniil评论的上下文中向最接近
Shmil猫

20
printf("%.2f", 37.777779);

如果要写入C字符串:

char number[24]; // dummy size, you should take care of the size!
sprintf(number, "%.2f", 37.777779);

@Sinan:为什么要编辑?@AraK:不,应该注意大小:)。使用snprintf()。
aib

1
@aib:我猜是因为/ ** /是C风格的注释,并且该问题标记为C
Michael Haren

5
C89仅允许/ ** /样式,C99引入了对//样式的支持。使用a脚/旧的编译器(或强制使用C89模式),将无法使用//样式。话虽如此,现在是2009年,让我们同时考虑一下C和C ++风格。
Andrew Coleson 09年

11

没有办法一轮float到另一个float,因为圆形float可能无法表示(浮点数的限制)。例如,假设您将37.777779舍入为37.78,但是最接近的可表示数字是37.781。

但是,您可以float通过使用格式字符串函数 “取整” a 。


3
这与说“无法划分两个浮点数并得到一个浮点数是没有区别的,因为除法后的结果可能无法表示”,这也许是正确的,但却无关紧要。浮点数总是不精确的,即使对于基本的东西也是如此。始终假设您实际得到的是“最接近精确舍入答案的浮点数”。
布鲁克斯·摩西,

我的意思是,您不能将a舍入float到n个小数位,然后期望结果始终具有n个小数位。您仍然会得到float,但不是您期望的。
Andrew Keeton

9

另外,如果您使用的是C ++,则可以只创建如下函数:

string prd(const double x, const int decDigits) {
    stringstream ss;
    ss << fixed;
    ss.precision(decDigits); // set # places after decimal
    ss << x;
    return ss.str();
}

然后,您可以输出任何双myDoublen的代码,例如这个小数点后地方:

std::cout << prd(myDouble,n);

7

您仍然可以使用:

float ceilf(float x); // don't forget #include <math.h> and link with -lm.

例:

float valueToRound = 37.777779;
float roundedValue = ceilf(valueToRound * 100) / 100;

这会在小数点处截断(即会产生37),他需要在小数点四舍五入到两位。
帕维尔·米纳夫09年

但是,小数点后舍入到小数点后两位是一个微不足道的变化(但仍应在答案中提及; ZeroCool,是否要添加编辑?):float roundedValue = ceilf(valueToRound * 100.0)/ 100.0;
布鲁克斯·摩西,

进入睡眠状态:)
ZeroCool

为什么这种解决方案不那么受欢迎?这完全可以用最少的代码实现。有一些警告吗?
安迪

7

在C ++中(或在具有C样式强制转换的C中),您可以创建以下函数:

/* Function to control # of decimal places to be output for x */
double showDecimals(const double& x, const int& numDecimals) {
    int y=x;
    double z=x-y;
    double m=pow(10,numDecimals);
    double q=z*m;
    double r=round(q);

    return static_cast<double>(y)+(1.0/m)*r;
}

然后std::cout << showDecimals(37.777779,2);将产生:37.78。

显然,您实际上不需要在该函数中创建所有5个变量,但我将其保留在那里,以便您可以看到逻辑。可能有更简单的解决方案,但这对我来说效果很好-特别是因为它允许我根据需要调整小数点后的位数。


5

printf为此,请始终使用功能族。即使您希望以浮点数形式获取值,也最好使用snprintf以字符串形式获取舍入后的值,然后使用以下方法将其解析回atof

#include <math.h>
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>

double dround(double val, int dp) {
    int charsNeeded = 1 + snprintf(NULL, 0, "%.*f", dp, val);
    char *buffer = malloc(charsNeeded);
    snprintf(buffer, charsNeeded, "%.*f", dp, val);
    double result = atof(buffer);
    free(buffer);
    return result;
}

我之所以这样说,是因为目前投票最多的答案以及此处的其他几个答案所示的方法-乘以100,四舍五入为最接近的整数,然后再除以100,则该方法存在两种缺陷:

  • 对于某些值,由于舍入点数不精确,乘以100会将确定舍入方向的十进制数字从4更改为5,反之亦然,因此它将向错误的方向取整
  • 对于某些值,相乘然后除以100并不是往返,这意味着即使不进行舍入,最终结果也将是错误的

为了说明第一种错误-舍入方向有时是错误的-尝试运行以下程序:

int main(void) {
    // This number is EXACTLY representable as a double
    double x = 0.01499999999999999944488848768742172978818416595458984375;

    printf("x: %.50f\n", x);

    double res1 = dround(x, 2);
    double res2 = round(100 * x) / 100;

    printf("Rounded with snprintf: %.50f\n", res1);
    printf("Rounded with round, then divided: %.50f\n", res2);
}

您将看到以下输出:

x: 0.01499999999999999944488848768742172978818416595459
Rounded with snprintf: 0.01000000000000000020816681711721685132943093776703
Rounded with round, then divided: 0.02000000000000000041633363423443370265886187553406

请注意,我们开始时的值小于0.015,因此将其四舍五入到小数点后两位时的数学正确答案为0.01。当然,不能精确地将 0.01 表示为双精度数,但是我们希望我们的结果是最接近0.01的双精度数。使用snprintf给出结果,但是使用round(100 * x) / 1000.02,这是错误的。为什么?因为100 * x给出的结果恰好是1.5。因此,乘以100会更改正确的方向以四舍五入。

为了说明第二类型的错误-结果有时是错误的,由于* 100/ 100没有真正被对方的逆-我们可以做一个类似的工作具有非常大的数字:

int main(void) {
    double x = 8631192423766613.0;

    printf("x: %.1f\n", x);

    double res1 = dround(x, 2);
    double res2 = round(100 * x) / 100;

    printf("Rounded with snprintf: %.1f\n", res1);
    printf("Rounded with round, then divided: %.1f\n", res2);
}

现在,我们的数字甚至不占分数。它是一个整数值,仅与type存储在一起double。因此,四舍五入后的结果应该与开始时的数字相同,对吗?

如果您运行上述程序,则会看到:

x: 8631192423766613.0
Rounded with snprintf: 8631192423766613.0
Rounded with round, then divided: 8631192423766612.0

哎呀。我们的snprintf方法再次返回正确的结果,但是“乘-乘-乘-乘-乘-除”方法失败。这是因为数学上正确的值8631192423766613.0 * 100863119242376661300.0是不是作为一个双精确表示; 最接近的值为863119242376661248.0。当您将其除以100时,您得到8631192423766612.0的数字与开始时的数字不同。

希望这是一个充分的演示,证明roundf舍入用于舍入到小数位的小数位,应该snprintf改为使用。如果这对您来说真是骇人听闻的破解,那么也许您会被CPython的基本功所知。


+1是一个具体示例,说明我的答案和与之相似的内容出了什么问题,这要归功于IEEE浮点数的怪异,并提供了一种直接的替代方法。很久以前,我一直在外围意识到很多工作已经付诸实践,而且对我来说,朋友们对于往返浮点值的使用很安全。我想当时完成的工作可能会出现在这里。
Dale Hagglund

哎呀...很抱歉在结尾处输入色拉一词,现在编辑为时已晚。我的意思是说“ ...为使printf和朋友们安全而付出了很多努力……”
Dale Hagglund

4

float roundf(float x)

“舍入函数将其参数舍入为浮点格式的最接近的整数值,无论当前舍入方向如何,都将中值舍入为零。C11dr§7.12.9.5

#include <math.h>
float y = roundf(x * 100.0f) / 100.0f; 

根据您的float实现,看起来可能不是一半的数字不是。因为浮点通常以2为基数。此外,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之间的“半程”,但是当转换为时,float该值1.115000009537...不再是“半程”,而是更接近1.12并四舍五入到最接近float1.120000004768...

“ 1.125”是1.12和1.13之间的“半程”,当转换为时float,该值正好1.125是“半程”。由于与偶数规则的关系,它接近1.13,并且舍入到最接近float1.129999995232...

尽管“ 1.135”是1.13和1.14之间的“半程”,但是当转换为时,float该值将1.134999990463...不再是“半程”,而是更接近1.13并四舍五入到最接近float1.129999995232...

如果使用代码

y = roundf(x*100.0f)/100.0f;

虽然“1.135”是“中途” 1.13和1.14之间,当转换为float,该值是1.134999990463...与不再是“中途”,但更接近1.13,但错误地发到float1.139999985695...由于的更有限的精度floatdouble。根据编码目标,此不正确的值可能被视为正确。


4

我做了这个宏来四舍五入浮点数。将其添加到您的标题/文件中

#define ROUNDF(f, c) (((float)((int)((f) * (c))) / (c)))

这是一个例子:

float x = ROUNDF(3.141592, 100)

x等于3.14 :)


这被截断了,但是问题要求四舍五入。此外,它在浮点运算中会舍入错误。
Eric Postpischil '18

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

3

代码定义:

#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

0

让我首先尝试证明为这个问题添加另一个答案的理由。在理想的世界中,舍入并不是什么大问题。但是,在实际系统中,您可能需要应对几个问题,这些问题可能导致舍入可能不是您期望的。例如,您可能正在执行财务计算,将最终结果四舍五入并以小数点后两位显示给用户;这些相同的值以固定的精度存储在一个数据库中,该数据库可能包含两个以上的小数位(由于各种原因;没有最佳的保留位数...取决于每个系统必须支持的特定情况,例如价格很小的商品是每单位一分钱的分数);对结果为正负ε的值执行浮点计算。这些年来,我一直在面对这些问题并发展自己的策略。我不会声称自己已遇到所有情况或给出最佳答案,但以下是到目前为止克服这些问题的一种方法示例:

假定使用以下舍入函数/方法,小数点后六位被认为足以计算浮点数/双精度数(对于特定应用是任意决定):

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,小数点后第七位为零。

对于val = 6.8249999,结果是6.839在这种情况下,小数点后第七位使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是浮点数/双精度数的所有中间计算要维持的精度。这也适用于负值。我不知道这种方法在数学上是否对所有可能性都正确。



-1

...或者您可以在没有任何库的情况下以老式的方式进行操作:

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;

当然,如果您想从号码中删除多余的信息。


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

通过左移该点并将其检查是否大于五个条件,它将浮点数转换为int。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.