2013-03-13 75 views
3

我需要一个函数来将一个正整数加到最接近的整数。潜伏aorund我发现这非常优雅的方式floor()表现奇怪

int x = floor(y + 0.5); 

我写了一个简单的测试程序:

double a = 10.0; 

for (int i = 0; i < 10; i++) { 
    cout << a << "\t" << a + 0.5 << "\t" << floor(a + 0.5) << endl; 
    a += 0.1; 
} 

,但我收到一些奇怪的输出

10  10.5 10 
10.1 10.6 10 
10.2 10.7 10 
10.3 10.8 10 
10.4 10.9 10 
10.5 11  10 <--- should be 11! 
10.6 11.1 11 
10.7 11.2 11 
10.8 11.3 11 
10.9 11.4 11 

whay是什么?

感谢 问候 卢卡

+2

双打和浮点(变量存储的是在浮点标准中)只是近似值。精度不会与精确的十进制值绑定。 – suspectus 2013-03-13 09:19:52

+0

http://stackoverflow.com/questions/5562492/strange-results-with-c-ceiling-function – uba 2013-03-13 09:26:27

+1

有关浮点计算的大量细节:http://docs.oracle.com/cd/E19957-01/806 -3568/ncg_goldberg.html - 网上还有不错的PDF版本。但是glglgl的回答已经说过了。 – Arne 2013-03-13 09:29:05

回答

2

这里的输出,它使用printf代替:

printf("%.15f\t%.15f\t%.15f\n", a, a + 0.5, floor(a + 0.5));

不精确现在很清楚:

10.000000000000000 10.500000000000000 10.000000000000000 
10.100000000000000 10.600000000000000 10.000000000000000 
10.199999999999999 10.699999999999999 10.000000000000000 
10.299999999999999 10.799999999999999 10.000000000000000 
10.399999999999999 10.899999999999999 10.000000000000000 
10.499999999999998 10.999999999999998 10.000000000000000 
10.599999999999998 11.099999999999998 11.000000000000000 
10.699999999999998 11.199999999999998 11.000000000000000 
10.799999999999997 11.299999999999997 11.000000000000000 
10.899999999999997 11.399999999999997 11.000000000000000 
+0

没有必要将问题与'printf'混淆。只需在'std :: cout'上设置精度即可。 – 2013-03-13 09:50:12

+0

@JamesKanze - 之前的问题是用C标记的。它应该仍然清楚发生了什么。 – teppic 2013-03-13 09:53:27

3

加入0.1,你确实添加稍低于0.1的值。

所以加入0.1 5次与添加0.5次不一样;你没有完全达到那个值。再次加入.5,你不会超过11,这会产生你观察到的行为。


AC程序,如

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

int main() 
{ 
    double a = 10.0; 
    int i; 
    for (i = 0; i < 11; i++) { 
     printf("%4.19f\t%4.19f\t%4.19f\n", a, a+.5, floor(a + 0.5)); 
     a += 0.1; 
    } 
    printf("\n"); 
    for (i = 0; i < 11; i++) { 
     a = 10.0 + i/10.0; 
     printf("%4.19f\t%4.19f\t%4.19f\n", a, a+.5, floor(a + 0.5)); 
    } 
} 

显示了它的输出

10.0000000000000000000 10.5000000000000000000 10.0000000000000000000 
10.0999999999999996447 10.5999999999999996447 10.0000000000000000000 
10.1999999999999992895 10.6999999999999992895 10.0000000000000000000 
10.2999999999999989342 10.7999999999999989342 10.0000000000000000000 
10.3999999999999985789 10.8999999999999985789 10.0000000000000000000 
10.4999999999999982236 10.9999999999999982236 10.0000000000000000000 
10.5999999999999978684 11.0999999999999978684 11.0000000000000000000 
10.6999999999999975131 11.1999999999999975131 11.0000000000000000000 
10.7999999999999971578 11.2999999999999971578 11.0000000000000000000 
10.8999999999999968026 11.3999999999999968026 11.0000000000000000000 
10.9999999999999964473 11.4999999999999964473 11.0000000000000000000 

10.0000000000000000000 10.5000000000000000000 10.0000000000000000000 
10.0999999999999996447 10.5999999999999996447 10.0000000000000000000 
10.1999999999999992895 10.6999999999999992895 10.0000000000000000000 
10.3000000000000007105 10.8000000000000007105 10.0000000000000000000 
10.4000000000000003553 10.9000000000000003553 10.0000000000000000000 
10.5000000000000000000 11.0000000000000000000 11.0000000000000000000 
10.5999999999999996447 11.0999999999999996447 11.0000000000000000000 
10.6999999999999992895 11.1999999999999992895 11.0000000000000000000 
10.8000000000000007105 11.3000000000000007105 11.0000000000000000000 
10.9000000000000003553 11.4000000000000003553 11.0000000000000000000 
11.0000000000000000000 11.5000000000000000000 11.0000000000000000000 

的区别:第一运行与累加错误并与0.0999999999999996447步骤你的方法,而第二次运行会尽可能地重新计算,从而可以准确地达到10.5和11.0。

+0

如果OP增加“0.125”,他们会更好,因为“1/8”可以精确表示。 – 2013-03-13 09:41:50

+1

@PeterWood如果OP增加了0.125,他很可能没有意识到可能存在问题。 – 2013-03-13 09:52:28

+0

@JamesKanze对不起,是的,OP需要明白有一个问题。我的意思是帮助他们思考为什么有些数字比其他数字更好。 – 2013-03-13 09:55:59

0

该问题是由于四舍五入错误的积累。浮点数在内部不是整数,它们的值大多是近似值。所以你每次执行时都会累积舍入误差a + = .1a + .5操作,结果就是你得到的结果。

您可以尝试在增量修改看看不行,但在使用下面的表达式(通常它使大规模更好的结果):

a = 10. + .1 * i; 
+1

这是朝着正确方向迈出的一步,但这里的问题是'0.1'不能完全表示。使用'10。 +(i/10)'应该给出正确的结果,但是:所有涉及的数字都可以精确地表示,'i'为'5'时的结果也可以。 (当'i'是'1'时,结果仍然不是'10.1',但是对于舍入,我们只关心结果是否接近'something.5'。) – 2013-03-13 09:49:09

+0

需要注意的是舍入错误在'.1'的**转换**中为浮点值。一个粗略的类比将是'int i = 0; i + = 3/2;我+ = 3/2;'。 “i”的结果值是2,而不是3. – 2013-03-13 12:09:15

0

问题是,正如其他人指出的,你从来没有 实际上有10.5;你只有非常接近的东西 到10.5(但非常小)。

作为一般规则,对于这种事情,你应该而不是是 增加一个增量到浮点值。你应该 只使用积分增量,它的规模每次去 浮点值:

for (int i = 0; i != 10; ++ i) { 
    double aa = a + (i/10.); 
    std::cout << aa << '\t' << aa + 0.5 << '\t' << floor(aa + 0.5) << std::endl; 
} 

这应该给你想要的结果。

当然,如果你的例子只是一个测试...很大程度上取决于 如何计算四舍五入的值。您正在使用的实际舍入 可能是合适的。或者,如果你知道 值应该是0.1倍数,你可以尝试做 算术10缩放,然后轮结果,然后轮 10的倍数