简介
JAVA的double型数据以及float类型的数据均不能进行精确计算,许多编程语言也是一样,这与计算机的底层原理有关。
因此计算得出的结果往往超出预期。
尤其是在金融行业,计算价格或者银行业务的钱的计算。精确计算变得尤为重要。
虽然我们可以通过四舍五入的方式来处理结果,但是这样做就意味着存在着误差。
场景分析
比如说下面这些计算,我知道结果应该是精确的数字,计算机并没有计算出我们想要的结果。
/**
* @author wzm
* @version 1.0.0
* @date 2020/1/25 14:24
**/
public class MathTest {
public static void main(String[] args) {
System.out.println(0.05 + 0.01);
System.out.println(1.0 - 0.43);
System.out.println(2.03 * 10);
System.out.println(3.3 / 10);
}
}
计算结果:
0.060000000000000005 0.5700000000000001 20.299999999999997 0.32999999999999996
BigDecimal
Java中提供精确计算的类。java.math.BigDecimal
四则运算
import java.math.BigDecimal;
/**
* @author wzm
* @version 1.0.0
* @date 2020/1/25 14:24
**/
public class BigDecTest {
public static void main(String[] args) {
BigDecimal a = new BigDecimal("10");
BigDecimal b = new BigDecimal("5");
BigDecimal c;
//加法
c = a.add(b);
System.out.println("加法运算:" + c);
//减法
c = a.subtract(b);
System.out.println("加法运算:" + c);
//除法
c = a.multiply(b);
System.out.println("除法运算:" + c);
//乘法
c = a.divide(b, BigDecimal.ROUND_CEILING);
System.out.println("乘法运算:" + c);
}
}
比较大小
BigDecimal v1 = new BigDecimal("-1");
BigDecimal v2 = new BigDecimal("3");
int r = v1.compareTo(v2);
System.out.println(r);
* if r==0 --> v1等于v2
* if r==1 --> v1大于v2
* if r==-1 --> v1小于v2
舍入模式
BigDecimal定义了以下舍入模式,只有在做除法运算或四舍五入时才会用到舍入模式。
ROUND_CEILING
ROUND_CEILING模式是向正无穷大方向舍入。结果往较大的数值靠。
import java.math.BigDecimal;
/**
* @author wzm
* @version 1.0.0
* @date 2020/1/25 14:24
**/
public class BigDecTest {
public static void main(String[] args) {
int a = 2;
int b = BigDecimal.ROUND_CEILING;
System.out.println(new BigDecimal("1.01").setScale(a, b));
System.out.println(new BigDecimal("1.0100").setScale(a, b));
System.out.println(new BigDecimal("1.011").setScale(a, b));
System.out.println(new BigDecimal("1.01001").setScale(a, b));
System.out.println(new BigDecimal("1.014").setScale(a, b));
System.out.println(new BigDecimal("-1.01").setScale(a, b));
System.out.println(new BigDecimal("-1.0100").setScale(a, b));
System.out.println(new BigDecimal("-1.011").setScale(a, b));
System.out.println(new BigDecimal("-1.01001").setScale(a, b));
System.out.println(new BigDecimal("-1.014").setScale(a, b));
}
}
结果:
1.01 1.01 1.02 1.02 1.02 -1.01 -1.01 -1.01 -1.01 -1.01
ROUND_FLOOR
ROUND_FLOOR模式是向负无穷大方向舍入。结果往较小的数值靠。
import java.math.BigDecimal;
/**
* @author wzm
* @version 1.0.0
* @date 2020/1/25 14:24
**/
public class BigDecTest {
public static void main(String[] args) {
int a = 2;
int b = BigDecimal.ROUND_FLOOR;
System.out.println(new BigDecimal("1.01").setScale(a, b));
System.out.println(new BigDecimal("1.0100").setScale(a, b));
System.out.println(new BigDecimal("1.011").setScale(a, b));
System.out.println(new BigDecimal("1.01001").setScale(a, b));
System.out.println(new BigDecimal("1.014").setScale(a, b));
System.out.println(new BigDecimal("-1.01").setScale(a, b));
System.out.println(new BigDecimal("-1.0100").setScale(a, b));
System.out.println(new BigDecimal("-1.011").setScale(a, b));
System.out.println(new BigDecimal("-1.01001").setScale(a, b));
System.out.println(new BigDecimal("-1.014").setScale(a, b));
}
}
结果:
1.01 1.01 1.01 1.01 1.01 -1.01 -1.01 -1.02 -1.02 -1.02
ROUND_DOWN
ROUND_DOWN模式是向靠近零的方向舍入。
ROUND_UP
ROUND_UP模式是向远离零的方向舍入。
ROUND_ UNNECESSARY
ROUND_ UNNECESSARY模式是不使用舍入模式。如果可以确保计算结果是精确的,则可以指定此模式,否则如果指定了使用此模式却遇到了不精确的计算结果,则抛出ArithmeticException。
ROUND_HALF_DOWN
ROUND_HALF_DOWN模式是向距离最近的一边舍入,如果两边距离相等,就向靠近零的方向舍入。
ROUND_HALF_UP
ROUND_HALF_UP模式是向距离最近的一边舍入,如果两边距离相等,就向远离零的方向舍入。这个模式在实际的场景中比较常用。
ROUND_HALF_EVEN
ROUND_HALF_UP模式是向距离最近的一边舍入,如果两边距离相等且小数点后保留的位数是奇数,就使用ROUND_HALF_UP模式;如果两边距离相等且小数点后保留的位数是偶数,就使用ROUND_HALF_DOWN模式。
工具类
import java.math.BigDecimal;
import java.math.RoundingMode;
/**
* java精确计算工具
*
* @author wzm
* @version 1.0.0
* @date 2020/1/25 14:15
**/
public class BigDecUtils {
/**
* 提供精确加法计算的add方法(整数运算)
*/
public static String add(String val1, String val2) {
return add(val1, val2, 0, 0);
}
/**
* 提供精确加法计算的add方法(默认四舍五入)
*
* @param val1 被加数
* @param val2 加数
* @param scale 精确范围(小数点后几位)
*/
public static String add(String val1, String val2, int scale) {
return add(val1, val2, scale, BigDecimal.ROUND_HALF_UP);
}
/**
* 提供精确加法计算的add方法
*
* @param val1 被加数
* @param val2 加数
* @param scale 精确范围(小数点后几位)
* @param roundMode 精確模式
*/
public static String add(String val1, String val2, int scale, int roundMode) {
BigDecimal b1 = new BigDecimal(val1);
BigDecimal b2 = new BigDecimal(val2);
BigDecimal result = b1.add(b2);
// mode为0,则不需要精确
if (roundMode != 0) {
result = result.setScale(scale, roundMode);
}
return result.toString();
}
/**
* 提供精确减法运算的subtract方法
*
* @param val1 被减数
* @param val2 减数
* @return 两个参数的差
*/
public static String sub(String val1, String val2) {
return sub(val1, val2, 0, 0);
}
/**
* 提供精确减法运算的subtract方法(默認四捨五入)
*
* @param val1 被减数
* @param val2 减数
* @param scale 精确范围(小数点后几位)
*/
public static String sub(String val1, String val2, int scale) {
return sub(val1, val2, scale, BigDecimal.ROUND_HALF_UP);
}
/**
* 提供精确减法运算的subtract方法
*
* @param val1 被减数
* @param val2 减数
* @param scale 精确范围(小数点后几位)
* @param roundMode 精確模式
*/
public static String sub(String val1, String val2, int scale, int roundMode) {
BigDecimal b1 = new BigDecimal(val1);
BigDecimal b2 = new BigDecimal(val2);
BigDecimal result = b1.subtract(b2);
// mode为0,则不需要精确
if (roundMode != 0) {
result = result.setScale(scale, roundMode);
}
return result.toString();
}
/**
* 提供精确的除法运算方法divide
*
* @param val1 被除数
* @param val2 除数
*/
public static String div(String val1, String val2) throws IllegalAccessException {
return div(val1, val2, 0, null);
}
/**
* 提供精确的除法运算方法divide(默認四捨五入)
*
* @param val1 被除数
* @param val2 除数
* @param scale 精确范围(小数点后几位)
*/
public static String div(String val1, String val2, int scale) throws IllegalAccessException {
return div(val1, val2, scale, RoundingMode.HALF_UP);
}
/**
* 提供精确的除法运算方法divide
*
* @param val1 被除数
* @param val2 除数
* @param scale 精确范围(小数点后几位)
* @param roundingMode 精確模式
*/
public static String div(String val1, String val2, int scale, RoundingMode roundingMode)
throws IllegalAccessException {
// 如果精确范围小于0,抛出异常信息
if (scale < 0) {
throw new IllegalAccessException("精确度不能小于0");
}
BigDecimal b1 = new BigDecimal(val1);
BigDecimal b2 = new BigDecimal(val2);
// roundingMode为null,则不需要精确
if (roundingMode != null) {
return Double.toString(b1.divide(b2, scale, roundingMode).doubleValue());
} else {
return Double.toString(b1.divide(b2, 0).doubleValue());
}
}
/**
* 提供精确乘法运算的multiply方法
*
* @param val1 被乘数
* @param val2 乘数
* @return 两个参数的积
*/
public static String mul(String val1, String val2) {
return mul(val1, val2, 0, 0);
}
/**
* 提供精确乘法运算的multiply方法(默認四捨五入)
*
* @param val1 被乘数
* @param val2 乘数
* @param scale 精确范围(小数点后几位)
*/
public static String mul(String val1, String val2, int scale) {
return mul(val1, val2, scale, BigDecimal.ROUND_HALF_UP);
}
/**
* 提供精确乘法运算的multiply方法
*
* @param val1 被乘数
* @param val2 乘数
* @param scale 精确范围(小数点后几位)
* @param roundMode 舍入模式
*/
public static String mul(String val1, String val2, int scale, int roundMode) {
BigDecimal b1 = new BigDecimal(val1);
BigDecimal b2 = new BigDecimal(val2);
BigDecimal result = b1.multiply(b2);
// mode为0,则不需要精确
if (roundMode != 0) {
result = result.setScale(scale, roundMode);
}
return result.toString();
}
/**
* 比较大小 :返回较大的那个
*
* @param val1 v1
* @param val2 v2
*/
public static String max(String val1, String val2) {
BigDecimal b1 = new BigDecimal(val1);
BigDecimal b2 = new BigDecimal(val2);
return Double.toString(b1.max(b2).doubleValue());
}
/**
* 比较大小 :返回较小的那个
*
* @param val1 v1
* @param val2 v2
*/
public static String min(String val1, String val2) {
BigDecimal b1 = new BigDecimal(val1);
BigDecimal b2 = new BigDecimal(val2);
return Double.toString(b1.min(b2).doubleValue());
}
/**
* 比较大小
* if(r==0) v1等于v2
* if(r==1) v1大于v2
* if(r==-1) v1小于v2
*
* @param val1 v1
* @param val2 v2
* @param scale 精确范围
* @param roundMode 舍入模式
*/
public static int compare(String val1, String val2, int scale, int roundMode) {
BigDecimal b1 = new BigDecimal(val1);
BigDecimal b2 = new BigDecimal(val2);
BigDecimal result = b1.subtract(b2);
// mode为0,则不需要精确
if (roundMode != 0) {
result = result.setScale(scale, roundMode);
}
return result.compareTo(BigDecimal.ZERO);
}
public static void main(String[] args) throws IllegalAccessException {
System.out.println(add("10", "5"));
System.out.println(sub("10", "5"));
System.out.println(mul("10", "5"));
System.out.println(div("10", "5"));
System.out.println(compare("-10", "5", 2, BigDecimal.ROUND_HALF_UP));
System.out.println(max("10", "5"));
System.out.println(min("10", "5"));
BigDecimal v1 = new BigDecimal("-1");
BigDecimal v2 = new BigDecimal("3");
int r = v1.compareTo(v2);
System.out.println(r);
}
}
来源:https://www.cnblogs.com/jockming/p/12233088.html