一、BigDecimal概述
1.1、 Java在java.math包中提供的API类BigDecimal,用来对超过16位有效位的数进行精确的运算。在大多数情况下,计算的结果是准确的,float和double只能用来做科学计算或者是工程计算,如果业务涉及银行、帐户、计费等领域,选择BigDecimal会更加合适,提供了精确的数值计算。
1.1.1. 我们先来回顾下浮点数:
举个简单的小例子:78.375,是一个正小数。要在计算机中存储这个数,需要把它表示为浮点数的格式,先执行二进制转换:
PS:
二进制的小数点和十进制的小数点是不同的。二进制小数点后是2的负幂,十进制是10的负幂。
小数的二进制转换(浮点数)
78.375的整数部分 78:
小数部分 0.375:
所以,78.375的二进制形式就是1001110.011
然后,使用二进制科学记数法,有
注意,转换后用二进制科学记数法表示的这个数,有底有指数有小数部分,这个就叫做浮点数。
在计算机中,保存这个数使用的是浮点表示法,分为三大部分:
第一部分用来存储符号位(sign), 用来区分正负数,这里是0,表示正数;
第二部分用来存储指数(exponent), 这里的指数是十进制的6;
第三部分用来存储小数(fraction), 这里的小数部分是001110011。
1.1.2. 再来看Float和Double:
float 类型是32位,是单精度浮点表示法:
符号位(sign)占用1位,用来表示正负数,
指数位(exponent)占用8位,用来表示指数,
小数位(fraction)占用23位,用来表示小数,不足位数补0。
double 类型是64位,是双精度浮点表示法:
符号位占用1位,指数位占用11位,小数位占用52位。
1.2、 双精度浮点型变量double可以精确处理16位有效数(double 的小数位有 52 位,对应十进制最大值为 4 503 599 627 370 496,这个数有 16 位,所以计算精度只能百分百保证十进制的 15 位运算),float可以精确处理7为有效数字(能表示的最大的十进制数为 2 的 23 次方,即 8388608,十进制的 7 位,严格点来说,精度只能百分百保证十进制的 6 位运算)。
但在实际应用中,可能需要对更大的数进行运算和处理。一般情况下,对于那些不需要准确计算精度的数字,我们可以直接使用Float和Double处理,但是Double.valueOf(String)和Float.valueOf(String)会丢失精度。所以开发中,如果我们需要精确的对大数字计算的结果,则必须使用BigDecimal类来操作。
1.3、 BigDecimal所创建的是对象,故我们不能使用传统的+、-、x、÷ 等算术运算符直接对其对象进行数学运算,而必须调用其相对应的方法。方法中的参数也必须是BigDecimal的对象。构造器是类的特殊方法,专门用来创建对象,特别是带有参数的对象。
二、BigDecimal常用构造函数
2.1、比较常用的构造函数
函数 | 含义 |
---|---|
BigDecimal(int) | 创建一个具有参数所指定整数值的对象 |
BigDecimal(double) | 创创建一个具有参数所指定双精度值的对象 |
BigDecimal(long) | 创建一个具有参数所指定长整数值的对象 |
BigDecimal(String) | 创建一个具有参数所指定以字符串表示的数值的对象 |
2.2、使用问题分析
使用示例:
BigDecimal bigDecimal1 = new BigDecimal(0.1);
System.out.println("bigDecimal1:" + bigDecimal1);
System.out.println("________________________________");
BigDecimal bigDecimal2 = BigDecimal.valueOf(0.1);
System.out.println("bigDecimal2:" + bigDecimal2);
System.out.println("________________________________");
BigDecimal bigDecimal3 = new BigDecimal("0.1");
System.out.println("bigDecimal3:" + bigDecimal3);
}
BigDecimal.valueOf()源码:
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));//会为double类型的value转为String类型
}
执行结果:
bigDecimal1:0.1000000000000000055511151231257827021181583404541015625
________________________________
bigDecimal2:0.1
________________________________
bigDecimal3:0.1
原因分析:
1)参数类型为double的构造方法的结果有一定的不可预知性。有人可能认为在Java中写入newBigDecimal(0.1)所创建的BigDecimal正好等于 0.1,但是它实际上等于0.1000000000000000055511151231257827021181583404541015625。这是因为0.1无法准确地表示为 double(或者说对于该情况,不能表示为任何有限长度的二进制小数)。这样,传入到构造方法的值不会正好等于 0.1(虽然表面上等于该值)。
2)String 构造方法是完全可预知的:写入 newBigDecimal(“0.1”) 将创建一个 BigDecimal,它正好等于预期的 0.1。因此,比较而言,通常建议优先使用String构造方法。
3)当double必须用作BigDecimal的源时,请注意,此构造方法提供了一个准确转换;它不提供与以下操作相同的结果:先使用Double.toString(double)方法,然后使用BigDecimal(String)构造方法,将double转换为String。要获取该结果,请使用static valueOf(double)方法。
三、BigDecimal常用方法详解
3.1、常用方法
标题 | 含义 |
---|---|
add(BigDecimal) | BigDecimal对象中的值相加,返回BigDecimal对象 |
subtract(BigDecimal) | BigDecimal对象中的值相减,返回BigDecimal对象 |
multiply(BigDecimal) | BigDecimal对象中的值相乘,返回BigDecimal对象 |
divide(BigDecimal) | BigDecimal对象中的值相除,返回BigDecimal对象 |
toString() | 将BigDecimal对象中的值转换成字符串 |
doubleValue() | 将BigDecimal对象中的值转换成双精度数 |
floatValue() | 将BigDecimal对象中的值转换成单精度数 |
longValue() | 将BigDecimal对象中的值转换成长整数 |
intValue() | 将BigDecimal对象中的值转换成整数 |
3.2、BigDecimal大小比较
java中对BigDecimal比较大小一般用的是bigdemical的compareTo方法
int a = bigdemical.compareTo(bigdemical2)
返回结果分析:
a = -1, 表示bigdemical小于bigdemical2;
a = 0, 表示bigdemical等于bigdemical2;
a = 1, 表示bigdemical大于bigdemical2。
举例:a大于等于b
new bigdemica(a).compareTo(new bigdemical(b)) >= 0
四、BigDecimal格式化
1. 由于NumberFormat类的format()方法可以使用BigDecimal对象作为其参数,可以利用BigDecimal对超出16位有效数字的货币值,百分值,以及一般数值进行格式化控制。
以利用BigDecimal对货币和百分比格式化为例。首先,创建BigDecimal对象,进行BigDecimal的算术运算后,分别建立对货币和百分比格式化的引用,最后利用BigDecimal对象作为format()方法的参数,输出其格式化的货币值和百分比。
NumberFormat currency = NumberFormat.getCurrencyInstance(); //建立货币格式化引用
NumberFormat percent = NumberFormat.getPercentInstance(); //建立百分比格式化引用
percent.setMaximumFractionDigits(3); //百分比小数点最多3位
BigDecimal loanAmount = new BigDecimal("15000.48"); //贷款金额
BigDecimal interestRate = new BigDecimal("0.008"); //利率
BigDecimal interest = loanAmount.multiply(interestRate); //相乘
System.out.println("贷款金额:\t" + currency.format(loanAmount));
System.out.println("利率:\t" + percent.format(interestRate));
System.out.println("利息:\t" + currency.format(interest));
结果:
贷款金额: ¥15,000.48 利率: 0.8% 利息: ¥120.00
2. BigDecimal格式化保留2为小数,不足则补0:
public class NumberFormat {
public static void main(String[] s) {
System.out.println(formatToNumber(new BigDecimal("3.435")));
System.out.println(formatToNumber(new BigDecimal(0)));
System.out.println(formatToNumber(new BigDecimal("0.00")));
System.out.println(formatToNumber(new BigDecimal("0.001")));
System.out.println(formatToNumber(new BigDecimal("0.006")));
System.out.println(formatToNumber(new BigDecimal("0.206")));
}
/**
* 1.1.0~1之间的BigDecimal小数,格式化后失去前面的0,则前面直接加上0。
* 2.传入的参数等于0,则直接返回字符串"0.00"
* 3.大于1的小数,直接格式化返回字符串
*
* @param obj传入的小数
* @return
*/
public static String formatToNumber(BigDecimal obj) {
DecimalFormat df = new DecimalFormat("#.00");
if (obj.compareTo(BigDecimal.ZERO) == 0) {
return "0.00";
} else if (obj.compareTo(BigDecimal.ZERO) > 0 && obj.compareTo(new BigDecimal(1)) < 0) {
return "0" + df.format(obj).toString();
} else {
return df.format(obj).toString();
}
}
结果为:
3.44
0.00
0.00
0.00
0.01
0.21
五、舍入模式
5.1、RoundingMode.DOWN 等价枚举: BigDecimal.ROUND_DOWN
舍位原则: 粗暴截断舍弃位,不考虑任何进位舍位操作
例: Scale = 2
Origin:3.33333333333333 OutPut:3.33
Origin:1.976744186046512 OutPut:1.97
Origin:-4.868913857677903 OutPut:-4.86
Origin:-2.307692307692308 OutPut:-2.3
5.2、RoundingMode.UP 等价枚举: BigDecimal.ROUND_UP
舍位原则: 精度保留的最后一位,朝远离数轴的方向进位。正数+1,负数-1
例: Scale = 2
Origin:1.976744186046512 OutPut:1.98
Origin:-4.868913857677903 OutPut:-4.87
Origin:-2.307692307692308 OutPut:-2.31
5.3、RoundingMode.CEILING 等价枚举: BigDecimal.ROUND_CEILING
舍位原则: 精度保留的最后一位,朝数轴正方向 round。正数时等价于 UP,负数时等价于 DOWN
例: Scale = 2
Origin:1.976744186046512 OutPut:1.98
Origin:-4.868913857677903 OutPut:-4.86
Origin:-2.307692307692308 OutPut:-2.3
5.4、RoundingMode.FLOOR 等价枚举: BigDecimal.ROUND_FLOOR
舍位原则: 与 CEILING 相反,在精度最后一位,朝数轴负方向 round。正数时等价于 DOWN,负数时等价于 UP
例: Scale = 2
Origin:1.976744186046512 OutPut:1.97
Origin:-4.868913857677903 OutPut:-4.87
Origin:-2.307692307692308 OutPut:-2.31
5.5、RoundingMode.HALF_UP 等价枚举: BigDecimal.ROUND_HALF_UP
舍位原则: 四舍五入
例: Scale = 2
Origin:1.976744186046512 OutPut:1.98
Origin:-4.868913857677903 OutPut:-4.87
Origin:-2.307692307692308 OutPut:-2.31
Origin:3.555 OutPut:3.56
Origin:-3.555 OutPut:-3.56
5.6、RoundingMode.HALF_DWON 等价枚举: BigDecimal.ROUND_HALF_DWON
舍位原则: 五舍六入
例: Scale = 2
Origin:-3.555 OutPut:-3.55
5.7、RoundingMode.HALF_EVEN 等价枚举: BigDecimal.ROUND_HALF_EVEN
舍位原则: 又称为“银行家舍入”,当舍入位非 5 时,四舍六入。当舍入位为5时,看舍入位前一位,即保留的最后一位,当其为奇数时进位,否则舍位。
例: Scale = 2
Origin:3.535 OutPut:3.54
Origin:-3.535 OutPut:-3.54
Origin:3.585 OutPut:3.58
Origin:-3.585 OutPut:-3.58
5.8、RoundingMode.UNNECESSARY 等价枚举: BigDecimal.ROUND_UNNECESSARY
舍位原则: 断言请求,认为传入的数据一定满足设置的小数模式,如果不满足,抛出 ArithmeticException 异常。
例: Scale = 2
Origin:3.530 OutPut:3.53
Origin:3.531 OutPut:ArithmeticException
六、BigDecimal常见异常
常用的两个divide重载方法:
/**
* 第一个参数时被除数
* 第二个参数是选择的舍入模式
*/
public BigDecimal divide(BigDecimal divisor, int roundingMode);
/**
* 第一个参数时被除数
* 第二个参数是一个整数类型,实际意思是最终结果小数点后面保留几位小数
* 第三个参数就是小数点后面保留小数时省略或者进位的选择模式,该模式可以有多种选择
*/
public BigDecimal divide(BigDecimal divisor,int scale, int roundingMode);
示例:
BigDecimal divisor = new BigDecimal("1");
BigDecimal dividend = new BigDecimal("3");
BigDecimal case1 = divisor.divide(dividend, 3, BigDecimal.ROUND_UP);
System.out.println("使用除法ROUND_UP:" + case1);
BigDecimal case2 = divisor.divide(dividend, 3, BigDecimal.ROUND_DOWN);
System.out.println("使用除法ROUND_DOWN:" + case2);
BigDecimal case3 = divisor.divide(dividend, 3, BigDecimal.ROUND_CEILING);
System.out.println("使用除法ROUND_CEILING:" + case3);
BigDecimal case4 = divisor.divide(dividend, 3, BigDecimal.ROUND_FLOOR);
System.out.println("使用除法ROUND_FLOOR:" + case4);
BigDecimal case5 = divisor.divide(dividend, 3, BigDecimal.ROUND_HALF_UP);
System.out.println("使用除法ROUND_HALF_UP:" + case5);
BigDecimal case6 = divisor.divide(dividend, 3, BigDecimal.ROUND_HALF_DOWN);
System.out.println("使用除法ROUND_HALF_DOWN:" + case6);
BigDecimal case7 = divisor.divide(dividend, 3, BigDecimal.ROUND_HALF_EVEN);
System.out.println("使用除法ROUND_HALF_EVEN:" + case7);
BigDecimal case8 = divisor.divide(dividend, 3, BigDecimal.ROUND_UNNECESSARY);
System.out.println("使用除法ROUND_UNNECESSARY:" + case8);
结果:
使用除法ROUND_UP:0.334
使用除法ROUND_DOWN:0.333
使用除法ROUND_CEILING:0.334
使用除法ROUND_FLOOR:0.333
使用除法ROUND_HALF_UP:0.333
使用除法ROUND_HALF_DOWN:0.333
使用除法ROUND_HALF_EVEN:0.333
Exception in thread "main" java.lang.ArithmeticException: Rounding necessary
at java.math.BigDecimal.commonNeedIncrement(BigDecimal.java:4179)
at java.math.BigDecimal.needIncrement(BigDecimal.java:4235)
at java.math.BigDecimal.divideAndRound(BigDecimal.java:4143)
at java.math.BigDecimal.divide(BigDecimal.java:5214)
at java.math.BigDecimal.divide(BigDecimal.java:1564)
at BigDec.main(BigDec.java:34)
※生产常见问题不设置小数点及舍入模式:
//不设置小数点及舍入模式
BigDecimal res9 = divisor.divide(dividend);
System.out.println("除法ROUND_UNNECESSARY:"+res9);
结果:
Exception in thread "main" java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.
at java.math.BigDecimal.divide(BigDecimal.java:1693)
at BigDec.main(BigDec.java:37)
原因分析:
通过BigDecimal的divide方法进行除法时当不整除,出现无限循环小数时,就会抛异常:java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.
解决方法: divide方法设置精确的小数点,如:divide(xxxxx,2)
七、BigDecimal总结
7.1、总结
- 在需要精确的小数计算时再使用BigDecimal,BigDecimal的性能比double和float差,在处理庞大,复杂的运算时尤为明显。故一般精度的计算没必要使用BigDecimal。
- 尽量使用参数类型为String的构造函数。
- BigDecimal都是不可变的(immutable)的, 在进行每一次四则运算时,都会产生一个新的对象 ,所以在做加减乘除运算时要记得要保存操作后的值。
7.2、方法推荐
package com.vivo.ars.util;
import java.math.BigDecimal;
/**
* 用于高精确处理常用的数学运算
*/
public class ArithmeticUtils {
//默认除法运算精度
private static final int DEF_DIV_SCALE = 10;
/**
* 提供精确的加法运算
*
* @param v1 被加数
* @param v2 加数
* @return 两个参数的和
*/
public static double add(double v1, double v2) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.add(b2).doubleValue();
}
/**
* 提供精确的加法运算
*
* @param v1 被加数
* @param v2 加数
* @return 两个参数的和
*/
public static BigDecimal add(String v1, String v2) {
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.add(b2);
}
/**
* 提供精确的加法运算
*
* @param v1 被加数
* @param v2 加数
* @param scale 保留scale 位小数
* @return 两个参数的和
*/
public static String add(String v1, String v2, int scale) {
if (scale < 0) {
throw new IllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.add(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
}
/**
* 提供精确的减法运算
*
* @param v1 被减数
* @param v2 减数
* @return 两个参数的差
*/
public static double sub(double v1, double v2) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.subtract(b2).doubleValue();
}
/**
* 提供精确的减法运算。
*
* @param v1 被减数
* @param v2 减数
* @return 两个参数的差
*/
public static BigDecimal sub(String v1, String v2) {
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.subtract(b2);
}
/**
* 提供精确的减法运算
*
* @param v1 被减数
* @param v2 减数
* @param scale 保留scale 位小数
* @return 两个参数的差
*/
public static String sub(String v1, String v2, int scale) {
if (scale < 0) {
throw new IllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.subtract(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
}
/**
* 提供精确的乘法运算
*
* @param v1 被乘数
* @param v2 乘数
* @return 两个参数的积
*/
public static double mul(double v1, double v2) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.multiply(b2).doubleValue();
}
/**
* 提供精确的乘法运算
*
* @param v1 被乘数
* @param v2 乘数
* @return 两个参数的积
*/
public static BigDecimal mul(String v1, String v2) {
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.multiply(b2);
}
/**
* 提供精确的乘法运算
*
* @param v1 被乘数
* @param v2 乘数
* @param scale 保留scale 位小数
* @return 两个参数的积
*/
public static double mul(double v1, double v2, int scale) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return round(b1.multiply(b2).doubleValue(), scale);
}
/**
* 提供精确的乘法运算
*
* @param v1 被乘数
* @param v2 乘数
* @param scale 保留scale 位小数
* @return 两个参数的积
*/
public static String mul(String v1, String v2, int scale) {
if (scale < 0) {
throw new IllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.multiply(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
}
/**
* 提供(相对)精确的除法运算,当发生除不尽的情况时,精确到
* 小数点以后10位,以后的数字四舍五入
*
* @param v1 被除数
* @param v2 除数
* @return 两个参数的商
*/
public static double div(double v1, double v2) {
return div(v1, v2, DEF_DIV_SCALE);
}
/**
* 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指
* 定精度,以后的数字四舍五入
*
* @param v1 被除数
* @param v2 除数
* @param scale 表示表示需要精确到小数点以后几位。
* @return 两个参数的商
*/
public static double div(double v1, double v2, int scale) {
if (scale < 0) {
throw new IllegalArgumentException("The scale must be a positive integer or zero");
}
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).doubleValue();
}
/**
* 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指
* 定精度,以后的数字四舍五入
*
* @param v1 被除数
* @param v2 除数
* @param scale 表示需要精确到小数点以后几位
* @return 两个参数的商
*/
public static String div(String v1, String v2, int scale) {
if (scale < 0) {
throw new IllegalArgumentException("The scale must be a positive integer or zero");
}
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v1);
return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).toString();
}
/**
* 提供精确的小数位四舍五入处理
*
* @param v 需要四舍五入的数字
* @param scale 小数点后保留几位
* @return 四舍五入后的结果
*/
public static double round(double v, int scale) {
if (scale < 0) {
throw new IllegalArgumentException("The scale must be a positive integer or zero");
}
BigDecimal b = new BigDecimal(Double.toString(v));
return b.setScale(scale, BigDecimal.ROUND_HALF_UP).doubleValue();
}
/**
* 提供精确的小数位四舍五入处理
*
* @param v 需要四舍五入的数字
* @param scale 小数点后保留几位
* @return 四舍五入后的结果
*/
public static String round(String v, int scale) {
if (scale < 0) {
throw new IllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimal b = new BigDecimal(v);
return b.setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
}
/**
* 取余数
*
* @param v1 被除数
* @param v2 除数
* @param scale 小数点后保留几位
* @return 余数
*/
public static String remainder(String v1, String v2, int scale) {
if (scale < 0) {
throw new IllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.remainder(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
}
/**
* 取余数 BigDecimal
*
* @param v1 被除数
* @param v2 除数
* @param scale 小数点后保留几位
* @return 余数
*/
public static BigDecimal remainder(BigDecimal v1, BigDecimal v2, int scale) {
if (scale < 0) {
throw new IllegalArgumentException(
"The scale must be a positive integer or zero");
}
return v1.remainder(v2).setScale(scale, BigDecimal.ROUND_HALF_UP);
}
/**
* 比较大小
*
* @param v1 被比较数
* @param v2 比较数
* @return 如果v1 大于v2 则 返回true 否则false
*/
public static boolean compare(String v1, String v2) {
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
int bj = b1.compareTo(b2);
boolean res;
if (bj > 0)
res = true;
else
res = false;
return res;
}
}
参考引用:
www.cnblogs.com/zhangyinhua…
www.zhihu.com/question/46…
blog.csdn.net/yuanlaijike…