我是靠谱客的博主 勤劳机器猫,最近开发中收集的这篇文章主要介绍java double float decimal int long 等数值型类型的范围及精度的问题,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

浮点数:

它是用来表示实数的一种方法,它用 M(尾数) * B( 基数)的E(指数)次方来表示实数,相对于定点数来说,在长度一定的情况下,具有表示数据范围大的特点。但同时也存在误差问题,这就是著名的浮点数精度问题!   

 浮点数有多种实现方法计算机中浮点数的实现大都遵从 IEEE754 标准,IEEE754 规定了单精度浮点数和双精度浮点数两种规格,单精度浮点数用4字节(32bit)表示浮点数,格式是:1位符号位 8位表示指数 23位表示尾数;双精度浮点数8字节(64bit)表示实数,格式是:1位符号位 11位表示指数 52位表示尾数。同时,IEEE754标准还对尾数的格式做了规范:d.dddddd...,小数点左面只有1位且不能为零,计算机内部是二进制,因此,尾数小数点左面部分总是1。显然,这个1可以省去,以提高尾数的精度。由上可知,单精度浮点数的尾数是用24bit表示的,双精度浮点数的尾数是用53bit表示的,转换成十进制:

2^24 - 1 = 16777215           

2^53 - 1 = 9007199254740991     

由上可见,IEEE754单精度浮点数的有效数字二进制是24位,按十进制来说,是8位;双精度浮点数的有效数字二进制是53位,按十进制来说,是16 位。

显然,如果一个实数的有效数字超过8位,用单精度浮点数来表示的话,就会产生误差!同样,如果一个实数的有效数字超过16位,用双精度浮点数来表示,也会产生误差!

例如:对于 1310720000000000000000.66 这个数,有效数字是24位,用单精度或双精度浮点数表示都会产生误差,只是程度不同:   

双精度浮点数: 1310720040000000000000.00   

单精度浮点数: 1310720000000000000000.00   

双精度差了 0.66 ,单精度差了近4万亿!以上说明了因长度限制而造成的误差,但这还不是全部!

采用IEEE754标准的计算机浮点数,在内部是用二进制表示的,但在将一个十进制数转换为二进制浮点数时,也会造成误差,原因是不是所有的数都能转换成有限长度的二进制数。对于131072.32 这个数,其有效数字是8位,按理应该能用单精度浮点数准确表示,为什么会出现偏差呢?看一下这个数据二进制尾数就明白了 10000000000000000001010001......显然,其尾数超过了24bit,根据舍入规则,尾数只取 100000000000000000010100,结果就造成测试中遇到的“奇怪”现象!131072.68 用单精度浮点数表示变成 131072.69 ,原因与此类似。实际上有效数字小于8位的数,浮点数也不一定能精确表示,7.22这个数的尾数就无法用24bit二进制表示,当然在数据库中测试不会有问题(舍入以后还是7.22),但如果参与一些计算,误差积累后,就可能产生较大的偏差。

浮点数在机内用指数型式表示,分解为:数符,尾数,指数符,指数四部分

数符: 占1位二进制,表示数的正负。
指数符:占1位二进制,表示指数的正负。
尾数:表示浮点数有效数字,0.xxxxxxx,但不存开头的0和点
指数:存指数的有效数字。

 

单精度浮点数(float)与双精度浮点数(double), decimal 的区别如下:

(1)在内存中占有的字节数不同

    单精度浮点数在机内占4个字节

    双精度浮点数在机内占8个字节

   decimal 在机内占16 字节 

(2)有效数字位数不同

    单精度浮点数有效数字8位

    双精度浮点数有效数字16位

   decimal 有效数字 28位

(3)所能表示数的范围不同

    单精度浮点的表示范围:-3.40E+38 ~ +3.40E+38(10的-38次方到10的38次方)

    双精度浮点的表示范围:-1.79E+308 ~ +1.79E+308(10的-308次方到10的308次方)

    decimal 表示的范围   : 79228162514264337593543950335,最小值:  -79228162514264337593543950335

(4)在程序中处理速度不同
一般来说,CPU处理单精度浮点数的速度比处理双精度浮点数快,因为内存占用小。

 

java 示例:

java 中  浮点数默认是 double 类型的,如果想把 浮点数赋值给 float 需要 在小数后面 加 f例如:float a=1.3;
 

public class TestMap {
    public static void main(String[] args)  throws Exception{


      float a=0.05f;
      float b=0.02f;
      System.out.println(a-b);
    }
}

结果  : 0.030000001 

注意 因为是float 类型有效精度为8位,所以不算开始的0以外,打印了 8位 30000001 

 

public class TestMap {
    public static void main(String[] args)  throws Exception{


      double a=0.05f;
      double b=0.02f;
      System.out.println(a-b);
    }
}

结果:0.030000001192092896 

因为 double 有效精度是 16位,所以我以为结果是16位,但是确实 17位,好奇怪,这个等待高手解答。

 

int  4字节 范围

uint 0~4294967295
int -2147483648~2147483647

 

long  8字节 范围
long:-9223372036854775808  ~ 9223372036854775807
ulong :0 ~   18446744073709551615

 

int 代表 小整形 ,long 代表大整形,  无误差。

float double decimal 代表浮点数  有误差。

实际中使用的时候,根据业务需要 使用不同的精度就可以了(因为float double decimal 的范围都比long 要大,所以数值范围基本不用考虑,只考虑精度就可以了)

 

对于货币性的数据,一般都是采用decimal 这个28位精度的类型,尽量避免误差。

Double 的坑

问题:

System.out.println(new Double("19.9")*100);
System.out.println( Double.valueOf("19.9")*new Double(100));

输出结果都是 1989.9999999999998

 

解决办法:

String s = "19.9";
BigDecimal temp = BigDecimal.valueOf(Double.valueOf(s));
// 将temp乘以100
temp = temp.multiply(BigDecimal.valueOf(100));
int sum = temp.intValue();
System.out.println(sum);

 

java Bigdecimal 使用注意事项

1 BigDecimal类的常用方法

add(BigDecimal):BigDecimal对象中的值相加,返回BigDecimal对象
subtract(BigDecimal):BigDecimal对象中的值相减,返回BigDecimal对象
multiply(BigDecimal):BigDecimal对象中的值相乘,返回BigDecimal对象
divide(BigDecimal):BigDecimal对象中的值相除,返回BigDecimal对象
toString():将BigDecimal对象中的值转换成字符串
doubleValue():将BigDecimal对象中的值转换成双精度数
floatValue():将BigDecimal对象中的值转换成单精度数
longValue():将BigDecimal对象中的值转换成长整数
intValue():将BigDecimal对象中的值转换成整数
 

 转化为 long 或者int 的时候 有可能会 截断数据,因为 bigdecimal 的范围比 long int 大。

 转化为 double  float 的时候 精度会丢失。

2 两数相除除不尽的问题
public static void main(String[] args) {

    BigDecimal a = new BigDecimal(10);
    BigDecimal b = new BigDecimal(3);
    BigDecimal c = a.divide(b);
}

执行:抛出

Exception in thread "main" java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.
at java.math.BigDecimal.divide(BigDecimal.java:1616)

怎么办?

可以使用 bigdecimal 的另外一个重载函数 :

public BigDecimal divide(BigDecimal divisor, int scale, int roundingMode)

第一参数表示除数,

第二个参数表示小数点后保留位数,

第三个参数表示舍入模式,只有在作除法运算或四舍五入时才用到舍入模式,有下面这几种

ROUND_CEILING    //向正无穷的方向舍入。如果为正数,舍入结果同ROUND_UP一致;如果为负数,舍入结果同ROUND_DOWN一致。注意:此模式不会减少数值大小

ROUND_FLOOR    //如果为正数,舍入结果同ROUND_DOWN一致;如果为负数,舍入结果同ROUND_UP一致。注意:此模式不会增加数值大小

ROUND_DOWN    //向零方向舍入    相当于最后一位后面的都舍去  9舍不入               

ROUND_UP    //向远离0的方向舍入  相当于最后一位加1,              0入不舍

 

ROUND_UNNECESSARY    //计算结果是精确的,不需要舍入模式

 

ROUND_HALF_DOWN    //向(距离)最近的一边舍入,除非两边(的距离)是相等,如果是这样,向下舍入, 例如1.55 保留一位小数结果为1.5  相当于于5舍6入

ROUND_HALF_EVEN    //向(距离)最近的一边舍入,除非两边(的距离)是相等,如果是这样,如果保留位数是奇数,使用ROUND_HALF_UP,如果是偶数,使用ROUND_HALF_DOWN

ROUND_HALF_UP    //向(距离)最近的一边舍入,除非两边(的距离)是相等,如果是这样,向上舍入, 1.55保留一位小数结果为1.6 ,  相当于于4舍5入

 

3 如果设置精度

public static void main(String[] args)
    {
        BigDecimal a = new BigDecimal("4.5635");

        a = a.setScale(3, RoundingMode.HALF_UP);    //保留3位小数,且四舍五入
        System.out.println(a);
    }

4 减乘除其实最终都返回的是一个新的BigDecimal对象,因为BigInteger与BigDecimal都是不可变的(immutable)的,在进行每一步运算时,都会产生一个新的对象

public static void main(String[] args)
    {
        BigDecimal a = new BigDecimal("4.5");
        BigDecimal b = new BigDecimal("1.5");
        a.add(b);

        System.out.println(a);  //输出4.5. 加减乘除方法会返回一个新的BigDecimal对象,原来的a不变


    }

5:BigDecimal取值范围的 validation 校验问题总结
  常常在与客户端交互时需要做很多校验,在javax.validation下面有很多不错的校验规则

  @NotNull :不为空,适用任何地方(@NotBlank只是用字符类型)

  @DecimalMax:取得最大值范围

    @DecimalMin(value = "0.00", message = "") 取值最小值

 

6构造函数

      1.public BigDecimal(double val)    将double表示形式转换为BigDecimal *不建议使用

  2.public BigDecimal(int val)  将int表示形式转换成BigDecimal

  3.public BigDecimal(String val)  将String表示形式转换成BigDecimal

 

7 比较大小

BigDecimal a = new BigDecimal (101);
BigDecimal b = new BigDecimal (111);
 
//使用compareTo方法比较
//注意:a、b均不能为null,否则会报空指针
if(a.compareTo(b) == -1){
    System.out.println("a小于b");
}
 
if(a.compareTo(b) == 0){
    System.out.println("a等于b");
}
 
if(a.compareTo(b) == 1){
    System.out.println("a大于b");
}
 
if(a.compareTo(b) > -1){
    System.out.println("a大于等于b");
}
 
if(a.compareTo(b) < 1){
    System.out.println("a小于等于b");
}

 

8 BigDecimal转String

public static void main(String[] args) {
        // 浮点数的打印
        System.out.println(new BigDecimal("10000000000").toString());

        // 普通的数字字符串
        System.out.println(new BigDecimal("100.000").toString());

        // 去除末尾多余的0
        System.out.println(new BigDecimal("100.000").stripTrailingZeros().toString());

        // 避免输出科学计数法
        System.out.println(new BigDecimal("100.000").stripTrailingZeros().toPlainString());

        System.out.println(new BigDecimal("100.001").stripTrailingZeros().toPlainString());

   System.out.println(new BigDecimal("100.001").stripTrailingZeros().toString());

}

// output
10000000000
100.000
1E+2
100
100.001

100.001
  1. 用toPlainString()函数代替toString(),避免输出科学计数法的字符串。
  2. stripTrailingZeros()函数就是用于去除末尾多余的0的,
  3. 用toString()方法输出的就是普通的数字字符串。

 

 

 

 

最后

以上就是勤劳机器猫为你收集整理的java double float decimal int long 等数值型类型的范围及精度的问题的全部内容,希望文章能够帮你解决java double float decimal int long 等数值型类型的范围及精度的问题所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(59)

评论列表共有 0 条评论

立即
投稿
返回
顶部