java中的除法

java中的除法,java,floating-point,Java,Floating Point,我在Java中有一个简单的划分: float f = 19.7f/100; System.out.println(f); // 0.19700001 double d = 19.7/100; System.out.println(d); // 0.19699999999999998 为什么会发生这种情况?神秘派发布的链接是必须阅读的,但有点厚。尝试一个更友好的版本 tl;dr是浮点运算总是要舍入的,而double由于精度更高,舍入方式与float不同。这有点像55四舍五入到最接近的10将是6

我在Java中有一个简单的划分:

float f = 19.7f/100;
System.out.println(f); // 0.19700001

double d = 19.7/100;
System.out.println(d); // 0.19699999999999998

为什么会发生这种情况?

神秘派发布的链接是必须阅读的,但有点厚。尝试一个更友好的版本

tl;dr是浮点运算总是要舍入的,而double由于精度更高,舍入方式与float不同。这有点像55四舍五入到最接近的10将是60,但四舍五入到最接近的100将是100


在这种情况下,无论是浮点数还是双精度数,都不能精确地表示十进制数0.197(或19.7),因此每个数字都可以表示最接近该值的数字。double可以更接近,因为它更精确。

这是有史以来最常被问到的问题之一,所以我在这里讲几点

  • 计算机只能表示有限数量的数字,因此在存储数字并随后对其进行除法时,必须进行舍入。这种舍入自然会产生误差,但是如果你只想要,比如说,3位数的精度,那么它们在你的情况下就不重要了

  • 舍入的行为有点不可预测,因为计算机以二进制存储数字。因此,19.7是一个终止的十进制数,而相同的数字是二进制的重复十进制数——10011.101100110011。。。因此,您可以看到,在任意点舍入将产生从终止的十进制表达式无法预测的行为


  • 不是因为除法,问题是1.7f!=1.7由于精度损失。我们可以看看我们的值的位表示

        float f = 19.7f; 
        double d = 19.7;
        System.out.println(Double.doubleToLongBits(f)); 
        System.out.println(Double.doubleToLongBits(d));
    
    输出

    4626238274938077184
    4626238274723328819
    

    Java使用IEEE754浮点数来处理其浮点和双精度。本标准设计用于ise基数2,该基数不能准确表示基数10。看这里

    下面的示例并不完全是标准,只是为了让您了解为什么基数2浮点不适用于其他基数

    base2 = base10 0001 = 0001 -> from 0*8 + 0*4 + 0*2 + 1*1 0010 = 0002 -> from 0*8 + 0*4 + 1*2 + 0*1 0011 = 0003 -> from 0*8 + 0*4 + 1*2 + 1*1 0100 = 0004 -> from 0*8 + 1*4 + 0*2 + 0*1 0101 = 0005 -> from 0*8 + 1*4 + 0*2 + 1*1 8 = 2^3, 4 = 2^2, 2=2^1 and 1 = 2^0 Then base2 = base10 .0000 = .0000 -> from 0*1 + 0.5*0 + 0.25*0 + 0.125*0 + 0.0625*0 .0001 = .0625 -> from 0*1 + 0.5*0 + 0.25*0 + 0.125*0 + 0.0625*0 .0010 = .1250 -> from 0*1 + 0.5*0 + 0.25*0 + 0.125*0 + 0.0625*0 .0011 = .1875 -> from 0*1 + 0.5*0 + 0.25*0 + 0.125*0 + 0.0625*0 .0100 = .2500 -> from 0*1 + 0.5*0 + 0.25*0 + 0.125*0 + 0.0625*0 .0101 = .3125 -> from 0*1 + 0.5*0 + 0.25*0 + 0.125*0 + 0.0625*0 .0110 = .3750 -> from 0*1 + 0.5*0 + 0.25*0 + 0.125*0 + 0.0625*0 .0111 = .4375 -> from 0*1 + 0.5*0 + 0.25*0 + 0.125*0 + 0.0625*0 .1000 = .5000 -> from 0*1 + 0.5*0 + 0.25*0 + 0.125*0 + 0.0625*0 .1001 = .5625 -> from 0*1 + 0.5*0 + 0.25*0 + 0.125*0 + 0.0625*0 .1010 = .6250 -> from 0*1 + 0.5*0 + 0.25*0 + 0.125*0 + 0.0625*0 .1011 = .6875 -> from 0*1 + 0.5*0 + 0.25*0 + 0.125*0 + 0.0625*0 .1100 = .7500 -> from 0*1 + 0.5*0 + 0.25*0 + 0.125*0 + 0.0625*0 .1101 = .8125 -> from 0*1 + 0.5*0 + 0.25*0 + 0.125*0 + 0.0625*0 .1110 = .8700 -> from 0*1 + 0.5*0 + 0.25*0 + 0.125*0 + 0.0625*0 .1111 = .9325 -> from 0*1 + 0.5*0 + 0.25*0 + 0.125*0 + 0.0625*0 1 = 2^0, 0.5 = 2^-1, 0.25=2^-2 and 0.125 = 2^-3 base2=base10 0001=0001->从0*8+0*4+0*2+1*1 0010=0002->从0*8+0*4+1*2+0*1 0011=0003->从0*8+0*4+1*2+1*1 0100=0004->从0*8+1*4+0*2+0*1 0101=0005->从0*8+1*4+0*2+1*1 8=2^3,4=2^2,2=2^1和1=2^0 然后 base2=base10 .0000=.0000->从0*1+0.5*0+0.25*0+0.125*0+0.0625*0 .0001=.0625->从0*1+0.5*0+0.25*0+0.125*0+0.0625*0 .0010=.1250->从0*1+0.5*0+0.25*0+0.125*0+0.0625*0 .0011=.1875->从0*1+0.5*0+0.25*0+0.125*0+0.0625*0 .0100=.2500->从0*1+0.5*0+0.25*0+0.125*0+0.0625*0 .0101=.3125->从0*1+0.5*0+0.25*0+0.125*0+0.0625*0 .0110=.3750->从0*1+0.5*0+0.25*0+0.125*0+0.0625*0 .0111=.4375->从0*1+0.5*0+0.25*0+0.125*0+0.0625*0 .1000=.5000->从0*1+0.5*0+0.25*0+0.125*0+0.0625*0 .1001=.5625->从0*1+0.5*0+0.25*0+0.125*0+0.0625*0 .1010=.6250->从0*1+0.5*0+0.25*0+0.125*0+0.0625*0 .1011=.6875->从0*1+0.5*0+0.25*0+0.125*0+0.0625*0 .1100=.7500->从0*1+0.5*0+0.25*0+0.125*0+0.0625*0 .1101=.8125->从0*1+0.5*0+0.25*0+0.125*0+0.0625*0 .1110=.8700->从0*1+0.5*0+0.25*0+0.125*0+0.0625*0 .1111=.9325->从0*1+0.5*0+0.25*0+0.125*0+0.0625*0 1=2^0,0.5=2^-1,0.25=2^-2和0.125=2^-3 如你所见。4位浮点只能表示0到0.9325之间的基数10,间距为0.0625。这也意味着它不能做0.1,0.2,0.3

    由于实际的标准使用了更多的位以及数字移位技术。它实际上可以表示比本例多得多的数字,但限制仍然相同。所以当你除以某个值,结果不会落在其中一个上。。。JVM将把它移动到最近的位置


    希望能解释一下。

    0.19700001
    是最接近的单精度浮点近似值0.197到十进制的转换。另一方面,
    0.19699999999998
    是最接近的双精度浮点近似值0.197到十进制的转换。Java完全按照您的要求使用而不是使用
    f
    后缀。有关更多示例,请参见(在C中,但问题是相同的)。