为什么双加时而向右,时而错了吗?(Why double plus sometimes right,s

2019-07-04 03:48发布

我知道,Java有双精度的缺陷,但为什么有时,近似结果是正确的,但有时并非如此。

这样的代码:

for ( float value = 0.0f; value < 1.0f; value += 0.1f )  
    System.out.println( value );

结果是这样的:

0.0
0.1
0.2
0.3
...
0.70000005
0.8000001
0.9000001

Answer 1:

当你的状态,不是所有的数字可以准确地在IEEE754表示。 在与Java使用打印这些号码的规则相结合,影响什么,你会看到的。

有关背景,我将简要介绍了IEEE754不准确。 在这种特殊情况下, 0.1不能精确表示,所以你经常会发现使用的实际数量是一样的东西0.100000001490116119384765625

见这里对于为什么会是这样分析。 你得到的“不准确”的价值观的原因是因为错误( 0.000000001490116119384765625 )逐渐增加了。


之所以0.10.2 (或类似的数字)并不总是显示错误与Java中的打印代码,而不是本身的实际价值的事情。

尽管0.1其实比你预期的要高一点,是打印出来的代码不会给你所有的数字。 你会发现,如果你设置格式字符串中的小数点后提供50个数字,你会再看看真正的价值。

为Java决定如何打印出一个浮点数(没有明确的格式)的规则详细说明这里 。 对于数字计数的相关位是:

必须有至少一个数来表示小数部分,并且除此之外,需要来唯一地区分型浮子的相邻值的参数值,作为许多,但仅作为许多,多个数字。

举例来说,这里是你展示它是如何工作的一些代码:

public class testprog {
    public static void main (String s[]) {
        float n; int i, x;
        for (i = 0, n = 0.0f; i < 10; i++, n += 0.1f) {
            System.out.print( String.format("%30.29f %08x ",
                n, Float.floatToRawIntBits(n)));
            System.out.println (n);
        }
    }
}

这样做的输出是:

0.00000000000000000000000000000 00000000 0.0
0.10000000149011611938476562500 3dcccccd 0.1
0.20000000298023223876953125000 3e4ccccd 0.2
0.30000001192092895507812500000 3e99999a 0.3
0.40000000596046447753906250000 3ecccccd 0.4
0.50000000000000000000000000000 3f000000 0.5
0.60000002384185791015625000000 3f19999a 0.6
0.70000004768371582031250000000 3f333334 0.70000005
0.80000007152557373046875000000 3f4cccce 0.8000001
0.90000009536743164062500000000 3f666668 0.9000001

第一列是浮子的实际值,包括从IEEE754局限性不准确性。

第二列是浮点值的32位整数表示(它的外观在存储器而不是其实际的整数值),为低电平位表示检查的值是有用的。

最后一列是你所看到的,当你刚刚打印出不带格式的数字。


现在看一些更多的代码,它会告诉你如何都不断增加的不准确的值会给你错了号码, 与周围的价值观差异如何控制打印什么不准确之处:

public class testprog {
    public static void outLines (float n) {
        int i, val = Float.floatToRawIntBits(n);
        for (i = -1; i < 2; i++) {
            n = Float.intBitsToFloat(val+i);
            System.out.print( String.format("%30.29f %.08f %08x ",
                n, n, Float.floatToRawIntBits(n)));
            System.out.println (n);
        }
        System.out.println();
    }
    public static void main (String s[]) {
        float n = 0.0f;
        for (int i = 0; i < 6; i++) n += 0.1f;
        outLines (n); n += 0.1f;
        outLines (n); n += 0.1f;
        outLines (n); n += 0.1f;
        outLines (0.7f);
    }
}

此代码使用续加入0.1起床0.6然后打印出该和相邻的浮筒的值。 的输出是:

0.59999996423721310000000000000 0.59999996 3f199999 0.59999996
0.60000002384185790000000000000 0.60000002 3f19999a 0.6
0.60000008344650270000000000000 0.60000008 3f19999b 0.6000001

0.69999998807907100000000000000 0.69999999 3f333333 0.7
0.70000004768371580000000000000 0.70000005 3f333334 0.70000005
0.70000010728836060000000000000 0.70000011 3f333335 0.7000001

0.80000001192092900000000000000 0.80000001 3f4ccccd 0.8
0.80000007152557370000000000000 0.80000007 3f4cccce 0.8000001
0.80000013113021850000000000000 0.80000013 3f4ccccf 0.80000013

0.69999992847442630000000000000 0.69999993 3f333332 0.6999999
0.69999998807907100000000000000 0.69999999 3f333333 0.7
0.70000004768371580000000000000 0.70000005 3f333334 0.70000005

看的第一件事是,最后一列在每个块的中间线足够的小数位,以从周围的线区分开来(按照前面提到的爪哇打印规格)。

例如,如果你只有小数点后三位,你将无法区分0.60.6000001 (相邻位模式0x3f19999a0x3f19999b )。 因此,它打印尽可能多的,因为它需要。

你会发现的第二件事情是,我们0.7的第二块值不是 0.7 。 相反,它是0.70000005 尽管有更紧密的位模式,以这个数字(上一行)。

这是通过因添加错误的逐渐积累造成0.1 。 您可以从最终块,如果你只是用来看看0.7 ,而不是直接不断加入0.1 ,你会得到正确的值。

所以,你的具体情况,这是造成你的问题, 后一个问题。 你得到的事实0.70000005打印出来是不是因为Java还没有得到足够接近的近似值(有),这是因为你到了道路0.7摆在首位。

如果修改了上面的代码包含:

outLines (0.1f);
outLines (0.2f);
outLines (0.3f);
outLines (0.4f);
outLines (0.5f);
outLines (0.6f);
outLines (0.7f);
outLines (0.8f);
outLines (0.9f);

你会发现它可以在该组中正确地打印出所有的数字。



文章来源: Why double plus sometimes right,sometimes wrong?
标签: java double