BigDecimal的使用方法和注意事项
做超市管理系统纯后端用的JDBC然后数据库里面的金额字段是BigDecimal的
做的时候遇到了好多坑,总结下来希望和我一样的萌新看到少走弯路
float和double类型的主要设计目标是为了科学计算和工程计算。他们执行二进制浮点运算,原因在于我们的计算机是二进制的。浮点数没有办法是用二进制进行精确表示。我们的CPU表示浮点数由两个部分组成:指数和尾数,这样的表示方法一般都会失去一定的精确度,有些浮点数运算也会产生一定的误差。如:2.4的二进制表示并非就是精确的2.4。反而最为接近的二进制表示是 2.3999999999999999。浮点数的值实际上是由一个特定的数学公式计算得到的。它们没有提供完全精确的结果,所以不应该被用于要求精确结果的场合。但是,商业计算往往要求结果精确,这时候BigDecimal就派上大用场啦。
<mark>探讨一</mark>:java已经有基本的数据类型float和double了为什么还要用DigDecimal呢接下来我们看一个案例
System.out.println(0.1 + 0.2); //0.30000000000000004
System.out.println(0.3 - 0.1); //0.19999999999999998
System.out.println(0.1 * 0.2); //0.020000000000000004
System.out.println(0.3 / 0.2); //1.4999999999999998
--------------------------------------------------------------------------------
--------------------------------------对比---------------------------------------
--------------------------------------------------------------------------------
BigDecimal big1 = new BigDecimal("0.3");
System.out.println(big1);
BigDecimal big2 = new BigDecimal("0.2");
System.out.println(big1.divide(big2)); //1.5
看一下0.3/0.2的结果通过基本类型计算得出来的是1.4999999999999998一个近似值而BigDecimal是一个准确的数值所以在进行金钱计算的时候一定要用准确是数字BigDcimal的作用就体现出来了
<mark>探讨二</mark>:在小数操作中,我们通常希望能有多种自由的定义方式。例如在不同的场景可能需要返回: 0.3, 0.4, 0.334等不同精度,在不同的精度进位时希望能自主控制。这个时候BigDecimal也可以帮忙
情况一 double a = 3;
double b = 10;
double c = b / a; //3.3333333333333335
System.out.println(c); //大家都知道3/10的结果是一个无线循环小数,怎么擦能拿到结果0.334呢
--------------------------------------------------------------------------------
解决方法 BigDecimal big1 = new BigDecimal("10");
BigDecimal big2 = new BigDecimal("3");
BigDecimal result = big1.divide(big2,3,BigDecimal.ROUND_UP);//制定精度
System.out.println(result);
--------------------------------------------------------------------------------
情况二 BigDecimal a = new BigDecimal(5.4);
BigDecimal b = new BigDecimal(3.1);
BigDecimal divide = a.divide(b);
System.out.println("divide:" + divide); // 出现异常:
//`ava.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.
//这个错误是因为没有指定精度导致的,我们只要指定了结果的精度,就可以避免这个问题。
--------------------------------------------------------------------------------
解决方法 BigDecimal d = new BigDecimal(5.4);
BigDecimal f = new BigDecimal(3.1);
BigDecimal divide = d.divide(f,2,BigDecimal.ROUND_UP);//制定精度
System.out.println("divide:" + divide); //1.74
我们额外传入第二个参数:保留的小数,指定了结果的精度,就可以避免出现这种问题。
所以我们日常用BigDecimal做除法运算的时候,务必写成推荐的形式。避免出现了异常。
1.构造方法
构造 | 描述 |
---|---|
BigDecimal(double val) | 将 double 转换为 BigDecimal ,这是 double 的二进制浮点值的精确十进制表示 |
BigDecimal(int val) | 将 int 成 BigDecimal |
BigDecimal(String val) | 将BigDecimal的字符串表示 BigDecimal 转换为 BigDecimal |
BigDecimal bigdouble = new BigDecimal(5.4); //5.4000000000000003552713678800500929355621337890625
BigDecimal bigInt = new BigDecimal(3); //3
BigDecimal bigString = new BigDecimal("3.2"); //3.2
不知道大家有没有看出什么端倪通过参数是double构造方法生成的bigdouble的数值我们不太认识。为什么会出现这种状况呢?
JDK的描述:
1、参数类型为double的构造方法的结果有一定的不可预知性。有人可能认为在Java中写入newBigDecimal(0.1)所创建的BigDecimal正好等于 0.1(非标度值 1,其标度为 1),但是它实际上等于0.1000000000000000055511151231257827021181583404541015625。这是因为0.1无法准确地表示为 double(或者说对于该情况,不能表示为任何有限长度的二进制小数)。这样,传入到构造方法的值不会正好等于 0.1(虽然表面上等于该值)。
2、另一方面,String 构造方法是完全可预知的:写入 newBigDecimal(“0.1”) 将创建一个 BigDecimal,它正好等于预期的 0.1。因此,比较而言,通常建议优先使用String构造方法。
当double必须用作BigDecimal的源时,请使用<mark>Double.toString(double)转成String</mark>,然后使用BigDecimal的参数为String类型的构造方法,或使用BigDecimal的静态方法valueOf,如下
BigDecimal bDouble1 = BigDecimal.valueOf(5.4); //2.3
BigDecimal bDouble2 = new BigDecimal(Double.toString(5.4)); //2.3
2.加减乘除(绝对值)
BigDecimal big1 = new BigDecimal("5");
BigDecimal big2 = new BigDecimal("40");
BigDecimal big3 = new BigDecimal("-20");
//加法
BigDecimal add = big1.add(big2);
//减法
BigDecimal subtract = big1.subtract(big2);
//乘法
BigDecimal multiply = big1.multiply(big2);
//除法
BigDecimal divide = big1.divide(big2);
//绝对值
BigDecimal abs = big3.abs();
System.out.println(" add :" + add); //45
System.out.println("subtract :" + subtract); //-35
System.out.println("multiply :" + multiply); //200
System.out.println(" divide :" + divide); //0.125
System.out.println(" abs :"+abs); //20
3.注意事项
1)System.out.println()中的数字默认是double类型的,double类型小数计算不精准。
2)使用BigDecimal类构造方法传入double类型时,计算的结果也是不精确的!
- BigDecimal都是不可变的(immutable)的,在进行每一步运算时,都会产生一个新的对象,所以在做加减乘除运算时千万要保存操作后的值。
4)使用除法函数在divide的时候要设置各种参数,要精确的小数位数和舍入模式,不然会出现报错
我们可以看到divide函数配置的参数如下
-
divide(BigDecimal divisor, int scale, RoundingMode roundingMode)
- 返回一个
BigDecimal
,其值为(this / divisor)
,其比例为指定。
即为 (BigDecimal divisor 除数, int scale 精确小数位, int roundingMode 舍入模式)
可以看到舍入模式有很多种BigDecimal.ROUND_XXXX_XXX, 具体都是什么意思呢
模式 | 描述 |
---|---|
CEILING | 正无穷大方向取整 |
FLOOR | 负无穷大方向取整 |
DOWN | 向 0 的方向取整 |
UP | 正数向正无穷大取整,负数向负无穷大取整(常用) |
HALF_UP | 5,6,7,8,9 向上取整、 1,2,3,4 向下取整、 常用的4舍5入 |
HALF_DOWN | 6,7,8,9 向上取整 1,2,3,4,5 向下取整 |
HALF_EVEN | 小数位是5时,判断整数部分是奇数就进位、 小数位是5时,判断整数部分是偶数就舍弃、 1,2,3,4, 舍弃、 6,7,8,9, 进位 |