在 C 中将正值四舍五入到小数点后 2 位
Posted
技术标签:
【中文标题】在 C 中将正值四舍五入到小数点后 2 位【英文标题】:Round positive value half-up to 2 decimal places in C 【发布时间】:2014-09-26 02:41:51 【问题描述】:通常情况下,四舍五入到小数点后两位很容易
printf("%.2lf",<variable>);
但是,舍入系统通常会舍入到最近的偶数。例如,
2.554 -> 2.55
2.555 -> 2.56
2.565 -> 2.56
2.566 -> 2.57
而我想要实现的是
2.555 -> 2.56
2.565 -> 2.57
事实上,四舍五入在 C 中是可行的,但仅适用于整数;
int a = (int)(b+0.5)
所以,我问的是如何在正值上使用 2 位小数而不是整数来执行与上述相同的操作,以实现我之前所说的打印。
【问题讨论】:
发挥一些想象力。四舍五入到百位与四舍五入到大 100 倍的整数相同。 请注意,您写为2.555
的double
不是“一半”(其值为2.5550000000000001598721115546022541821002960205078125),因此“一半”规则将不适用于它。只要您使用两位小数四舍五入到最接近的值,结果将是“2.56”。对中点值应用什么规则并不重要。
您确实意识到,如果您使用的是 C 双精度,那么几乎没有可精确表示的小数中途情况?例如,您说要对2.565
进行四舍五入,但是在典型的机器上,如果您写成double x = 2.565;
,那么由于2.565
不能以二进制浮点格式精确表示,因此存储的实际值对于x
是2.564999999999999946709294817992486059665679931640625
。因此,对于这种情况,您的问题的正确解决方案应该向下取整,而不是向上取整。如果您希望它四舍五入,您需要解释原因,并指定您通常想要的精确行为。
您的整数“四舍五入”可能无法满足您对 -ve 值的要求。如果b
= -2.5; a
= 2 而不是 3。您需要指定向上、向下、接近零或远离零的舍入。
@TruthseekerRangwan:是的,但是您的问题目前是不一致的-您不能有round-ties-away-from-zero 并且有最接近@ 987654334@四舍五入。其中一个必须改变 - 要么你想要从零开始做些什么,要么你接受 2.565
将向下舍入。
【参考方案1】:
不清楚您是否真的想要“round half-up”,或者更确切地说是“round half away from zero”,这需要对负值进行不同的处理.
单精度二进制 float
至少精确到小数点后 6 位,double
精确到 20,因此通过 DBL_EPSILON(在 float.h 中定义)微调 FP 值将导致向上舍入到下一个第 100 位printf( "%.2lf", x )
用于 n.nn5 值。不影响非 n.nn5
double x2 = x * (1 + DBL_EPSILON) ; // round half-away from zero
printf( "%.2lf", x2 ) ;
对于不同的舍入行为:
double x2 = x * (1 - DBL_EPSILON) ; // round half-toward zero
double x2 = x + DBL_EPSILON ; // round half-up
double x2 = x - DBL_EPSILON ; // round half-down
【讨论】:
向零舍入可能是“常规的”,但 OP 正确地观察到 C 的*printf
函数的正常行为是将平局舍入为偶数。您可以通过测试实际的完全可表示的中途情况(如0.125
、1.375
等)轻松地在您自己的机器上进行测试。我不认为它实际上是由语言规范强制执行的:最接近的规范是(对于 C99 ) 附件 F,第 5 节,第 2 段,其中说应该尊重当前的舍入模式(通常是平局对偶数),但在实践中肯定很常见。
@MarkDickinson :您所指的是实际答案的旁白;看来我可能处于摇摇欲坠的境地。为避免与问题无关的讨论,我删除了该段落。
当 FP 值接近 0.001 的倍数时,这是可行的,但对于像 2.5449
这样的值会失败,并打印“2.55”。也许轻推double x2 = x*(1+DBL_EPSILON);
?
@chux :您的建议更好 - 它适用于 2.54499999999999
,它有 15 个有效数字,即保证 double
的十进制精度。这也是处理 -ve 值的一种更简洁的方式。
另一种想法:将x
微调到下一个最大浮点数的新C 方法是使用nextafter(x, 2*x)
。【参考方案2】:
以下是将double
舍入到最接近的0.01 double
的精确代码。
像x = round(100.0*x)/100.0;
这样的代码函数,除了它处理使用操作来确保按 100.0 进行缩放时完全没有精度损失。
这可能比 OP 感兴趣的代码更多,但它确实有效。
它适用于整个double
范围-DBL_MAX
到DBL_MAX
。 (仍然应该做更多的单元测试)。
这取决于FLT_RADIX == 2
,这很常见。
#include <float.h>
#include <math.h>
void r100_best(const char *s)
double x;
sscanf(s, "%lf", &x);
// Break x into whole number and fractional parts.
// Code only needs to round the fractional part.
// This preserves the entire `double` range.
double xi, xf;
xf = modf(x, &xi);
// Multiply the fractional part by N (256).
// Break into whole and fractional parts.
// This provides the needed extended precision.
// N should be >= 100 and a power of 2.
// The multiplication by a power of 2 will not introduce any rounding.
double xfi, xff;
xff = modf(xf * 256, &xfi);
// Multiply both parts by 100.
// *100 incurs 7 more bits of precision of which the preceding code
// insures the 8 LSbit of xfi, xff are zero.
int xfi100, xff100;
xfi100 = (int) (xfi * 100.0);
xff100 = (int) (xff * 100.0); // Cast here will truncate (towards 0)
// sum the 2 parts.
// sum is the exact truncate-toward-0 version of xf*256*100
int sum = xfi100 + xff100;
// add in half N
if (sum < 0)
sum -= 128;
else
sum += 128;
xf = sum / 256;
xf /= 100;
double y = xi + xf;
printf("%6s %25.22f ", "x", x);
printf("%6s %25.22f %.2f\n", "y", y, y);
int main(void)
r100_best("1.105");
r100_best("1.115");
r100_best("1.125");
r100_best("1.135");
r100_best("1.145");
r100_best("1.155");
r100_best("1.165");
return 0;
【讨论】:
【参考方案3】:[编辑] OP 澄清只有 打印的 值需要四舍五入到小数点后 2 位。
OP 的观察认为,每“舍入到偶数”或“从零舍入”对数字进行“中途”舍入具有误导性。在 0.005、0.015、0.025、... 0.995 等 100 个“中途”数字中,通常只有 4 个完全是“中途”:0.125、0.375、0.625、0.875。这是因为浮点数格式使用 base-2 并且无法精确表示 2.565 之类的数字。
相反,像2.565
这样的样本编号具有最接近的double
值2.564999999999999947...
,假设binary64。将该数字四舍五入到最接近的 0.01 应该是 2.56,而不是 OP 所希望的 2.57。
因此只有以 0.125 和 0.625 区域结尾的数字正好在中途向下舍入,而不是按照 OP 的要求向上舍入。建议接受并使用:
printf("%.2lf",variable); // This should be sufficient
为了接近 OP 的目标,可以 A) 测试以 0.125 或 0.625 结尾的数字,或者 B) 略微增加。最小的增加是
#include <math.h>
printf("%.2f", nextafter(x, 2*x));
使用@Clifford 发现了另一种轻推方法。
[将double
舍入到最接近的double
0.01 倍数的前一个答案]
典型的浮点使用像binary64 这样的格式,它使用base-2。 “四舍五入到最接近的数学 0.01 并远离 0.0”具有挑战性。
正如@Pascal Cuoq 所提到的,像2.555
这样的浮点数通常只在2.555
附近,并且具有像2.555000000000000159872...
这样更精确的值,不是一半。
下面的@BLUEPIXY 解决方案是最好和实用的。
x = round(100.0*x)/100.0;
"round 函数将其参数四舍五入到最接近的浮点整数值 格式,从零开始舍入一半大小写,无论当前舍入方向如何。”C11dr §7.12.9.6。
((int)(100 * (x + 0.005)) / 100.0)
方法有两个问题:对于负数(OP 未指定),它可能以错误的方向舍入,并且整数通常具有比double
小得多的范围(INT_MIN
到 INT_MAX
)。
在某些情况下,当 double x = atof("1.115");
最终接近 1.12 时,它实际上应该是 1.11
,因为 1.115
,因为 double
确实更接近 1.11
而不是“中途” ”。
string x rounded x
1.115 1.1149999999999999911182e+00 1.1200000000000001065814e+00
OP 没有指定负数的四舍五入,假设为y = -f(-x)
。
【讨论】:
除了浮点问题,round
解决方案的一个问题是 MSVC 不提供round
函数。 (或者至少,我上次没有看到。)不过,在大多数 Unix-y 平台上,这通常不是问题。
啊,我收回了。看起来它 is 在 Visual Studio 2013 中:msdn.microsoft.com/en-us/library/dn353646.aspx。不确定是哪个版本添加的;看起来它可能是 VS 2013 中的新功能。
@MarkDickinson : VC++ round() - 自 VC++ 2012 以来,它现在由 C++11 和 C99 定义。我的 VS2013 链接有一个“其他版本”链接 - 不知道你的为什么没有 - 那是你的 MSDN - 通常无法导航!以上是关于在 C 中将正值四舍五入到小数点后 2 位的主要内容,如果未能解决你的问题,请参考以下文章
c语言中浮点数四舍五入 。 保留一个浮点数小数点后的6位,第3位要四舍五入。如 1.1234.567到1234.570000