在 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.555double 不是“一半”(其值为2.5550000000000001598721115546022541821002960205078125),因此“一半”规则将不适用于它。只要您使用两位小数四舍五入到最接近的值,结果将是“2.56”。对中点值应用什么规则并不重要。 您确实意识到,如果您使用的是 C 双精度,那么几乎没有可精确表示的小数中途情况?例如,您说要对2.565 进行四舍五入,但是在典型的机器上,如果您写成double x = 2.565;,那么由于2.565 不能以二进制浮点格式精确表示,因此存储的实际值对于x2.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.1251.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_MAXDBL_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 这样的样本编号具有最接近的double2.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_MININT_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# 中将数字四舍五入到小数点后两位?

C语言里,double类型的数据可以精确到小数点后几位?

c语言中浮点数四舍五入 。 保留一个浮点数小数点后的6位,第3位要四舍五入。如 1.1234.567到1234.570000

python中小数点后取2位(四舍五入)以及取2位(四舍五不入)

在Javascript中将小数添加到字符串[重复]

Android-四舍五入到小数点后2位[重复]