浮点类型精度问题

在Java中,浮点类型有float、double两种类型,主要用于表示小数。我们经常看到浮点数有精度问题,不适用于比较大小或比较相等的逻辑。任何数字在计算机中都是用0和1的二进制形式来表示的,那么对于浮点数又是如何使用0和1表示出来的呢?

浮点数二进制计算方法

对于整数的二进制计算,采用“除2取余,逆序排列”来算出二进制,如下所示:

img

对于小数的二进制计算,采用“乘2取整,顺序排列”来算出二进制,如下所示:

img

浮点数二进制转10进制

要使用二进制表示浮点数,就是要让小数部分也能使用二进制来表示。二进制整数部分,最低位表示2的0次方,往高位依次增加,为2的1次方,2次方,3次方…,小数部分最高位是2的-1次方,往低位依次为2的-2次方,2的-3次方…,如果二进制为0则不做计算,如下表格所示。

二进制11111.1111
2423222120小数点2-12-22-32-4
十进制168421.1/21/41/81/16
转换示例
十进制二进制计算方式
2.510.12.5=21+2-1
0.60.10011001…0.6=2-1+2-4+2-5+2-8+…

上面我们发现0.6本身不是无限循环的小数,但是表示成二进制之后便成了无限循环小数。计算机是无法精确存储无限循环二进制数的,只能0舍1入。

二进制科学技术法

二进制科学技术法,就是将二进制的小数点左移或右移,直到首位为1,然后乘以2的多少次方,往左移几位就是2的几次方,往右移几位就是2的负几次方。小数点后面的0或1就叫做【尾数】,多少次方就叫做【指数】。

十进制二进制二进制科学技术法
2.510.110.1=1.01 x 21
0.60.10011001…0.10011001…=1.0011001… x 2-1

要在计算机中存储一个浮点数,还需要有正负号。我们来看看浮点数是如何在计算机中存储的。在Java中,浮点数有float和double,其中float占4个字节【共32位】,double占8个字节【共64位】。

浮点数 (1)

对于float,最高位1bit存储正负号,然后8位存储指数,其余全都用来存储尾数。对于指数部分,实际存储的是【指数+偏移量】之后的二进制结果,因为指数有负数,在加上偏移量之后就不会出现负数了。

浮点数 (2)

案例

明白了上面浮点数相关的知识,我们来分析经典的【1-0.9】为什么不等于0.1。

首先来学一下二进制的减法是怎么运算的,如下:

未命名文件 (2)

未命名文件 (3)

未命名文件 (4)

未命名文件 (5)

搞明白二进制减法的运算方法,我们通过代码来实战一下:

  • 通过计算出float类型1-0.9的二进制结果,然后转换成10进制输出结果
  • 二进制转换成10进制的步骤在下图计算器中
/**
 * @auther doaredo
 * @mail doaredo@163.com
 * @date 2021/07/31/1:22
 * @description
 */
public class FloatDemo {

    public static void main(String[] args) {
        /*float经典:1-0.9*/
        /*
          二进制减法:1-0.9
          1.000000000000000000000000
         -0.111001100110011001100110
         =0.000110011001100110011010*/
        float a = 1f;
        float b = 0.9f;
        // 直接Java计算的结果
        System.out.println(a - b);
        // 自己二进制减法后的结果
        System.out.println(0.0625 +
                0.03125 +
                0.00390625 +
                0.001953125 +
                0.000244140625 +
                0.0001220703125 +
                0.0000152587890625 +
                0.00000762939453125 +
                9.5367431640625e-7 +
                4.76837158203125e-7 +
                1.1920928955078125e-7);
        // 我们发现自己计算的结果倍数比Java计算的要多
        // java中自动对浮点数进行了四舍五入
        System.out.println(0.10000002384185791f);
    }
}
输出结果
0.100000024
0.10000002384185791
0.100000024

image-20210731011503706

上面我们指定了数据类型为float,如果没有指定为float,那么默认为double类型。

  • 通过计算出double类型1-0.9的二进制结果,然后转换成10进制输出结果
  • 二进制转换成10进制的步骤在下图计算器中
/**
 * @auther doaredo
 * @mail doaredo@163.com
 * @date 2021/07/31/1:22
 * @description
 */
public class FloatDemo {

    public static void main(String[] args) {
        /*
        * 经典:1-0.9
        * 如果没有指定为float类型,默认为double类型的浮点数
        * */
        /*
          二进制减法1-0.9
          1.00000000000000000000000000000000000000000000000000000
         -0.11100110011001100110011001100110011001100110011001101
         =0.00011001100110011001100110011001100110011001100110011*/
        // java计算结果
        System.out.println(1 - 0.9);
        // 自己二进制减法后的结果
        System.out.println(0.0625
                + 0.03125
                + 0.00390625
                + 0.001953125
                + 0.000244140625
                + 0.0001220703125
                + 0.0000152587890625
                + 0.00000762939453125
                + 9.5367431640625e-7
                + 4.76837158203125e-7
                + 5.960464477539063e-8
                + 2.9802322387695312e-8
                + 3.725290298461914e-9
                + 1.862645149230957e-9
                + 2.3283064365386963e-10
                + 1.1641532182693481e-10
                + 1.4551915228366852e-11
                + 7.275957614183426e-12
                + 9.094947017729282e-13
                + 4.547473508864641e-13
                + 5.684341886080802e-14
                + 2.842170943040401e-14
                + 3.552713678800501e-15
                + 1.7763568394002505e-15
                + 2.220446049250313e-16
                + 1.1102230246251565e-16
        );
    }
}
输出结果
0.09999999999999998
0.09999999999999998

image-20210731011016307

浮点数比较

由于浮点数占用的位数长度有限,当整数部分过大的时候,小数部分没就有充分的bit使用,导致精度问题。

/**
 * @auther doaredo
 * @mail doaredo@163.com
 * @date 2021/07/31/1:22
 * @description
 */
public class FloatDemo {

    public static void main(String[] args) {
        float c = 0.123456789f;
        float d = 0.123456789f;
        // 输出为true
        System.out.println(c==d);

        float a = 12345678.009f;
        float b = 12345678.000f;
        // 由于整数位占用bit过多,导致小数位没有地方而被忽略掉了
        // 由于小数位被忽略了部分,实际比较两个数是相等的
        System.out.println(a==b);
    }
}
输出结果
true
true
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值