bigdecimal
Java 在
java.math包中提供的 API 类 BigDecimal,用来对超过 16 位有效位的数进行精确的运算。双精度浮点型变量 double 可以处理 16 位有效数,但在实际应用中,可能需要对更大或者更小的数进行运算和处理。我们都知道浮点型变量在进行计算的时候会出现丢失精度的问题。Java 中
float的精度为6-7位有效数字。double的精度为15-16位
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
BigDecimal 四舍五入保留两位小数
BigDecimal batDecimal = new BigDecimal(ObjectUtils.getDisplayString(batAmount))
.divide(factorDecimal, 2, BigDecimal.ROUND_HALF_UP);
BigDecimal 舍位模式
ROUND_UP
进位舍入模式,在舍弃部分非零情况下,向前进位加 1,始终增加数字(始终对非零舍弃部分前面的数字加 1)。
ROUND_DOWN
截断舍入模式,始终不进位。注意,此舍入模式始终不会增加计算值的大小。
ROUND_CEILING
舍入模式朝向正无穷大,如果 BigDecimal 是正的,则表现为 ROUND_UP;如果为负,则表现为 ROUND_DOWN。请注意,此舍入模式永远不会降低计算值。
ROUND_FLOOR
舍入模式朝向负无穷大,如果 BigDecimal 是正的,则表现为 ROUND_DOWN;如果为负,则表现为 ROUND_UP。请注意,此舍入模式永远不会增加计算值。
ROUND_HALF_UP
向“最接近的”数字舍入,如果与两个相邻数字的距离相等,在这种情况下。如果舍弃部分大于等于 0.5,则表现为 ROUND_UP,否则,表现为 ROUND_DOWN
ROUND_HALF_DOWN
向“最接近的”数字舍入,如果与两个相邻数字的距离相等,在这种情况下。如果舍弃部分大于 0.5,则表现为 ROUND_UP;否则,表现为 ROUND_DOWN。
ROUND_HALF_EVEN(银行家舍入法)
向“最接近的”数字舍入,如果与两个相邻数字的距离相等,在这种情况下,则向相邻的偶数舍入。即:
- 如果舍弃部分左边的数字为奇数,则舍入行为与 ROUND_HALF_UP 相同;
- 如果舍弃部分左边的数字为偶数,则舍入行为与 ROUND_HALF_DOWN 相同。
::: tip ROUND_HALF_EVEN(银行家舍入法) 注意,在重复进行一系列计算时,此舍入模式可以将累加错误减到最小。 :::
ROUND_UNNECESSARY
断言请求的操作具有精确的结果,因此不需要舍入。
指定此舍入模式时,如果无法获得精确结果,则抛出 ArithmeticException
MathContext 运算上下文(设置有效数字位数)
new MathContext(6, RoundingMode.UP)
- 6:表示最大有效数字位数为6位。(有效精度即:从左边第一个不是0的数字起,到末位数止),有效数字位数越多,精度越高,计算越准确(属于数学范畴,大家应该都能明白)
- RoundingMode.UP:处理有效精度时可能需要用到的舍位模式
BigDecimal 常见异常
- java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result
除法的时候出现异常 - 通过 BigDecimal 的 divide 方法进行除法时当不整除,出现无限循环小数时,就会抛异常:
java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.
解决方法:divide方法设置精确的小数点,如:divide(xxxxx,2)
BigDecimal 格式化
由于
NumberFormat类的 format()方法可以使用 BigDecimal 对象作为其参数,可以利用 BigDecimal 对超出 16 位有效数字的货币值,百分值,以及一般数值进行格式化控制。
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
BigDecimal 大小比较
int a = bigdemical.compareTo(bigdemical2);
// a = -1,表示bigdemical小于bigdemical2;
// a = 0,表示bigdemical等于bigdemical2;
// a = 1,表示bigdemical大于bigdemical2;
API
构造器:
| 构造器 | 描述 |
|---|---|
| BigDecimal(int) | 创建一个具有参数所指定整数值的对象。 |
| BigDecimal(double) | 创建一个具有参数所指定双精度值的对象。 |
| BigDecimal(long) | 创建一个具有参数所指定长整数值的对象。 |
| BigDecimal(String) | 创建一个具有参数所指定以字符串表示的数值的对象。 |
函数:
| 方法 | 描述 |
|---|---|
| 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)构造器创建对象才有意义。其他的如 BigDecimal b = new BigDecimal(1)这种,还是会发生精度丢失的问题。如下代码
BigDecimal a = new BigDecimal(1.01);
BigDecimal b = new BigDecimal(1.02);
BigDecimal c = new BigDecimal("1.01");
BigDecimal d = new BigDecimal("1.02");
System.out.println(a.add(b));
System.out.println(c.add(d));
// 输出:
// 2.0300000000000000266453525910037569701671600341796875
// 2.03
double 由于有 exp 位,可以存 16 位以上的数字,但是需要以低位的不精确作为代价。如果需要高于 19 位数字的精确存储,则必须用
BigInteger来保存
声明 BigDecimal 对象的时候一定要使用它构造参数为 String 的类型的构造器。
float 和 double 只能用来做科学计算和工程计算。商业运算中我们要使用BigDecimal。
正确运用 BigDecimal
另外,BigDecimal 所创建的是对象,我们不能使用传统的+、-、*、/等算术运算符直接对其对象进行数学运算,而必须调用其相对应的方法。方法中的参数也必须是 BigDecimal 的对象
public class BigDecimalUtil {
//工具类写一个私有无参构造函数
private BigDecimalUtil {
}
public static BigDecimal add(double v1, double v2) {// v1 + v2
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.add(b2);
}
public static BigDecimal sub(double v1, double v2) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.subtract(b2);
}
public static BigDecimal mul(double v1, double v2) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.multiply(b2);
}
public static BigDecimal div(double v1, double v2) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
// 2 = 保留小数点后两位 ROUND_HALF_UP = 四舍五入
return b1.divide(b2, 2, BigDecimal.ROUND_HALF_UP);// 应对除不尽的情况
}
}
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;
}
}