舍入浮点数 - .5 - sprintf

10

我正在使用以下代码将数字保留两位小数:

sprintf(temp,"%.2f",coef[i]); //coef[i] returns a double

它成功将6.666四舍五入为6.67,但在将5.555四舍五入时却无法正常工作。它返回了5.55,然而我认为应该返回5.56。

如何使它在下一位数字为5时向上舍入?即返回5.56。

编辑:我现在意识到这是因为当我用cin输入5.555时,它被保存为5.554999997。

我将尝试分两个阶段进行舍入-先进行三位小数的四舍五入,然后再进行两位小数的四舍五入。还有其他(更优雅的)想法吗?

5个回答

13

看起来您需要使用 math round 函数进行正确的四舍五入。

printf("%.2f %.2f\n", 5.555, round(5.555 * 100.)/100.);

在我的机器上,这会产生以下输出:

5.55 5.56

这似乎比加入0.0005的想法更可靠,但我想两种方法都可以。谢谢你的帮助! - Meir
7
@ypnos,这并不是bug,只是你还没掌握浮点数的精度限制。5.5551被表示为5.555100000000000370...,会四舍五入;而5.555被表示为5.5549999999...,会向下取整。你可以通过在你的代码中使用5.5500000000000000001来尝试,两个输出都是错误的5.55,因为当数字被存储时它不是5.55000...1,而是5.549999...。由此可见这是一个表示问题。 - paxdiablo
我非常了解浮点数的限制。我在这里看到的错误是不一致性,虽然你可能会认为这是一个预期的错误(由于格式的限制)。感谢您对正确答案的投票! - ypnos
1
我没有给你@ypnos点踩,这不是我的风格,因为我们的回答是“竞争”的关系,我宁愿让更广泛的社区来决定。无论如何,你的回答没有任何问题,只是你之后发表的评论有些问题——这不是一个bug。IEE754的限制是众所周知和记录的(除了那些仍然在SO上提问的人们之外)。只有当实际行为与记录的行为不匹配时才会出现错误;但这里并非如此。 - paxdiablo
3
事实上,我给你点赞是因为你非常有礼貌(也因为我无法指责你的方法,整数比浮点数更容易处理)。 - paxdiablo

11

在IEEE754中,数字5.555无法被表示为一个精确数。使用"%.50f"打印常数5.555的结果为:

5.55499999999999971578290569595992565155029300000000

所以它将会被向下取整。尝试使用这个代替:

printf ("%.2f\n",x+0.0005);

需要注意的是,虽然这个表达式可以将数字四舍五入到最接近的整数,但你需要小心那些可被精确表示的数字,因为它们会被错误地四舍五入。

你需要了解浮点数表示的限制。如果获取准确值很重要,你可以使用(或编写)一个BCD或其他十进制类,它不具有IEEE754表示的缺陷。


这并不影响你的答案,但我只是好奇:你用什么语言打印了5.55499999999999971578290569595992565155029300000000?实际上应该是5.55499999999999971578290569595992565155029296875000。 - Rick Regan
@Rick,很可能是在CygWin下使用gcc,但是一年前的事情很难记得了 :-) 在Ubuntu10下我得到了和你一样的结果。但是我认为双精度浮点数只能保证15位小数的精度,所以它可能使用了更宽的内部格式(我模糊地记得至少有一个系统的计算在内部使用了80位,但那已经是很久以前的事情了——要想得到像你的数字一样的48位小数,你需要大约160位的精度)。 - paxdiablo
好的,谢谢。我知道它不能是Visual C++(它无法准确打印那么多位数),而且对于在Linux上使用gcc/glibc来说,它似乎位数太少了(尽管可以打印所有位数)。无论如何,您可以使用标准double(53位)获得这么多位数 - 它是double所持有的精确十进制表示(即使它可能只是对其分配给它的内容的15位数字近似)。 - Rick Regan

2
这里有另一种可能的解决方案:
printf("%.2f", _nextafter(n, n*2));

这个想法是通过浮点数学中最小可能表示的数量,将数字增加到不为零(n * 2可以得到正确的符号)。

例如:

double n=5.555;
printf("%.2f\n", n);
printf("%.2f\n", _nextafter(n, n*2));
printf("%.20f\n", n);
printf("%.20f\n", _nextafter(n, n*2));

使用MSVC编译后的结果为:

5.55
5.56
5.55499999999999970000
5.55500000000000060000

1
这个问题标记为C++,所以我会在这个假设下继续。请注意,与C的printf系列不同,C++流会四舍五入。你只需要提供想要的精度,streams库就会为你进行四舍五入。我只是这样说是因为如果你没有理由不使用streams的话,这很有帮助。

-2

你也可以这样做(节省乘除操作):

printf("%.2f\n", coef[i] + 0.00049999);

1
这将把 5.549999 四舍五入到 5.56 ;-) - ypnos
@ypnos,在你发布之前,你真的需要检查一下事情——它根本不会给你5.56。 - paxdiablo
1
对不起,我误解了。但你应该能理解我的观点。5.544999 + 0.0005 被四舍五入成5.55。实际上它应该被四舍五入成5.54。这并不需要什么天才才能意识到。加入偏差并不能神奇地解决舍入问题而不引入新的问题。 - ypnos
我会downvote的少数答案之一。这完全是错误的。 - John Dibling

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