BigDecimal浅析
jopen
9年前
为什么使用BigDecimal
首先看一个例子: public class DoubleTest { public static void main(String[] args) { System.out.println(0.1 + 0.2); } } 输出的结果:0.30000000000000004(我们的预期是0.3) 其实float和double类型的主要设计目标是为了科学计算和工程计算。他们执行二进制浮点运算,这是为了在广域数值范围上提供较为精确的快速近似计算而精心设计的。然而,它们没有提供完全精确的结果,所以不应该被用于要求精确结果的场合。所有就出现了BigDecimal。 jdk文档BigDecimal的描述: 不可变的、任意精度的有符号十进制数。BigDecimal 由任意精度的整数非标度值和32位的整数标度(scale)组成。BigDecimal 表示的数值是 (unscaledValue × 10-scale)。
BigDecial构造方法
下面是常用的四种构造方法: BigDecimal(int) BigDecimal(double) BigDecimal(long) BigDecimal(String) public static void main(String[] args) { double a = 123.11; BigDecimal bigDecimal = new BigDecimal(a); System.out.println(bigDecimal); } 输出结果:123.1099999999999994315658113919198513031005859375 public static void main(String[] args) { String a = "123.11"; BigDecimal bigDecimal = new BigDecimal(a); System.out.println(bigDecimal); } 输出结果:123.11 public static void main(String[] args) { double a = 123.11; System.out.println(BigDecimal.valueOf(a)); } 输出结果:123.11 BigDecimal(double)这个构造方法为什么会输出不是我们预期的值: 查看源码的注释得知: new BigDecimal(0.1)所创建的BigDecimal实际上等于 0.1000000000000000055511151231257827021181583404541015625,这是因为0.1无法准确地表示为 double(或者不能表示为任何有限长度的二进制小数),所以建议使用BigDecimal(String)构造方法。 valueOf其实也调用BigDecimal(String)构造方法 public static BigDecimal valueOf(double val) { // Reminder: a zero double returns '0.0', so we cannot fastpath // to use the constant ZERO. This might be important enough to // justify a factory approach, a cache, or a few private // constants, later. return new BigDecimal(Double.toString(val)); }
BigDecimal基本运算
add(BigDecimal) BigDecimal对象中的值相加 subtract(BigDecimal) BigDecimal对象中的值相减 multiply(BigDecimal) BigDecimal对象中的值相乘 divide(BigDecimal) BigDecimal对象中的值相除 注意:+-* /运算返回的是新的BigDecimal对象 比较大小: compareTo() -1、0、1,即左边比右边数大,返回1,相等返回0,比右边小返回-1。注意不能使用equals方法来比较大小。 //add(BigDecimal)源码 public BigDecimal add(BigDecimal augend) { if (this.intCompact != INFLATED) { if ((augend.intCompact != INFLATED)) { return add(this.intCompact, this.scale, augend.intCompact, augend.scale); } else { return add(this.intCompact, this.scale, augend.intVal, augend.scale); } } else { if ((augend.intCompact != INFLATED)) { return add(augend.intCompact, augend.scale, this.intVal, this.scale); } else { return add(this.intVal, this.scale, augend.intVal, augend.scale); } } } //add private static BigDecimal add(final long xs, int scale1, BigInteger snd, int scale2) { int rscale = scale1; long sdiff = (long)rscale - scale2; boolean sameSigns = (Long.signum(xs) == snd.signum); BigInteger sum; if (sdiff < 0) { int raise = checkScale(xs,-sdiff); rscale = scale2; long scaledX = longMultiplyPowerTen(xs, raise); if (scaledX == INFLATED) { sum = snd.add(bigMultiplyPowerTen(xs,raise)); } else { sum = snd.add(scaledX); } } else { //if (sdiff > 0) { int raise = checkScale(snd,sdiff); snd = bigMultiplyPowerTen(snd,raise); sum = snd.add(xs); } return (sameSigns) ? //新对象 new BigDecimal(sum, INFLATED, rscale, 0) : //valueOf()返回的也是新对象 valueOf(sum, rscale, 0); }
BigDecimal格式化
如果我们要将String转换为BigDecimal,然后保留两位小数。
DecimalFormat format = new DecimalFormat("0.00"); System.out.println(format.format(new BigDecimal(str))); //如果四舍五入呢 String str = "123.4444"; System.out.println(new BigDecimal(str).setScale(2,BigDecimal.ROUND_HALF_UP)); 参数 直接删除多余的小数位,如2.35会变成2.3 setScale(1,BigDecimal.ROUND_DOWN) 进位处理,2.35变成2.4 setScale(1,BigDecimal.ROUND_UP) 四舍五入,2.35变成2.4 setScale(1,BigDecimal.ROUND_HALF_UP) 四舍五入,2.35变成2.3,如果是5则向下舍setScaler(1,BigDecimal.ROUND_HALF_DOWN)
BigDecimal使用时注意点
(1)尽量使用参数类型为String的构造函数。
(2) BigDecimal都是不可变的,每次计算会产生新的对象,所以+-*/后保存值,如:a.dd(b)要写成a = a.add(b)。
BigDecimal 由任意精度的整数非标度值 和 32 位的整数标度 (scale) 组成理解
例如:-314和 3.1415 表示为:-314 × 10-0 和13412 × 10-4 这里用(非标度值 和 标度)表示分别为:[-314, 0]和[13412, 4] BigDecimal amount = new BigDecimal("314"); System.out.println(amount.signum());//正负 System.out.println(amount.scale()); //标度 System.out.println(amount.stripTrailingZeros()); System.out.println(amount.stripTrailingZeros().scale());//去零后的标度(注意是末尾的0) //结果 1 0 314 0 BigDecimal amount = new BigDecimal("3.1415"); System.out.println(amount.signum());//正负 System.out.println(amount.scale()); //标度 System.out.println(amount.stripTrailingZeros()); System.out.println(amount.stripTrailingZeros().scale());//去零后的标度 //结果 1 4 3.1415 4 //大家尝试理解判断是否是整数这段代码 private boolean isIntegerValue(BigDecimal bd) { return bd.signum() == 0 || bd.scale() <= 0 || bd.stripTrailingZeros().scale() <= 0; }