Java精确计算
简介
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); } }