java 将小数拆分为两部分+浮点型精度丢失问题

简介: java 将小数拆分为两部分+浮点型精度丢失问题

问题:将一个String类型的小数拆分为整数部分和小数部分,如9.9拆分为9和0.9

1.将小数的整数和小数部分拆分开

public float numberSub(String totalMoney){
        float moneyFloat=Float.parseFloat(totalMoney);
        System.out.println("moneyFloat="+moneyFloat);
        int moneyInteger=(int) moneyFloat;
        System.out.println("moneyInteger="+moneyInteger);
        float moneyDecimal=moneyFloat-moneyInteger;
        System.out.println("moneyDecimal="+moneyDecimal);
        float result=this.sub(moneyFloat, moneyInteger);
        return result;
    }

上面这个方法里面,float-->int转化时直接丢弃小数部分,从而取得小数中的整数,而后作差得到小数部分,但是看下面输出:

2e03decd5643132803bcc42ec2a302e9.png


2.浮点型表示一个小数的时候存在精度不准确的问题

原因:

首先我们要搞清楚下面两个问题:  
   (1) 十进制整数如何转化为二进制数  
         算法很简单。举个例子,11表示成二进制数:  
                    11/2=5   余   1  
                     5/2=2   余   1  
                     2/2=1   余   0  
                     1/2=0   余   1  
                      0结束         11二进制表示为(从下往上):1011  
        这里提一点:只要遇到除以后的结果为0了就结束了,大家想一想,所有的整数除以2是不是一定能够最终得到0。换句话说,所有的整数转变为二进制数的算法会不会无限循环下去呢?绝对不会,整数永远可以用二进制精确表示 ,但小数就不一定了。  
    (2) 十进制小数如何转化为二进制数  
         算法是乘以2直到没有了小数为止。举个例子,0.9表示成二进制数  
                   0.9*2=1.8   取整数部分 1  
                   0.8(1.8的小数部分)*2=1.6    取整数部分 1  
                   0.6*2=1.2   取整数部分 1  
                   0.2*2=0.4   取整数部分 0  
                   0.4*2=0.8   取整数部分 0  
                   0.8*2=1.6 取整数部分 1  
                   0.6*2=1.2   取整数部分 0  
                            .........      0.9二进制表示为(从上往下): 1100100100100......  
         注意:上面的计算过程循环了,也就是说*2永远不可能消灭小数部分,这样算法将无限下去。很显然,小数的二进制表示有时是不可能精确的 。其实道理很简单,十进制系统中能不能准确表示出1/3呢?同样二进制系统也无法准确表示1/10。这也就解释了为什么浮点型减法出现了精度丢失的问题。

3.验证

众所周知、 Java 的float型在内存中占4个字节。float的32个二进制位结构如下

98312234a107e16cafd733295cc1cbcb.png

float内存存储结构  

4bytes          31                  30                29----23          22----0        


表示       实数符号位      指数符号位        指数位          有效数位


其中符号位1表示正,0表示负。有效位数位24位,其中一位是实数符号位。


将一个float型转化为内存存储格式的步骤为:


    (1)先将这个实数的绝对值化为二进制格式,注意实数的整数部分和小数部分的二进制方法在上面已经探讨过了。

    (2)将这个二进制格式实数的小数点左移或右移n位,直到小数点移动到第一个有效数字的右边。

    (3)从小数点右边第一位开始数出二十三位数字放入第22到第0位。

    (4)如果实数是正的,则在第31位放入“0”,否则放入“1”。

    (5)如果n 是左移得到的,说明指数是正的,第30位放入“1”。如果n是右移得到的或n=0,则第30位放入“0”。

    (6)如果n是左移得到的,则将n减去1后化为二进制,并在左边加“0”补足七位,放入第29到第23位。如果n是右移得到的或n=0,则将n化为二进制后在左边加“0”补足七位,再各位求反,再放入第29到第23位。


我们以数字8举例验证,float类型为8.0


1.将8.0转换为二进制之后是1000.0


2.将小数点左移三位到第一个有效位右侧1.0000(保证有效位数24位)得1.00000000000000000000000(只保留24位,多出的被截取掉了,从而引起了误差!)


3.这时已经有了二十四位有效数字,将最左边一位“1”去掉,得到0000000000000000000000共23位,将它放入float存储结构的第22到第0位。


4.因为8.0是正数,因此在第31位实数符号位放入“0”。


5.由于我们把小数点左移,因此在第30位指数符号位放入“1”。


6.因为我们是把小数点左移3位,因此将3减去1得2,化为二进制并补足7位得到0000010,放入第29到第23位。


最后得到0 1 0000010 0000000000000000000000


代码中打印出来如下:

public void testJavaDataType(){
        int aint=8;
        float afloat=8;
        int fl=Float.floatToIntBits(afloat);
        double adouble=8;
        long dl=Double.doubleToLongBits(adouble);
        System.out.println("Int 8 :"+Integer.toBinaryString(aint));
        System.out.println("Float 8 :"+Integer.toBinaryString(fl));
        System.out.println("Double 8 :"+Long.toBinaryString(dl));
    }

658fd02b54bd0c36087693152bf0ae8e.png

打印的时候如果是整数则第31位0默认不打印,篮框是第30位指数符号位,红框是指数位,蓝底是有效位数

很显然对于小数来说一个有限的23位有效位数是不足以精确表示一个小数的。

4.BigDecimal解决

public float sub(float a,float b){
        BigDecimal bda=new BigDecimal(Float.toString(a));
        BigDecimal bdb=new BigDecimal(String.valueOf(b));
        return bda.subtract(bdb).floatValue();
    }

5.利用String截取小数点来解决该问题

public float stringSub(String totalMoeny){
        String[] strArray=totalMoeny.split("\\.");
        if(strArray.length>=2){
            float a=Float.valueOf(strArray[0]);
            System.out.println(a);
            float b=Float.parseFloat("0."+strArray[1]);
            System.out.println(b);
            return a-b;
        }else{
            return 0l;    
        }
    }

参考文章:


http://blog.csdn.net/chencheng19912012/article/details/30072389


http://www.cnblogs.com/jym-sunshine/p/5853070.html


http://blog.csdn.net/tomcat_2014/article/details/51453988

————————————————

版权声明:本文为CSDN博主「翎野君」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/lingyejun/article/details/121646979

目录
相关文章
|
5月前
|
Java
java判断小数点后是否为0,是的话则取整,不是的话则保留
java判断小数点后是否为0,是的话则取整,不是的话则保留
180 0
|
7月前
|
Java
湖南大学Java编程题7. 输出较小数
湖南大学Java编程题7. 输出较小数
Zp
|
Java
Java中BigDecimal保留两位小数
Java中BigDecimal保留两位小数
Zp
667 0
|
3月前
|
Go Java 算法
Java每日一练(20230404) 买卖股票时机3、分数到小数、出现一次的数字2
Java每日一练(20230404) 买卖股票时机3、分数到小数、出现一次的数字2
25 0
Java每日一练(20230404) 买卖股票时机3、分数到小数、出现一次的数字2
|
9月前
|
Java
DecimalFormat的使用讲解数字格式化和demo(java小数控制,金额返回相关处理)
DecimalFormat的使用讲解数字格式化和demo(java小数控制,金额返回相关处理)
130 0
|
5月前
|
Java
Java对double值进行四舍五入,保留两位小数的几种方法
Java对double值进行四舍五入,保留两位小数的几种方法
|
7月前
|
Java 程序员
Java浮点型变量和BigDecimal的使用
Java浮点型变量和BigDecimal的使用
54 0
|
11月前
|
存储 Java
Java优雅的保留两位小数
可以使用Java中的DecimalFormat类来保留一个浮点数或双精度数的两位小数。
|
Java
Java:String.format格式化浮点数保留指定小数位输出字符串
Java:String.format格式化浮点数保留指定小数位输出字符串
164 0