首页 > 编程语言 >[Java]BigDecimal与Double的区别和使用场景

[Java]BigDecimal与Double的区别和使用场景

时间:2023-09-23 15:34:54浏览次数:33  
标签:Java BigDecimal Double valueOf System println out

BigDecimal与Double的区别和使用场景

背景

在项目中发现开发小组成员在写程序时,对于Oracle数据类型为Number的字段(经纬度),实体映射类型有的人用Double有的人用BigDecimal,没有一个统一规范,为此我在这里总结记录一下。

一般说到BigDecimalDouble,绕不开的就是金融或电商行业,毕竟涉及到了钱的问题,数据的敏感程度很高,对数据精度要求也很高。

BigDecimalDouble于两种类型在使用上都有一些缺点。

Double的问题

  1. 在计算时会出现不精确的问题
public static void main(String[] args) {
    System.out.println(12.3 + 45.6); // 57.900000000000006
    System.out.println(12.3 / 100); // 0.12300000000000001
}
  1. 小数部分无法使用二进制准确的表示

  2. 等于判断在使用时需要注意

public static void main(String[] args) {
    double a = 2.111111111111111111111111112;
    double b = 2.111111111111111111111111113;
    // duoble超过15位以后就会不对了
    System.out.println(a == b); // true
}

BigDecimal的问题

  1. 使用除法时除不尽会报 ArithmeticException 异常
public static void main(String[] args) {
    // 报异常:Exception in thread "main" java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.
    System.out.println(BigDecimal.valueOf(121).divide(BigDecimal.valueOf(3)));
}
public static void main(String[] args) {
    // 需要指定精度和舍入方式,当除不尽时也不会报异常 
    // 运行结果为 40.33
    System.out.println(BigDecimal.valueOf(121).divide(BigDecimal.valueOf(3), 2, RoundingMode.HALF_UP)); 
}
  1. new BigDecimal(double)结果或许预料不到。
public static void main(String[] args) {
    BigDecimal a = new BigDecimal(12.3);
    BigDecimal b = BigDecimal.valueOf(12.3);
    // Double的小数位其实无法被精确表示,所以传入的.3被精度扩展之后精度丢失,展示出来的并不是精确的.3
    // 结果为12.300000000000000710542735760100185871124267578125
    System.out.println(a);
    // 从底层代码可以看出来,BigDecimal.valueOf 会先转换为字符串之后再调用new BigDecimal,不会造成精度丢失
    // 结果为12.3
    System.out.println(b);
}

所以一般情况下会使用BigDecimal.valueOf()而不是new BigDecimal()

另:BigDecimal.valueOf() 是静态工厂类,永远优先于构造函数。(摘自《Effective java》)

  1. BigDecimal是不可变类

不可变类代表着,任何针对BigDecimal的修改都将产生新对象。所以每次对BigDecimal的修改都要重新指向一个新的对象。

public static void main(String[] args) {
    BigDecimal a = BigDecimal.valueOf(12.3);
    a.add(BigDecimal.valueOf(2.1));
    System.out.println(a); // 12.3,值未修改

    BigDecimal b = BigDecimal.valueOf(12.3);
    // 重新赋值给新对象
    BigDecimal c = b.add(BigDecimal.valueOf(2.1));
    System.out.println(c); // 14.4
}
  1. 比较大小不方便

BigDecimal大小的比较都需要使用compareTo,如果需要返回更大的数或更小的数可以使用maxmin。还要注意在BigDecimal中慎用equals

public static void main(String[] args) {
    BigDecimal a = BigDecimal.valueOf(12.3);
    BigDecimal b = BigDecimal.valueOf(12.32);
    System.out.println(a.compareTo(b)); // -1
    System.out.println(b.compareTo(a)); //1
    System.out.println(a.max(b)); // 12.32
    System.out.println(a.min(b)); // 12.3
    System.out.println(b.max(a)); // 12.32
    System.out.println(b.min(a)); // 12.3
    System.out.println(BigDecimal.valueOf(1).equals(BigDecimal.valueOf(1.0))); //false
}

BigDecimal重写了equals方法,在equals方法里比较了小数位数,在方法注释上也有说明,所以 BigDecimal.valueOf(1).equals(BigDecimal.valueOf(1.0)) 为什么结果为false就可以理解了。在附录中会贴出 BigDecimal 中的 equals方法的源码。

通过源码可以知道 if (scale != xDec.scale) 这句代码就是比较了小数位数,不等则直接返回false

Double与BigDecimal数值操作效率比较

做了一个测试,从1累加到1000000,DoubleBigDecimal的效率比:

public static void main(String[] args) {
    double a = 0;
    long startLong = System.currentTimeMillis();
    for (int i = 0; i < 1000000; i++) {
        a += i;
    }
    // 打印结果:double耗时:9
    System.out.println("double耗时:" + (System.currentTimeMillis() - startLong));

    BigDecimal b = new BigDecimal(0);
    long startLong2 = System.currentTimeMillis();
    for (int i = 0; i < 1000000; i++) {
        b = b.add(BigDecimal.valueOf(i));
    }
    // 打印结果:BigDecimal耗时:83
    System.out.println("BigDecimal耗时:" + (System.currentTimeMillis() - startLong2));
}

可以看到DoubleBigDecimal的效率高了快10倍。

总结

  • 使用Double

    在计算过程中会出现精度丢失问题,但使用方便,计算效率高,在对精度要求不高的情况下建议使用。

  • 使用BigDecimal

    高精度,但做除法的时候要注意除不尽异常,且因为是不可变类和对象类型,做计算时没那么方便,效率比Double低。

所以可以知道,在使用场景上,如果涉及到精确的数值计算,比如典型的金额,一定要使用BigDecimal进行计算,对精准度要求没那么高的可以不使用BigDecimal。需要进行频繁计算的可以使用Double

其实Double或者Float这种浮点类型如果不参与计算只是传值的话,其实没有精度问题,如果要计算就得注意一下。

那么回到我们自己的项目上,经纬度其实一般业务代码也不会去进行计算,大多是用于传参定位记录用,涉及到计算的比如距离,如果是保留到6位小数时已经是1米级别了,可以满足绝大多数场景了,所以在经纬度上使用Double就足够了。

附录

BigDecimal 的 equals方法源码。

/**
 * Compares this {@code BigDecimal} with the specified
 * {@code Object} for equality.  Unlike {@link
 * #compareTo(BigDecimal) compareTo}, this method considers two
 * {@code BigDecimal} objects equal only if they are equal in
 * value and scale (thus 2.0 is not equal to 2.00 when compared by
 * this method).
 * 该方法认为两个BigDecimal对象只有在值和比例相等时才相等,所以当使用该方法比较2.0与2.00时,二者不相等。
 *
 * @param  x {@code Object} to which this {@code BigDecimal} is
 *         to be compared.
 * @return {@code true} if and only if the specified {@code Object} is a
 *         {@code BigDecimal} whose value and scale are equal to this
 *         {@code BigDecimal}'s.
 * @see    #compareTo(java.math.BigDecimal)
 * @see    #hashCode
 */
@Override
public boolean equals(Object x) {
    // 比较对象是否为 BigDecimal 数据类型,不是直接返回false
    if (!(x instanceof BigDecimal))
        return false;
    BigDecimal xDec = (BigDecimal) x;
    if (x == this)
        return true;
    // 比较 scale 值是否相等。在这里比较了小数位数,不等返回false。
    // scale 是BigDecimal 的标度。如果为零或正数,则标度是小数点后的位数。
    // 如果为负数,则将该数的非标度值乘以 10 的负 scale 次幂。例如,-3 标度是指非标度值乘以 1000。
    if (scale != xDec.scale)
        return false;
    long s = this.intCompact;
    long xs = xDec.intCompact;
    if (s != INFLATED) {
        if (xs == INFLATED)
            xs = compactValFor(xDec.intVal);
        return xs == s;
    } else if (xs != INFLATED)
        return xs == compactValFor(this.intVal);

    return this.inflated().equals(xDec.inflated());
}

关于MySql中如何选用这两种类型

在查资料的时候还看到了关于MySql中如何选用这两种类型的问题,也在此记录一下。

在数据库中除了指定数据类型之外还需要指定精度,所以在MySqlDouble的计算精度丢失比在Java里要高很多,Java的默认精度到了15-16位。

在阿里的编码规范中也强调统一带小数类型的一律要使用Decimal类型而不是Double,使用Decimal可以大大减少计算采坑的概率。

所以在选用类型时,与Java同样,在精度要求不高的情况下可以使用Double,比如经纬度,但是有需要计算、金融金额等优先使用Decimal

参考链接:

  1. https://zhuanlan.zhihu.com/p/94144867
  2. https://www.cnblogs.com/r1-12king/p/15895512.html

标签:Java,BigDecimal,Double,valueOf,System,println,out
From: https://www.cnblogs.com/knqiufan/p/17724440.html

相关文章

  • 2023-09-23:用go语言,假设每一次获得随机数的时候,这个数字大于100的概率是P。 尝试N次,其
    2023-09-23:用go语言,假设每一次获得随机数的时候,这个数字大于100的概率是P。尝试N次,其中大于100的次数在A次~B次之间的概率是多少?0<P<1,P是double类型,1<=A<=B<=N<=100。来自左程云。答案2023-09-23:首先,我们可以使用动态规划来解决这个问题。我们可以定义一个二......
  • JavaSE(6) - 面向对象-1
    JavaSE(6)-面向对象-1p82类和对象类(***设计图***):是对象共同特征的描述;对象:是真实存在的具体东西.在java中,必须先设计类,才能获得对象.如何得到对象publicclass类名{1.成员变量(代表属性的,一般是名词)2.成员方法(代表行为的,一般是动词)......
  • 非常有用的JavaScript高阶面试技巧
    ......
  • Java:JSR 310日期时间体系LocalDateTime、OffsetDateTime、ZonedDateTime
    JSR310日期时间体系:LocalDateTime:本地日期时间OffsetDateTime:带偏移量的日期时间ZonedDateTime:带时区的日期时间(目录)日期时间包importjava.time.LocalDateTime;importjava.time.OffsetDateTime;importjava.time.ZonedDateTime;importjava.time.format.DateTimeF......
  • 2023-09-23:用go语言,假设每一次获得随机数的时候,这个数字大于100的概率是P。 尝试N次,其
    2023-09-23:用go语言,假设每一次获得随机数的时候,这个数字大于100的概率是P。尝试N次,其中大于100的次数在A次~B次之间的概率是多少?0<P<1,P是double类型,1<=A<=B<=N<=100。来自左程云。答案2023-09-23:首先,我们可以使用动态规划来解决这个问题。我们可以定义一个二维数组d......
  • 物联网项目-通讯协议包之 Java版
    物联网项目-温湿度表结构物联网项目-温湿度-Web后台物联网项目-温湿度Web管理后台代码之一物联网项目-温湿度Web管理后台代码之二物联网项目-温湿度Web管理后台代码之三物联网项目-温湿度Web管理后台代码之四物联网项目-温湿度Web管理后台代码之五物联网项目-服务端TCPServer物联......
  • 无涯教程-JavaScript - MEDIAN函数
    描述MEDIAN函数返回给定数字的中位数。中位数是一组数字中间的数字。语法MEDIAN(number1,[number2]...)争论Argument描述Required/OptionalNumber11to255numbersforwhichyouwantthemedian.requirednumber2,...OptionalNotesMEDIAN函数测量集中趋势,......
  • 物联网项目-通讯协议之 Java版
    物联网项目-温湿度表结构物联网项目-温湿度-Web后台物联网项目-温湿度Web管理后台代码之一物联网项目-温湿度Web管理后台代码之二物联网项目-温湿度Web管理后台代码之三物联网项目-温湿度Web管理后台代码之四物联网项目-温湿度Web管理后台代码之五物联网项目-服务端TCPServer物联......
  • Maven 命令行构建 Java 项目
    Maven命令行构建Java项目(22条消息)使用Maven构建SpringBoot项目_Amazing_time的博客-CSDN博客_如何生成springboot项目的mvn构建命令xml-在Spring-BootIntro之后,"Unabletofindasuitablemainclass,pleaseadda'mainClass'属性"-IT工具网(coder.work)(2......
  • 无涯教程-JavaScript - MAXIFS函数
    描述MAXIFS函数返回由一组给定条件或条件指定的单元格中的最大值。Excel2016中已添加此功能。语法MAXIFS(max_range,criteria_range1,criteria1,[criteria_range2,criteria2],...)争论Argument描述Required/Optionalmax_rangeTheactualrangeofcellsinwh......