浮点数(float,double)表数范围和精度问题

浮点数(float,double)表数范围和精度问题

    • 浮点数(float,double)表数范围和精度问题

其实之前就遇到过浮点数精度丢失的问题,但是一直没有去深入研究,只是停留在知识记忆的层面,久而久之发现之前的东西都忘记了,之所以想要围绕这个问题来写一篇文章,是因为最近出现的一个bug,在项目中一个列表显示中id列后台数据是long类型,前端用easyui显示的时候发现数据显示错乱,显示的数据并不是数据库拿到的数据,之前是没有这个问题的,数据量多了以后就出现了这个问题,后来经过各种排查,苦心孤诣探索原因,然并卵,这时候偶然想到会不会是long类型长度超出的问题,于是把后台long类型字段转为String后再返回给前段,显示正常了。原来js中整数只能表示15位,多于15位就会出现精度问题;

 JavaScript不是类型语言,与其他雨多编程语言不同,js没有区分定义short,int,long这些类型,
 只有Number一种数字类型,Number本质是浮点数,跟java中的double类似,64位,8字节的长度
  1. 简单定义

    在IEEE754标准中进行了单精度浮点数(float)和双精度数浮点数(double)的定义。
    float有32bit,double有64bit。它们的构成包括符号位、指数位和尾数位。

  2. 结构组成

    类型 符号位 指数位 尾数位
    floa(32bit) 最左侧(第31位) 第30-23位(占8bit) 第22-0位(占23bit)
    double(64bit) 最左侧(第63位) 第62-52位(占11bit) 第51-0位(占52bit)
  3. 取值范围

    取值范围主要看指数部分:
    float的指数部分有8bit(2^8),由于是有符号型,所以得到对应的指数范围-128~128。取值范围为:
    -2^128到2^128,约等于-3.4E38 ~ +3.4E38 ;
    double的指数部分有11bit(2^11) , 对应的指数范围-1024~1024。 取值范围为:-2^1024~2^1024,约 等于-1.797E308 ~ +1.797E308;

  4. 精度

    精度(有效数字)主要看尾数位:
    float的尾数位是23bit,对应7~8位十进制数,所以有效数字有的编译器是7位,也有的是8位;
    double的尾数位是52bit,对应15~16位十进制数,有效数字位15位或16位;

  5. java中浮点数运算精度丢失问题
    项目中用浮点数运算时通常会出现精度丢失的问题,比如:

        public class Test {
             public static void main(String[] args) {
                double a = 0.2;
                double b = 0.4;
                System.out.println(a);
                System.out.println(b);
                System.out.println(a+b);
                System.out.println(a*b);
            }
        }
    

    输出结果为:
    0.2
    0.4
    0.6000000000000001
    0.08000000000000002

    可以看到0.2+0.4并不等于0.6, 0.2*0.4也不等于0.08,这就是浮点运算中出现的精度丢失问题,他跟我们通常工程运算中以为的不同,这是由于计算机二进制存储的原因造成的,那么怎么解决呢?在项目中通常会避免使用double类型,而是使用BigDecimal类来进行浮点数的运算。

    BigDecimal
    public BigDecimal(double val)
    将 double 转换为 BigDecimal,后者是 double 的二进制浮点值准确的十进制表示形式。
    

    注:

    1. 此构造方法的结果有一定的不可预知性。有人可能认为在 Java 中写入 new BigDecimal(0.1) 所创建的 BigDecimal 正好等于 0.1,但是它实际上等于 0.1000000000000000055511151231257827021181583404541015625。这是因为 double 无法准确地表示为 0.1(或者说对于该情况,不能表示为任何有限长度的二进制小数)。这样,传入 到构造方法的值不会正好等于 0.1(虽然表面上等于该值)。

    2. 另一方面,String 构造方法是完全可预知的:写入 new BigDecimal(“0.1”) 将创建一个 BigDecimal,它正好 等于预期的 0.1。因此,比较而言,通常建议优先使用 String 构造方法。

    3. 当 double 必须用作 BigDecimal 的源时,请注意,此构造方法提供了一个准确转换;它不提供与以下操作相同的结果:先使用 Double.toString(double) 方法,然后使用 BigDecimal(String) 构造方法,将 double 转换为 String。要获取该结果,请使用 static valueOf(double) 方法。

解决方法

在需要精确的表示两位小数时我们需要把他们转换为BigDecimal对象,然后再进行运算。

使用BigDecimal(double val)构造函数时仍会存在精度丢失问题,通常使用BigDecimal(String val)
这就需要先把double转换为字符串然后在作为BigDecimal(String val)构造函数的参数。转换为BigDecimal对象
之后再进行加减乘除操作,这样精度就不会出现问题了。所以有关金钱数据存储都使用BigDecimal。

import java.math.BigDecimal;

public class Test {
     public static void main(String[] args) {
        double a = 0.2;
        double b = 0.4;
        BigDecimal c = BigDecimal.valueOf(a);
        BigDecimal d = BigDecimal.valueOf(b);
        System.out.println(c.add(d));
        System.out.println(c.multiply(d));
    }
}

输出:
0.6
0.08

你可能感兴趣的:(浮点数,精度丢失,float,double)