Java中BigDecimal类与int、Integer使用总结
前言
我们都知道浮点型变量在进行计算的时候会出现丢失精度的问题。如下一段代码:
System.out.println(0.05+0.01); System.out.println(1.0-0.42); System.out.println(4.015*100); System.out.println(123.3/100); 输出: 0.060000000000000005 0.5800000000000001 401.49999999999994 1.2329999999999999
可以看到在Java中进行浮点数运算的时候,会出现丢失精度的问题。那么我们如果在进行商品价格计算的时候,就会出现问题。很有可能造成我们手中有0.06元,却无法购买一个0.05元和一个0.01元的商品。因为如上所示,他们两个的总和为0.060000000000000005。这无疑是一个很严重的问题,尤其是当电商网站的并发量上去的时候,出现的问题将是巨大的。可能会导致无法下单,或者对账出现问题。所以接下来我们就可以使用Java中的BigDecimal类来解决这类问题。
Java中float的精度为6-7位有效数字。double的精度为15-16位
API
方法 描述
add(BigDecimal) BigDecimal对象中的值相加,然后返回这个对象。
subtract(BigDecimal) BigDecimal对象中的值相减,然后返回这个对象。
multiply(BigDecimal) BigDecimal对象中的值相乘,然后返回这个对象。
divide(BigDecimal) BigDecimal对象中的值相除,然后返回这个对象。
toString() 将BigDecimal对象的数值转换成字符串。
doubleValue() 将BigDecimal对象中的值以双精度数返回。
floatValue() 将BigDecimal对象中的值以单精度数返回。
longValue() 将BigDecimal对象中的值以长整数返回。
intValue() 将BigDecimal对象中的值以整数返回。
BigDecimal精度也丢失
我们在使用BigDecimal时,使用它的BigDecimal(String)构造器创建对象才有意义。其他的如BigDecimalb=newBigDecimal(1)这种,还是会发生精度丢失的问题。如下代码:
BigDecimala=newBigDecimal(1.01); BigDecimalb=newBigDecimal(1.02); BigDecimalc=newBigDecimal("1.01"); BigDecimald=newBigDecimal("1.02"); System.out.println(a.add(b)); System.out.println(c.add(d)); 输出: 2.0300000000000000266453525910037569701671600341796875 2.03
可见论丢失精度BigDecimal显的更为过分。但是使用Bigdecimal的BigDecimal(String)构造器的变量在进行运算的时候却没有出现这种问题。究其原因计算机组成原理里面都有,它们的编码决定了这样的结果。long可以准确存储19位数字,而double只能准备存储16位数字。double由于有exp位,可以存16位以上的数字,但是需要以低位的不精确作为代价。如果需要高于19位数字的精确存储,则必须用BigInteger来保存,当然会牺牲一些性能。所以我们一般使用BigDecimal来解决商业运算上丢失精度的问题的时候,声明BigDecimal对象的时候一定要使用它构造参数为String的类型的构造器。
同时这个原则EffectiveJava和MySQL
必知必会中也都有提及。float和double只能用来做科学计算和工程计算。商业运算中我们要使用BigDecimal。
正确运用BigDecimal
BigDecimalBigDecimal(doubled);//不允许使用
BigDecimalBigDecimal(Strings);//常用,推荐使用
staticBigDecimalvalueOf(doubled);//常用,推荐使用
其原因有
- double参数的构造方法,不允许使用!!!!因为它不能精确的得到相应的值;
- String构造方法是完全可预知的:写入newBigDecimal("0.1")将创建一个BigDecimal,它正好等于预期的0.1;因此,通常建议优先使用String构造方法;
- 静态方法valueOf(doubleval)内部实现,仍是将double类型转为String类型;这通常是将double(或float)转化为BigDecimal的首选方法;
BigDecimal的大小比较
例子:a.compareTo(b)<0
compareTo返回: -1,0,1
-1小于
0 等于1 大于
BigDecimal的小数点后位数
BigDecimalc=newBigDecimal("2.224667").setScale(2,BigDecimal.ROUND_UP); System.out.println(c);//2.23跟上面相反,进位处理 ---------- ROUND_CEILING天花板(向上):正数进位向上,负数舍位向上 BigDecimalf=newBigDecimal("2.224667").setScale(2,BigDecimal.ROUND_CEILING); System.out.println(f);//2.23如果是正数,相当于BigDecimal.ROUND_UP BigDecimalg=newBigDecimal("-2.225667").setScale(2,BigDecimal.ROUND_CEILING); System.out.println(g);//-2.22如果是负数,相当于BigDecimal.ROUND_DOWN ---------- ROUND_FLOOR地板(向下):正数舍位向下,负数进位向下 BigDecimalh=newBigDecimal("2.225667").setScale(2,BigDecimal.ROUND_FLOOR); System.out.println(h);//2.22如果是正数,相当于BigDecimal.ROUND_DOWN BigDecimali=newBigDecimal("-2.224667").setScale(2,BigDecimal.ROUND_FLOOR); ---------- ROUND_HALF_UP BigDecimald=newBigDecimal("2.225").setScale(2,BigDecimal.ROUND_HALF_UP); System.out.println("ROUND_HALF_UP"+d);//2.23四舍五入(若舍弃部分>=.5,就进位) ---------- ROUND_HALF_DOWN BigDecimale=newBigDecimal("2.225").setScale(2,BigDecimal.ROUND_HALF_DOWN); System.out.println("ROUND_HALF_DOWN"+e);//2.22四舍五入(若舍弃部分>.5,就进位) ----------
int和Integer的区别
- int是java提供的8种原始类型之一,java为每个原始类型提供了封装类,Integer是int的封装类。int默认值是0,而Integer默认值是null;
- int和Integer(无论是否new)比较,都为true,因为会把Integer自动拆箱为int再去比;
- Integer是引用类型,用==比较两个对象,其实比较的是它们的内存地址,所以不同的Integer对象肯定是不同的;
- 但是对于Integeri=,java在编译时会将其解释成Integeri=Integer.valueOf();。但是,Integer类缓存了[-128,127]之间的整数,所以对于Integeri1=127;与Integeri2=127;来说,i1==i2,因为这二个对象指向同一个内存单元。而Integeri1=128;与Integeri2=128;来说,i1==i2为false。
各自的应用场景
- Integer默认值是null,可以区分未赋值和值为0的情况。比如未参加考试的学生和考试成绩为0的学生
- 加减乘除和比较运算较多,用int
- 容器里推荐用Integer。对于PO实体类,如果db里int型字段允许null,则属性应定义为Integer。当然,如果系统限定db里int字段不允许null值,则也可考虑将属性定义为int。
- 对于应用程序里定义的枚举类型,其值如果是整形,则最好定义为int,方便与相关的其他int值或Integer值的比较
- Integer提供了一系列数据的成员和操作,如Integer.MAX_VALUE,Integer.valueOf(),Integer.compare(),compareTo(),不过一般用的比较少。建议,一般用int类型,这样一方面省去了拆装箱,另一方面也会规避数据比较时可能带来的bug。