我如何限制一个浮点值,只有两个地方小数点后,在C?

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

963238 次浏览

printf中使用% .2f。它只打印2个小数点。

例子:

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

输出:

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

如果你想写入C-string:

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

没有办法将float舍入为另一个float,因为舍入后的float可能不可表示(浮点数的限制)。例如,假设你将37.777779四舍五入为37.78,但最接近的数字是37.781。

但是,您可以使用格式化字符串函数将可以“舍入”float

假设你谈论的是打印的值,那么安德鲁Coleson阿拉克的答案是正确的:

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

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

参见Greg Hewgill对相关问题的回答中的链接,其中还介绍了为什么不应该使用浮点数进行财务计算。

你仍然可以使用:

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

例子:

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

这个怎么样:

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

如果只是为了输出目的而四舍五入,那么"%.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 */

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

正如其他几个人指出的那样,由于浮点表示法的特殊性,这些四舍五入的值可能并不完全是“明显的”十进制值,但它们非常非常接近。

有关舍入的更多(很多!)信息,特别是舍入到最接近的打结规则,请参见维基百科上关于舍入的文章

在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个变量,但我把它们留在那里,这样你就可以看到逻辑。可能有更简单的解决方案,但这对我来说很有效——特别是因为它允许我根据需要调整小数点后的位数。

另外,如果你使用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();
}

然后,你可以输出任意双myDouble,小数点后有n位,代码如下:

std::cout << prd(myDouble,n);
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

使用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,并舍入到1.120000004768...中最接近的float

"1.125"是介于1.12和1.13之间的"half-way",当转换为float时,值正好是1.125并且是"half-way"。由于与偶数规则的关系,它四舍五入到1.13,并四舍五入到1.129999995232...中最接近的float

虽然"1.135"是介于1.13和1.14之间的"中间值",但当转换为float时,值为1.134999990463...,不再是"中间值",而是更接近1.13,并舍入到1.129999995232...中最接近的float

如果使用代码

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

虽然“1.135”是介于1.13和1.14之间的“中间值”,但当转换为float时,值为1.134999990463...,不再是“中间值”,而是更接近1.13,但由于float相对于double的精度更有限,不正确舍入为1.139999985695...float。这个不正确的值可能被视为正确的,这取决于编码目标。

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

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

它通过左移浮点数并检查大于5的条件将浮点数转换为int。

我做了这个宏为浮点数舍入。 将它添加到你的头文件/文件

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

这里有一个例子:

float x = ROUNDF(3.141592, 100)

X = 3.14:)

首先,让我尝试证明我为这个问题增加另一个答案的理由。在理想的情况下,舍入并不是什么大问题。然而,在实际系统中,您可能需要处理几个问题,这些问题可能导致舍入结果可能不是您所期望的。例如,您可能正在执行财务计算,最终结果是四舍五入,并显示为小数点后两位;这些相同的值以固定的精度存储在数据库中,该数据库可能包含超过2位小数点后数位(由于各种原因;没有最佳的存放位置,这取决于每个系统必须支持的特定情况,例如每单位价格不到一美分的小物品);浮点计算的结果是正/负。这些年来,我一直在面对这些问题,并制定了自己的策略。我不会说我遇到过所有的情况,也不会说我有最好的答案,但下面是我迄今为止克服这些问题的方法的一个例子:

假设小数点后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;
}
}

四舍五入到小数点后2位的结果可以这样表示:

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)函数给出预期的结果。在这种情况下,后面可以有任意数量的__abc2。

对于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是浮点数/双精度数上所有中间计算所要维持的精度。这也适用于负值。我不知道这种方法在数学上是否适用于所有可能性。

代码定义:

#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

...或者你也可以采用传统的方式,不需要任何库:

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;

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

为此,始终使用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) / 100会得到0.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);
}

我们的数字甚至没有小数部分;它是一个整数值,只是存储在double类型中。四舍五入后的结果应该和一开始的数一样,对吧?

如果你运行上面的程序,你会看到:

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

哦。snprintf方法再次返回正确的结果,但先乘后舍再除的方法失败。这是因为数学上正确的8631192423766613.0 * 100值,863119242376661300.0,不能精确地表示为double;最接近的值是863119242376661248.0。当你把它除以100,你得到8631192423766612.0 -一个不同于你开始的数字。

希望这是一个充分的证明,使用roundf舍入到小数点后数位是坏的,你应该使用snprintf代替。如果你觉得这是一个可怕的黑客,也许你会放心,因为它是基本上就是CPython所做的