为什么加倍有时是正确的,有时候是错的? [英] Why double plus sometimes right,sometimes wrong?

查看:211
本文介绍了为什么加倍有时是正确的,有时候是错的?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我知道java有双精度陷阱,但是为什么有时候,近似结果是可以的,但是有时候是不行的。



这样的代码:

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

结果如下:

  0.0 
0.1
0.2
0.3
...
0.70000005
0.8000001
0.9000001


解决方案

正如你所说,不是所有的数字都可以在IEEE754中正确表示。结合Java用于打印这些数字的规则,这将影响您将看到的内容。



对于背景,我将简要介绍IEEE754的不准确之处。在这种特殊情况下, 0.1 无法正确表示,因此您经常会发现使用的实际数字类似于 0.100000001490116119384765625



请参阅 here 分析为什么是这样的。您得到不准确值的原因是因为该错误( 0.000000001490116119384765625 )逐渐加起来。






为什么 0.1 0.2 (或类似数字) t总是显示该错误与Java中的打印代码有关,而不是实际值本身。



即使 0.1 实际上比您预期的要高一点,打印出来的代码不会给你所有的数字。您会发现,如果您将格式字符串设置为在小数点后传递50位数字,那么您将看到真正的值。



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


必须至少有一个数字来表示小数部分,而且除此之外,但是只需要很多的数字,才能将参数值与float类型的相邻值唯一区分开来。


作为示例,这里有一些代码显示了它的工作原理:

  public class testprog {
public static void main ]){
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); (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; (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

首先要看的是,最后一列在每个块的中间线都有足够的小数位数d从周围的线路(根据前面提到的Java打印规格)熄灭它。



例如,如果你在小数点后只有三个地方,你将无法区分 0.6 0.6000001 (相邻位模式 0x3f19999a 0x3f19999b )。所以,它打印尽可能多的。



你会注意到的第二件事是我们的 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);

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


I know that java has double precision pitfalls, but why sometimes, the approximation result is ok, but sometimes isn't.

code like this:

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

result like this:

0.0
0.1
0.2
0.3
...
0.70000005
0.8000001
0.9000001

解决方案

As you state, not all numbers can be represented exactly in IEEE754. In conjunction with the rules that Java uses for printing those numbers, that affects what you'll see.

For background, I'll briefly cover the IEEE754 inaccuracies. In this particular case, 0.1 cannot be represented exactly so you'll often find that the actual number used is something like 0.100000001490116119384765625.

See here for the analysis of why this is so. The reason you're getting the "inaccurate" values is because that error (0.000000001490116119384765625) gradually adds up.


The reason why 0.1 or 0.2 (or similar numbers) don't always show that error has to do with the printing code in Java, rather than the actual value itself.

Even though 0.1 is actually a little higher than what you expect, the code that prints it out doesn't give you all the digits. You'd find, if you set the format string to deliver 50 digits after the decimal, that you'd then see the true value.

The rules for how Java decides to print out a float (without explicit formatting) are detailed here. The relevant bit for the digit count is:

There must be at least one digit to represent the fractional part, and beyond that as many, but only as many, more digits as are needed to uniquely distinguish the argument value from adjacent values of type float.

By way of example, here's some code showing you how this works:

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);
        }
    }
}

The output of this is:

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

The first column is the real value of the float, including inaccuracies from IEEE754 limitations.

The second column is the 32-bit integer representation of the floating point value (how it looks in memory rather than its actual integer value), useful for checking the values at the low level bit representation.

The final column is what you see when you just print out the number with no formatting.


Now looking at some more code, which will show you both how the inaccuracies of continuously adding an inexact value will give you the wrong number, and how the differences with surrounding values controls what is printed:

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);
    }
}

This code uses the continued addition of 0.1 to get up to 0.6 then prints out the values for that and adjacent floats. The output of that is:

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

The first thing to look at is that the final column has enough fractional digits in the middle lines of each block to distinguish it from the surrounding lines (as per the Java printing specifications mentioned previously).

For example, if you only had three places after the decimal, you would not be able to distinguish between 0.6 and 0.6000001 (the adjacent bit patterns 0x3f19999a and 0x3f19999b). So, it prints as much as it needs.

The second thing you'll notice is that our 0.7 value in the second block is not 0.7. Rather, it's 0.70000005 despite the fact that there's an even closer bit pattern to that number (on the previous line).

This has been caused by the gradual accumulation of errors caused by adding 0.1. You can see from the final block that, if you just used 0.7 directly rather than continuously adding 0.1, you'd get the right value.

So, in your particular case, it's the latter issue causing you the problems. The fact that you're getting 0.70000005 printed out is not because Java hasn't got a close enough approximation (it has), it's because of the way you got to 0.7 in the first place.

If you modify that code above to contain:

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);

you'll find it can print out all the numbers in that group correctly.

这篇关于为什么加倍有时是正确的,有时候是错的?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆