前言

本篇介绍下BigDecimal;

没有实质的了解过BigDecimal之前,只大概了解过它是适用于精确计算的类型;

而我们常用的float和double这两个类型主要用于科学计算和工程计算,它们执行二进制浮点运算,是为了在广域数值范围上提供较为精确的快速近似计算而设计的。所以并不适合计算精确的数值

今天来学习下适用于商业计算的BigDecimal类型。

1.我们先看下通常计算挠头的事情

public static void main(String[] args) {
        System.out.println(0.1 + 0.2);
        System.out.println(0.2 - 0.05);
        System.out.println(0.2 * 0.1);
        System.out.println(0.3 / 0.1);
 }

//计算结果
0.30000000000000004
0.15000000000000002
0.020000000000000004
2.9999999999999996
// 我*你*

这结果谁看谁不头疼?但是是什么原因呢?

原因是因为的我们的计算机是二进制的,但是二进制是么有办法准确的表示浮点数的
CPU表示浮点数是由指数和尾数两部分组成的,很显然这样的表示方法会丢失一定的精度,所以有些浮点数我们计算的时候是会存在一定的误差的

2.我们看下BigDecimal

构造方法
BigDecimal(BigInteger val)BigInteger转换成 BigDecimal
BigDecimal(BigInteger unscaledVal, int scale) 将BigInteger的 BigInteger值和 int等级转换为 BigDecimal
BigDecimal(BigInteger unscaledVal, int scale, MathContext mc)BigInteger未缩放值和 int扩展转换为 BigDecimal ,根据上下文设置进行舍入。
BigDecimal(BigInteger val, MathContext mc) 根据上下文设置将 BigInteger转换为 BigDecimal舍入。
BigDecimal(char[] in) 一个转换的字符数组表示 BigDecimalBigDecimal ,接受字符作为的相同序列 BigDecimal(String)构造。
BigDecimal(char[] in, int offset, int len) 一个转换的字符数组表示 BigDecimalBigDecimal ,接受字符作为的相同序列 BigDecimal(String)构造,同时允许一个子阵列被指定。
BigDecimal(char[] in, int offset, int len, MathContext mc) 一个转换的字符数组表示 BigDecimalBigDecimal ,接受字符作为的相同序列 BigDecimal(String)构造,同时允许指定一个子阵列和用根据上下文设置进行舍入。
BigDecimal(char[] in, MathContext mc) 一个转换的字符数组表示 BigDecimalBigDecimal ,接受相同的字符序列作为 BigDecimal(String)构造与根据上下文设置进行舍入。
BigDecimal(double val)double转换为 BigDecimal ,这是 double的二进制浮点值的精确十进制表示。
BigDecimal(double val, MathContext mc)double转换为 BigDecimal ,根据上下文设置进行舍入。
BigDecimal(int val)intBigDecimal
BigDecimal(int val, MathContext mc)int转换为 BigDecimal ,根据上下文设置进行舍入。
BigDecimal(long val)longBigDecimal
BigDecimal(long val, MathContext mc)long转换为 BigDecimal ,根据上下文设置进行舍入。
BigDecimal(String val) 将BigDecimal的字符串表示 BigDecimal转换为 BigDecimal
BigDecimal(String val, MathContext mc) 一个转换的字符串表示 BigDecimalBigDecimal ,接受相同的字符串作为 BigDecimal(String)构造,利用根据上下文设置进行舍入。

找出4个最常用的方法

1.BigDecimal(int) 创建一个具有参数所指定整数值的对象。

2.BigDecimal(double) 创建一个具有参数所指定双精度值的对象。(不建议采用)

3.BigDecimal(String) 创建一个具有参数所指定以字符串表示的数值的对象 。

4.BigDecimal(long) 创建一个具有参数所指定长整数值的对象。

public static void main(String[] args) {
    int a = 2;
    double b = 2.3d;
    long c = 2L;
    String d = "2.3";
    BigDecimal bigDecimal = new BigDecimal(a);
    BigDecimal bDouble = new BigDecimal(b);
    BigDecimal bLong = new BigDecimal(c);
    BigDecimal bString = new BigDecimal(d);
    System.out.println("bigDecimal=" + bigDecimal);
    System.out.println("bDouble=" + bDouble);
    System.out.println("bLong=" + bLong);
    System.out.println("bString=" + bString);
}

//结果
bigDecimal=2
bDouble=2.29999999999999982236431605997495353221893310546875
bLong=2
bString=2.3

这是因为double的构造方法有一定的不可预知性。

建议是使用[BigDecimal(String val) 将BigDecimal的字符串表示 BigDecimal转换为 BigDecimal]

String的构造方法是完全可以预知的

如果非得使用double的时候,我们可以将double转为String后在使用String的构造函数Double.toString(double)

3.BigDecimal的方法

类型 说明
BigDecimal abs() 返回一个 BigDecimal ,其值为此 BigDecimal的绝对值,其缩放比例为 this.scale()
BigDecimal abs(MathContext mc) 返回一个 BigDecimal ,其值为此 BigDecimal的绝对值,根据上下文设置进行舍入。
BigDecimal add(BigDecimal augend) 返回 BigDecimal ,其值是 (this + augend) ,其标为 max(this.scale(), augend.scale())
BigDecimal add(BigDecimal augend, MathContext mc) 返回 BigDecimal ,其值是 (this + augend) ,根据上下文设置进行舍入。
byte byteValueExact() 将此 BigDecimal转换为 byte ,检查丢失的信息。
int compareTo(BigDecimal val) 将此 BigDecimal与指定的BigDecimal进行 BigDecimal
BigDecimal divide(BigDecimal divisor) 返回BigDecimal ,其值为(this / divisor) ,优先级为(this.scale() - divisor.scale()) ; 如果不能表示确切的商(因为它具有非终止的十进制扩展),则抛出一个ArithmeticException
BigDecimal divide(BigDecimal divisor, int roundingMode) 返回 BigDecimal ,其值是 (this / divisor) ,其标为 this.scale()
BigDecimal divide(BigDecimal divisor, int scale, int roundingMode) 返回一个 BigDecimal ,其值为 (this / divisor) ,其比例为指定。
BigDecimal divide(BigDecimal divisor, int scale, RoundingMode roundingMode) 返回一个 BigDecimal ,其值为 (this / divisor) ,其比例为指定。
BigDecimal divide(BigDecimal divisor, MathContext mc) 返回 BigDecimal ,其值是 (this / divisor) ,根据上下文设置进行舍入。
BigDecimal divide(BigDecimal divisor, RoundingMode roundingMode) 返回 BigDecimal ,其值是 (this / divisor) ,其标为 this.scale()
BigDecimal[] divideAndRemainder(BigDecimal divisor) 返回一个两元件 BigDecimal阵列含有的结果 divideToIntegralValue接着的结果 remainder上的两个操作数。
BigDecimal[] divideAndRemainder(BigDecimal divisor, MathContext mc) 返回一个两元件 BigDecimal阵列含有的结果 divideToIntegralValue接着的结果 remainder上与根据上下文设置进行舍入计算出的两个操作数。
BigDecimal divideToIntegralValue(BigDecimal divisor) 返回一个 BigDecimal ,它的值是 BigDecimal的整数部分 (this / divisor)取整。
BigDecimal divideToIntegralValue(BigDecimal divisor, MathContext mc) 返回值为 BigDecimal的整数部分的 (this / divisor)
double doubleValue() 将此 BigDecimal转换为 double
boolean equals(Object x) 将此 BigDecimal与指定的 Object进行比较以获得相等性。
float floatValue() 将此 BigDecimal转换为 float
int hashCode() 返回此 BigDecimal的哈希码。
int intValue() 将此 BigDecimal转换为 int
int intValueExact() 将此 BigDecimal转换为 int ,检查丢失的信息。
long longValue() 将此 BigDecimal转换为 long
long longValueExact() 将此 BigDecimal转换为 long ,检查丢失的信息。
BigDecimal max(BigDecimal val) 返回此 BigDecimalval
BigDecimal min(BigDecimal val) 返回此 BigDecimalval
BigDecimal movePointLeft(int n) 返回一个 BigDecimal ,相当于这个小数点,向左移动了 n个地方。
BigDecimal movePointRight(int n) 返回一个 BigDecimal ,相当于这个小数点移动了 n个地方。
BigDecimal multiply(BigDecimal multiplicand) 返回 BigDecimal ,其值是 (this × multiplicand),其标为 (this.scale() + multiplicand.scale())
BigDecimal multiply(BigDecimal multiplicand, MathContext mc) 返回 BigDecimal ,其值是 (this × multiplicand),根据上下文设置进行舍入。
BigDecimal negate() 返回 BigDecimal ,其值是 (-this) ,其标为 this.scale()
BigDecimal negate(MathContext mc) 返回 BigDecimal ,其值是 (-this) ,根据上下文设置进行舍入。
BigDecimal plus() 返回 BigDecimal ,其值是 (+this) ,其标为 this.scale()
BigDecimal plus(MathContext mc) 返回 BigDecimal ,其值是 (+this) ,根据上下文设置进行舍入。
BigDecimal pow(int n) 返回 BigDecimal ,其值是 (thisn),该电源,准确计算,使其具有无限精度。
BigDecimal pow(int n, MathContext mc) 返回 BigDecimal ,其值是 (thisn)。
int precision() 返回此 BigDecimalBigDecimal
BigDecimal remainder(BigDecimal divisor) 返回 BigDecimal ,其值是 (this % divisor)
BigDecimal remainder(BigDecimal divisor, MathContext mc) 返回 BigDecimal ,其值是 (this % divisor) ,根据上下文设置进行舍入。
BigDecimal round(MathContext mc) 返回 BigDecimal根据四舍五入 MathContext设置。
int scale() 返回此 规模 BigDecimal
BigDecimal scaleByPowerOfTen(int n) 返回一个BigDecimal,其数值等于( this * 10 n )。
BigDecimal setScale(int newScale) 返回一个 BigDecimal ,其大小是指定值,其值在数字上等于此 BigDecimal
BigDecimal setScale(int newScale, int roundingMode) 返回一个 BigDecimal ,其规模是指定值,其缩放值通过将此 BigDecimal的非标度值乘以10的适当功率来确定,以维持其总体值。
BigDecimal setScale(int newScale, RoundingMode roundingMode) 返回一个 BigDecimal ,其规模是指定值,其缩放值通过将该 BigDecimal的非标度值乘以10的适当功率来确定,以维持其整体值。
short shortValueExact() 将此 BigDecimal转换为 short ,检查丢失的信息。
int signum() 返回这个 BigDecimal的signum函数。
BigDecimal stripTrailingZeros() 返回一个 BigDecimal ,它在数字上等于此值, BigDecimal表示中删除任何尾随的零。
BigDecimal subtract(BigDecimal subtrahend) 返回 BigDecimal ,其值是 (this - subtrahend) ,其标为 max(this.scale(), subtrahend.scale())
BigDecimal subtract(BigDecimal subtrahend, MathContext mc) 返回 BigDecimal ,其值是 (this - subtrahend) ,根据上下文设置进行舍入。
BigInteger toBigInteger() 将此 BigDecimal转换为 BigInteger
BigInteger toBigIntegerExact() 将此 BigDecimal转换为 BigInteger ,检查丢失的信息。
String toEngineeringString() 如果需要指数,则使用工程符号返回此 BigDecimal的字符串表示形式。
String toPlainString() 返回没有指数字段的此 BigDecimal的字符串表示形式。
String toString() 返回此 BigDecimal的字符串表示,如果需要指数,则使用科学计数法。
BigDecimal ulp() 返回此 BigDecimal的最后一个位置的ulp(一个单位)的大小。
BigInteger unscaledValue() 返回一个 BigInteger ,其值是此 BigDecimal未缩放值
static BigDecimal valueOf(double val) 转换一个 doubleBigDecimal ,使用 double通过所提供的规范的字符串表示 Double.toString(double)方法。
static BigDecimal valueOf(long val)long值转换为 BigDecimal ,比例为零。
static BigDecimal valueOf(long unscaledVal, int scale)long值和 int比例转换为 BigDecimal

有点太多了 60个方法

简单列出我们常用的几个:

add(BigDecimal)     		//BigDecimal对象中的值相加,然后返回这个对象。
subtract(BigDecimal)  	//BigDecimal对象中的值相减,然后返回这个对象。 
multiply(BigDecimal)   	//BigDecimal对象中的值相乘,然后返回这个对象。 
divide(BigDecimal)   	 	//BigDecimal对象中的值相除,然后返回这个对象。 

toString()         			//将BigDecimal对象的数值转换成字符串。 
doubleValue()      			//将BigDecimal对象中的值以双精度数返回。 
floatValue()       			//将BigDecimal对象中的值以单精度数返回。 
longValue()        			//将BigDecimal对象中的值以长整数返回。 
intValue()         			//将BigDecimal对象中的值以整数返回。

简单的加减乘除方法;

需要单拿出来说一下的是divide(BigDecimal); 当不能整除的时候就会报错!

public static void main(String[] args) {
    BigDecimal a=new BigDecimal(6.5);
    BigDecimal b=new BigDecimal(1.2);
    System.out.println("a / b =" + 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:1690)
	at com.sqzy.softplat.malls.service.impl.OrderReturnServiceImpl.main(OrderReturnServiceImpl.java:226)

解决方法:

BigDecimal divide(BigDecimal divisor, int scale, int roundingMode) 返回一个 BigDecimal ,其值为 (this / divisor) ,其比例为指定。

我们看下这个入参;

​ 第一参数表示除数, 第二个参数表示小数点后保留位数,第三个参数表示舍入模式

也只有在除法或四舍五入的时候才用到舍入模式

  • ROUND_UP

public static final int ROUND_UP

舍入模式从零开始。 始终在非零丢弃分数之前增加数字。 请注意,该舍入模式不会降低计算值的大小。

  • 另请参见:

    Constant Field Values

  • ROUND_DOWN

    public static final int ROUND_DOWN
    

    舍入模式向零舍入。 不要在丢弃的分数之前递增数字(即截断)。 请注意,该舍入模式不会增加计算值的大小。

  • ROUND_CEILING

    public static final int ROUND_CEILING
    

    圆形模式向正无穷大转弯。 如果BigDecimal为正,则表现为ROUND_UP ; 如果为负,则表现为ROUND_DOWN 。 请注意,舍入模式不会降低计算值。

  • ROUND_FLOOR

    public static final int ROUND_FLOOR
    

    舍入模式向负无穷大转弯。 如果BigDecimal为正,则表现为ROUND_DOWN ; 如果为负,表现为ROUND_UP 。 请注意,舍入模式不会增加计算值。

  • ROUND_HALF_UP

    public static final int ROUND_HALF_UP
    

    四舍五入模式向“最近邻居”转弯,除非两个邻居都是等距的,在这种情况下是圆括弧的。 对于ROUND_UP如果丢弃的分数为0.5,则表现为; 否则,表现为ROUND_DOWN 。 请注意,这是我们大多数人在小学教学的舍入模式。

  • ROUND_HALF_DOWN

    public static final int ROUND_HALF_DOWN
    

    四舍五入模式向“最近邻居”转弯,除非这两个邻居都是等距离的,在这种情况下,这是倒圆的。 对于ROUND_UP如果丢弃的分数> 0.5,则表示行为; 否则,表现为ROUND_DOWN

  • ROUND_HALF_EVEN

    public static final int ROUND_HALF_EVEN
    

    四舍五入模式向“最近邻居”转弯,除非两个邻居都是等距离的,在这种情况下,向着邻居方向转移。 对于ROUND_HALF_UP行为,如果丢弃的分数的左边的数字是奇数的; 像ROUND_HALF_DOWN一样,如果它是均匀的。 请注意,这是在一系列计算中重复应用时最小化累积误差的舍入模式。

  • ROUND_UNNECESSARY

    public static final int ROUND_UNNECESSARY
    

    舍入模式来确定所请求的操作具有精确的结果,因此不需要舍入。 如果在产生不精确结果的操作上指定了舍入模式,则抛出ArithmeticException

根据自己的需求来传入参数:

public static void main(String[] args) {
    BigDecimal a=new BigDecimal(6.5);
    BigDecimal b=new BigDecimal(1.2);
    System.out.println("a / b =" + a.divide(b,2, RoundingMode.HALF_UP));
}

//结果 5.41666667
a / b =5.42

另外还有个小方法:比较BigDecimal的大小

  • compareTo

    public int compareTo(BigDecimal val)
    

    将此BigDecimal与指定的BigDecimal进行BigDecimal 。 两个BigDecimal对象的价值相等但具有不同的比例(如2.0和2.00)被认为是相等的这种方法。 该方法优先于六个布尔比较运算符(<,==,>,> =,!=,<=)中的每一个的各个方法。 用于执行这些比较的建议成语是: (x.compareTo(y) < op > 0) ,其中< op >是六个比较运算符之一。

    • Specified by:

      compareTo中的 Comparable<BigDecimal>

    • 参数

      val - BigDecimal ,对此 BigDecimal进行比较。

    • 结果

      -1,0或1,因为这个 BigDecimal在数字上小于,等于或大于 val

    • equals

      public boolean equals(Object x)
      

      将此BigDecimal与指定的Object进行比较以获得相等性。 与compareTo不同,该方法认为两个BigDecimal对象只有在值和比例相等时才相等(因此,当通过此方法比较时,2.0不等于2.00)。

      • 重写:

        equalsObject

      • 参数

        x - Object要对这 BigDecimal进行比较。

      • 结果

        true当且仅当指定的 ObjectBigDecimal ,其值和比例等于该 BigDecimal的时候。

      • 另请参见:

        compareTo(java.math.BigDecimal)hashCode()

4.格式化

既然是专业做商业计算的BigDecimal怎么能没有专业的商业计算的风格

接下来我们小小的计算下某付宝的七日年化

public static void main(String[] args) {
    //货币格式化
    NumberFormat currency = NumberFormat.getCurrencyInstance();
    //百分比格式化
    NumberFormat percent = NumberFormat.getPercentInstance();
    //百分比小数点
    percent.setMaximumFractionDigits(3);

    //本金
    BigDecimal loanAmount = new BigDecimal("8000.00");
    //七日年化率 %
    BigDecimal interestRate = new BigDecimal("0.0164");

    //公式 10000*2.3%÷365≈0.63元
    BigDecimal interest = loanAmount.multiply(interestRate).divide(new BigDecimal(365),2,RoundingMode.UP);

    System.out.println("七日年化:\t" + percent.format(interestRate));
    System.out.println("贷款金额:\t" + currency.format(loanAmount));
    System.out.println("每日:\t" + currency.format(interest));
}


//计算结果 8000块在七日年化率为1.64%的情况下,每天的收益大概是0.36左右
七日年化:	1.64%
本金:	¥8,000.00
每日:	¥0.36