java BigDecimal使用详细介绍

简介: java BigDecimal使用详细介绍

一、BigDecimal引入

开发中经常遇到小数计算,比如

System.out.println(1.01+2.31);

计算结果并不是3.32而是3.3200000000000003,这是因为不论是float 还是double都是浮点数,而计算机是二进制的,浮点数会失去一定的精确度。有没有不失精度的办法呢?这里就要用到BigDecimal了。

Java在java.math包中提供的API类BigDecimal,用来对超过16位有效位的数进行精确的运算。双精度浮点型变量double可以处理16位有效数。在实际应用中,需要对更大或者更小的数进行运算和处理。float和double只能用来做科学计算或者是工程计算,在商业计算中要用java.math.BigDecimal。BigDecimal所创建的是对象,我们不能使用传统的+、-、*、/等算术运算符直接对其对象进行数学运算,而必须调用其相对应的方法。

方法中的参数也必须是BigDecimal的对象。

构造器是类的特殊方法,专门用来创建对象,特别是带有参数的对象。

 

其实在java.math包(注意和java.lang包下的Math类区分)下有三个class和一个enums

class

  • BigDecimal
  • BigInteger
  • MathContext

enums

  • RoundingMode

二、RoundingMode

RoundingMode是一个枚举类,列举了8中对数据舍入(近似)模式

  1. UP

public static final RoundingMode UP

  1. 远离零方向舍入的舍入模式。始终对非零舍弃部分前面的数字加 1。注意,此舍入模式始终不会减少计算值的绝对值。示例:
输入数字 使用 UP 舍入模式
将输入数字舍入为一位数
5.5 6
2.5 3
1.6 2
1.1 2
1.0 1
-1.0 -1
-1.1 -2
-1.6 -2
-2.5 -3
-5.5 -6
  1. 显示详细信息
  2. DOWN

public static final RoundingMode DOWN

  1. 向零方向舍入的舍入模式。从不对舍弃部分前面的数字加 1(即截尾)。注意,此舍入模式始终不会增加计算值的绝对值。
    示例:
输入数字 使用 DOWN 舍入模式
将输入数字舍入为一位数
5.5 5
2.5 2
1.6 1
1.1 1
1.0 1
-1.0 -1
-1.1 -1
-1.6 -1
-2.5 -2
-5.5 -5
  1. 显示详细信息
  2. CEILING

public static final RoundingMode CEILING

  1. 向正无限大方向舍入的舍入模式。如果结果为正,则舍入行为类似于  RoundingMode.UP;如果结果为负,则舍入行为类似于  RoundingMode.DOWN。注意,此舍入模式始终不会减少计算值。
    示例:
输入数字 使用 CEILING 舍入模式
将输入数字舍入为一位数
5.5 6
2.5 3
1.6 2
1.1 2
1.0 1
-1.0 -1
-1.1 -1
-1.6 -1
-2.5 -2
-5.5 -5
  1. 显示详细信息
  2. FLOOR

public static final RoundingMode FLOOR

  1. 向负无限大方向舍入的舍入模式。如果结果为正,则舍入行为类似于  RoundingMode.DOWN;如果结果为负,则舍入行为类似于 RoundingMode.UP。注意,此舍入模式始终不会增加计算值。
    示例:
输入数字 使用 FLOOR 舍入模式
将输入数字舍入为一位数
5.5 5
2.5 2
1.6 1
1.1 1
1.0 1
-1.0 -1
-1.1 -2
-1.6 -2
-2.5 -3
-5.5 -6
  1. 显示详细信息
  2. HALF_UP

public static final RoundingMode HALF_UP

  1. 向最接近数字方向舍入的舍入模式,如果与两个相邻数字的距离相等,则向上舍入。如果被舍弃部分 >= 0.5,则舍入行为同  RoundingMode.UP;否则舍入行为同 RoundingMode.DOWN。注意,此舍入模式就是通常学校里讲的四舍五入。
    示例:
输入数字 使用 HALF_UP 舍入模式
将输入数字舍入为一位数
5.5 6
2.5 3
1.6 2
1.1 1
1.0 1
-1.0 -1
-1.1 -1
-1.6 -2
-2.5 -3
-5.5 -6
  1. 显示详细信息
  2. HALF_DOWN

public static final RoundingMode HALF_DOWN

  1. 向最接近数字方向舍入的舍入模式,如果与两个相邻数字的距离相等,则向下舍入。如果被舍弃部分 > 0.5,则舍入行为同  RoundingMode.UP;否则舍入行为同 RoundingMode.DOWN。
    示例:
输入数字 使用 HALF_DOWN 舍入模式
将输入数字舍入为一位数
5.5 5
2.5 2
1.6 2
1.1 1
1.0 1
-1.0 -1
-1.1 -1
-1.6 -2
-2.5 -2
-5.5 -5
  1. 显示详细信息
  2. HALF_EVEN

public static final RoundingMode HALF_EVEN

  1. 向最接近数字方向舍入的舍入模式,如果与两个相邻数字的距离相等,则向相邻的偶数舍入。如果舍弃部分左边的数字为奇数,则舍入行为同 RoundingMode.HALF_UP;如果为偶数,则舍入行为同 RoundingMode.HALF_DOWN。注意,在重复进行一系列计算时,此舍入模式可以在统计上将累加错误减到最小。此舍入模式也称为“银行家舍入法”,主要在美国使用。此舍入模式类似于 Java 中对 float 和 double 算法使用的舍入策略。
    示例:
输入数字 使用 HALF_EVEN 舍入模式
将输入数字舍入为一位数
5.5 6
2.5 2
1.6 2
1.1 1
1.0 1
-1.0 -1
-1.1 -1
-1.6 -2
-2.5 -2
-5.5 -6
  1. 显示详细信息
  2. UNNECESSARY

public static final RoundingMode UNNECESSARY

  1. 用于断言请求的操作具有精确结果的舍入模式,因此不需要舍入。如果对生成精确结果的操作指定此舍入模式,则抛出  ArithmeticException。
    示例:
输入数字 使用 UNNECESSARY 舍入模式
将输入数字舍入为一位数
5.5 抛出 ArithmeticException
2.5 抛出 ArithmeticException
1.6 抛出 ArithmeticException
1.1 抛出 ArithmeticException
1.0 1
-1.0 -1
-1.1 抛出 ArithmeticException
-1.6 抛出 ArithmeticException
-2.5 抛出 ArithmeticException
-5.5 抛出 ArithmeticException
  1. 显示详细信息

RoundingMode拥有静态的valueOf方法,对BigDecimal中的常量,进行映射转换

返回RoundingMode对象

public static RoundingMode valueOf(int rm) {
        switch(rm) {
 
        case BigDecimal.ROUND_UP:
            return UP;
 
        case BigDecimal.ROUND_DOWN:
            return DOWN;
 
        case BigDecimal.ROUND_CEILING:
            return CEILING;
 
        case BigDecimal.ROUND_FLOOR:
            return FLOOR;
 
        case BigDecimal.ROUND_HALF_UP:
            return HALF_UP;
 
        case BigDecimal.ROUND_HALF_DOWN:
            return HALF_DOWN;
 
        case BigDecimal.ROUND_HALF_EVEN:
            return HALF_EVEN;
 
        case BigDecimal.ROUND_UNNECESSARY:
            return UNNECESSARY;
 
        default:
            throw new IllegalArgumentException("argument out of range");
        }
    }

这个enum 是打算用来替代 BigDecimal中的舍入模式常量

(BigDecimal.ROUND_UP、BigDecimal.ROUND_DOWN 等)

所以后续写代码使用这个枚举

三、MathContext

1、引入

RoundingMode 是舍入模式的抽象描述,仅仅描述了舍入的规则,但是运算中还有一些其他的规则,比如保留几位有效数字?

MathContext则是针对于计算的更进一步抽象

是封装上下文设置的不可变对象,它描述数字运算符的某些规则

2、属性

他拥有两个属性

precision:某个操作使用的数字个数;结果舍入到此精度

roundingMode:一个 RoundingMode 对象,该对象指定舍入使用的算法

针对于这两个属性,也提供了两个方法进行获取

3、构造方法

MathContext(int setPrecision, RoundingMode setRoundingMode)           构造一个新的 MathContext,它具有指定的精度和舍入模式

MathContext(int setPrecision)           构造一个新的 MathContext,它具有指定的精度和 HALF_UP 舍入模式 调用上一个构造方法

MathContext(String val)           根据字符串构造一个新的 MathContext 注意:该字符串的格式必须与 toString() 方法生成的字符串的格式相同不是可以随便写的!!

4、其他方法

equals

equals方法已经被重写,对比的是两个属性的数值是否相等

    public boolean equals(Object x){
        MathContext mc;
        if (!(x instanceof MathContext))
            return false;
        mc = (MathContext) x;
        return mc.precision == this.precision
            && mc.roundingMode == this.roundingMode; // no need for .equals()
    }

toString

注意这个格式可以用于构造对象

public java.lang.String toString() {
        return "precision=" +           precision + " " +
               "roundingMode=" +        roundingMode.toString();
    }

5、常量

MathContext 就是针对于运算中的一些规则进行描述的类型

对于一些规定,已经内置了几个静态对象供我们使用

static MathContext DECIMAL128 其精度设置与 IEEE 754R Decimal128 格式(即 34 个数字)匹配 舍入模式为 HALF_EVEN 这是 IEEE 754R 的默认舍入模式

static MathContext DECIMAL32 其精度设置与 IEEE 754R Decimal32 格式(即 7 个数字)匹配 舍入模式为 HALF_EVEN 这是 IEEE 754R 的默认舍入模式

static MathContext DECIMAL64 其精度设置与 IEEE 754R Decimal64 格式(即 16 个数字)匹配 舍入模式为 HALF_EVEN 这是 IEEE 754R 的默认舍入模式

static MathContext UNLIMITED 其设置具有无限精度算法所需值的 MathContext 对象

显示详细信息

MathContext 他在使用上也可以理解为"常量" 一样的存在  

四、BigDecimal

1、创建

创建BigDecimal对象主要有两种。

BigDecimal b1 = new BigDecimal("1.34");//1.34

BigDecimal b2 = BigDecimal.valueOf(1.34);//1.34

其中b1也可以写成new BigDecimal(Double.toString(1.34)),可以直接new BigDecimal(1.34)吗,也是可以的,只是会出现上述的精度问题。

BigDecimal one1 = new BigDecimal(1.34);//1.3400000000000000799360577730112709105014801025390625

BigDecimal two1 = new BigDecimal("1.34");//1.34

除了这两种外,特殊的像0、1、10可以这样写。

BigDecimal zero = BigDecimal.ZERO;

BigDecimal one = BigDecimal.ONE;

BigDecimal ten = BigDecimal.TEN;

比较一下BigDecimal.ZERO、new BigDecimal("0")、BigDecimal.valueOf(0)这三者,equals都是true,==的话 new BigDecimal("0")就不用看了都new了,而BigDecimal.ZERO == BigDecimal.ZERO为true。查看一下源代码可得。

 

 private static final BigDecimal zeroThroughTen[] = {
        new BigDecimal(BigInteger.ZERO,       0,  0, 1),
        new BigDecimal(BigInteger.ONE,        1,  0, 1),
        new BigDecimal(BigInteger.valueOf(2), 2,  0, 1),
        new BigDecimal(BigInteger.valueOf(3), 3,  0, 1),
        new BigDecimal(BigInteger.valueOf(4), 4,  0, 1),
        new BigDecimal(BigInteger.valueOf(5), 5,  0, 1),
        new BigDecimal(BigInteger.valueOf(6), 6,  0, 1),
        new BigDecimal(BigInteger.valueOf(7), 7,  0, 1),
        new BigDecimal(BigInteger.valueOf(8), 8,  0, 1),
        new BigDecimal(BigInteger.valueOf(9), 9,  0, 1),
        new BigDecimal(BigInteger.TEN,        10, 0, 2),
    };

发现10以内的对象都是同一个,所以为true。

2、加减乘除运算方法

public BigDecimal add(BigDecimal value);//加法
public BigDecimal subtract(BigDecimal value);//减法 
public BigDecimal multiply(BigDecimal value);//乘法
public BigDecimal divide(BigDecimal value);//除法

BigDecimal的运算都没有对原值进行操作,而是返回一个新的BigDecimal对象

BigDecimal b1 =new BigDecimal("1.34");
System.out.println("b1: " + b1);
BigDecimal b2 =new BigDecimal("2.34");
b1.add(b2);
System.out.println("b1: " + b1);//b1并没有变

对BigDecimal做加、减、乘时,精度不会丢失,但是做除法时,存在无法除尽的情况,这时,就必须指定精度以及如何进行截断:

BigDecimal d1 = new BigDecimal("123.456");
BigDecimal d2 = new BigDecimal("23.456789");
BigDecimal d3 = d1.divide(d2, 10, RoundingMode.HALF_UP); // 保留10位小数并四舍五入
BigDecimal d4 = d1.divide(d2); // 报错:ArithmeticException,因为除不尽

还可以对BigDecimal做除法的同时求余数:

public class Main {
    public static void main(String[] args) {
        BigDecimal n = new BigDecimal("12.345");
        BigDecimal m = new BigDecimal("0.12");
        BigDecimal[] dr = n.divideAndRemainder(m);
        System.out.println(dr[0]); // 102
        System.out.println(dr[1]); // 0.105
    }
}

调用divideAndRemainder()方法时,返回的数组包含两个BigDecimal,分别是商和余数,其中商总是整数,余数不会大于除数。我们可以利用这个方法判断两个BigDecimal是否是整数倍数:

BigDecimal n = new BigDecimal("12.75");
BigDecimal m = new BigDecimal("0.15");
BigDecimal[] dr = n.divideAndRemainder(m);
if (dr[1].signum() == 0) {
    // n是m的整数倍
}

3、scale()

BigDecimal用scale()表示小数位数,例如:

BigDecimal d1 = new BigDecimal("123.45");
BigDecimal d2 = new BigDecimal("123.4500");
BigDecimal d3 = new BigDecimal("1234500");
System.out.println(d1.scale()); // 2,两位小数
System.out.println(d2.scale()); // 4
System.out.println(d3.scale()); // 0

通过BigDecimal的stripTrailingZeros()方法,可以将一个BigDecimal格式化为一个相等的,但去掉了末尾0的BigDecimal:

BigDecimal d1 = new BigDecimal("123.4500");
BigDecimal d2 = d1.stripTrailingZeros();
System.out.println(d1.scale()); // 4
System.out.println(d2.scale()); // 2,因为去掉了00
 
BigDecimal d3 = new BigDecimal("1234500");
BigDecimal d4 = d3.stripTrailingZeros();
System.out.println(d3.scale()); // 0
System.out.println(d4.scale()); // -2

如果一个BigDecimal的scale()返回负数,例如,-2,表示这个数是个整数,并且末尾有2个0。

可以对一个BigDecimal设置它的scale,如果精度比原始值低,那么按照指定的方法进行四舍五入或者直接截断:

public class Main {
    public static void main(String[] args) {
        BigDecimal d1 = new BigDecimal("123.456789");
        BigDecimal d2 = d1.setScale(4, RoundingMode.HALF_UP); // 四舍五入,123.4568
        BigDecimal d3 = d1.setScale(4, RoundingMode.DOWN); // 直接截断,123.4567
        System.out.println(d2);
        System.out.println(d3);
    }
}

BigDecimal.setScale(int newScale, int roundingMode);的使用

newScale: 保留newScale位小数

roundingMode: 舍去规则(0 <= roundingMode <= 7)分别有8个常量(就是MathContext的八个枚举)

ROUND_UP(0):第newScale位小数进1,后面舍去

   BigDecimal b1 = new BigDecimal(3.1415926);

   BigDecimal b2 = b1.setScale(2, BigDecimal.ROUND_UP);

   System.out.println(b2);// 3.15

ROUND_DOWN(1):第newScale位后面的,直接舍去

   BigDecimal b1 = new BigDecimal(3.1415926);

   BigDecimal b2 = b1.setScale(2, BigDecimal.ROUND_DOWN);

   System.out.println(b2);// 3.14

ROUND_CEILING(2):如果是正数,同ROUND_UP,如果是负数,同ROUND_DOWN。

ROUND_CEILING 其实就是向上进1

   BigDecimal b1 = new BigDecimal(3.1415926);

   BigDecimal b2 = b1.setScale(2, BigDecimal.ROUND_CEILING);

   System.out.println(b2);// 3.15

       

   b1 = new BigDecimal(-3.1415926);

   b2 = b1.setScale(2, BigDecimal.ROUND_CEILING);

   System.out.println(b2);// -3.14

ROUND_FLOOR(3):与ROUND_CEILING相反

   BigDecimal b1 = new BigDecimal(3.1415926);

   BigDecimal b2 = b1.setScale(2, BigDecimal.ROUND_FLOOR);

   System.out.println(b2);// 3.14

       

   b1 = new BigDecimal(-3.1415926);

   b2 = b1.setScale(2, BigDecimal.ROUND_FLOOR);

   System.out.println(b2);// -3.15

ROUND_HALF_UP(4):四舍五入:小学学过的那种

   BigDecimal b1 = new BigDecimal(3.1415926);

   BigDecimal b2 = b1.setScale(2, BigDecimal.ROUND_HALF_UP);

   System.out.println(b2);// 3.14

       

   b1 = new BigDecimal(3.1455926);

   b2 = b1.setScale(2, BigDecimal.ROUND_HALF_UP);

   System.out.println(b2);// 3.15

ROUND_HALF_DOWN(5):另一种四舍五入:舍去部分 > 0.5时,表现为ROUND_UP,否则表现为ROUND_DOWN

   BigDecimal b1 = new BigDecimal(3.55);

   System.out.println(b1);// 3.54999999999999982236431605997495353221893310546875

   BigDecimal b2 = b1.setScale(1, BigDecimal.ROUND_HALF_DOWN);

   System.out.println(b2);// 3.5

       

   b1 = new BigDecimal(3.551);

   System.out.println(b1);// 3.5510000000000001563194018672220408916473388671875

   b2 = b1.setScale(1, BigDecimal.ROUND_HALF_DOWN);

   System.out.println(b2);// 3.6

ROUND_HALF_EVEN(6):舍去部分左边为奇数:表现为ROUND_HALF_UP;偶数:表现为ROUND_HALF_DOWN

   BigDecimal b1 = new BigDecimal(3.55);

   System.out.println(b1);// 3.54999999999999982236431605997495353221893310546875

   BigDecimal b2 = b1.setScale(1, BigDecimal.ROUND_HALF_EVEN);// 舍去部分从4开始,4是偶数

   System.out.println(b2);// 3.5

       

   b1 = new BigDecimal(3.551);

   System.out.println(b1);// 3.5510000000000001563194018672220408916473388671875

   b2 = b1.setScale(1, BigDecimal.ROUND_HALF_EVEN);

   System.out.println(b2);// 3.6

ROUND_UNNECESSARY(7):

       Rounding mode to assert that the requested operation has an exactresult, hence no rounding is necessary. If this rounding mode isspecified on an operation that yields an inexact result, an ArithmeticException is thrown.

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

 

4、BigDecimal比较

在比较两个BigDecimal的值是否相等时,要特别注意,使用equals()方法不但要求两个BigDecimal的值相等,还要求它们的scale()相等:

BigDecimal d1 = new BigDecimal("123.456");
BigDecimal d2 = new BigDecimal("123.45600");
System.out.println(d1.equals(d2)); // false,因为scale不同
System.out.println(d1.equals(d2.stripTrailingZeros())); // true,因为d2去除尾部0后scale变为2
System.out.println(d1.compareTo(d2)); // 0

必须使用compareTo()方法来比较,它根据两个值的大小分别返回负数、正数和0,分别表示小于、大于和等于

BigDecimal one = BigDecimal.valueOf(1);
BigDecimal two = BigDecimal.valueOf(2);
BigDecimal three = one.add(two);
int i1 = one.compareTo(two);//-1
int i2 = two.compareTo(two);//0
int i3 = three.compareTo(two);//1

总是使用compareTo()比较两个BigDecimal的值,不要使用equals()!

如果查看BigDecimal的源码,可以发现,实际上一个BigDecimal是通过一个BigInteger和一个scale来表示的,即BigInteger表示一个完整的整数,而scale表示小数位数:

public class BigDecimal extends Number implements Comparable<BigDecimal> {

   private final BigInteger intVal;

   private final int scale;

}

5、其他

BigDecimal.abs();返回BigDecimal:绝对值

BigDecimal.negate();返回BigDecimal:相反数

BigDecimal也是从Number继承的,也是不可变对象

五、BigInteger

在Java中,由CPU原生提供的整型最大范围是64位long型整数,如果我们使用的整数范围超过了long型怎么办?

java.math.BigInteger就是用来表示任意大小的整数。BigInteger内部用一个int[]数组来模拟一个非常大的整数:

对BigInteger做运算的时候,只能使用实例方法,例如,加法运算:

BigInteger i1 = new BigInteger("1234567890");
BigInteger i2 = new BigInteger("12345678901234567890");
BigInteger sum = i1.add(i2); // 12345678902469135780

和long型整数运算比,BigInteger不会有范围限制,但缺点是速度比较慢。

也可以把BigInteger转换成long型:

BigInteger i = new BigInteger("123456789000");
System.out.println(i.longValue()); // 123456789000
System.out.println(i.multiply(i).longValueExact()); // java.lang.ArithmeticException: BigInteger out of long range

使用longValueExact()方法时,如果超出了long型的范围,会抛出ArithmeticException。

BigInteger和Integer、Long一样,也是不可变类,并且也继承自Number类。因为Number定义了转换为基本类型的几个方法:

  • 转换为byte:byteValue()
  • 转换为short:shortValue()
  • 转换为int:intValue()
  • 转换为long:longValue()
  • 转换为float:floatValue()
  • 转换为double:doubleValue()

因此,通过上述方法,可以把BigInteger转换成基本类型。如果BigInteger表示的范围超过了基本类型的范围,转换时将丢失高位信息,即结果不一定是准确的。如果需要准确地转换成基本类型,可以使用intValueExact()、longValueExact()等方法,在转换时如果超出范围,将直接抛出ArithmeticException异常。

如果BigInteger的值甚至超过了float的最大范围(3.4x1038),那么返回的float是什么呢?

 

  public static void main(String[] args)  {
        BigInteger n = new BigInteger("999999").pow(99);
        float f = n.floatValue();
        System.out.println(f);//Infinity
    }
相关文章
|
4天前
|
Java
深入了解Java中的BigDecimal类及其方法
深入了解Java中的BigDecimal类及其方法
9 1
|
8天前
|
安全 Java
java中BigDecimal详解
java中BigDecimal详解
|
2月前
|
存储 Java
Java中BigDecimal怎样取反
在上述示例中,`number.negate()`会将BigDecimal对象 `number`的值取反,并将结果存储在新的BigDecimal对象 `negated`中。
30 0
|
2月前
|
Java 数据库
Java中BigDecimal比较大小的方法BigDecimal转换为Integer
Java中BigDecimal比较大小的方法BigDecimal转换为Integer
11 0
|
2月前
|
Java
【Java】如果一个集合中类型是String如何使用拉姆达表达式 进行Bigdecimal类型计算?
【Java】如果一个集合中类型是String如何使用拉姆达表达式 进行Bigdecimal类型计算?
44 0
|
2月前
|
存储 Java
35、Java 中的 Math 类、Random 随机数、UUID、格式化字符串或数字、字符串和数字的相互转换、高精度计算、BigDecimal、计算机中的浮点数都是近似值
35、Java 中的 Math 类、Random 随机数、UUID、格式化字符串或数字、字符串和数字的相互转换、高精度计算、BigDecimal、计算机中的浮点数都是近似值
56 0
|
2月前
|
存储 Java
你知道Java中的BigInteger类和BigDecimal类吗?
你知道Java中的BigInteger类和BigDecimal类吗?
|
2月前
|
存储 Java API
Java BigDecimal知多少?
Java BigDecimal知多少?
45 0
|
10月前
|
存储 Java API
Java之API详解之BigDecimal类的详细解析
Java之API详解之BigDecimal类的详细解析
63 0
|
Java API
Java中BigDecimal详解及应用
BigDecimal用来对超过16位有效位的数进行精确的运算。双精度浮点型变量double可以处理16位有效数,但在实际应用中,可能需要对更大或者更小的数进行运算和处理。一般情况下,对于那些不需要准确计算精度的数字,我们可以直接使用Float和Double处理,但是Double.valueOf(String) 和Float.valueOf(String)会丢失精度。所以在开发中,如果你所做的业务跟财务相关需要精确计算的结果,那必须使用BigDecimal类来操作啦!
328 2
Java中BigDecimal详解及应用