第十一章《Java实战常用类》第2节:BigInteger类和BigDecimal类

时间:2023-01-02 11:58:49

​编写Java代码的过程中总是要和数字打交道。通常情况下,程序员使用Java语言所提供的int、long、float、double这些基础数据类型的变量就能存储数字。但有的时候程序中会使用一些超大的数字,这些数字已经大到无法用任何一种变量来存储的程度,例如123400000000000000000000000000000这样大的数字。为解决超大数字的处理问题,Java语言专门提供了两个用于存储和计算超大数字的类,这两个类分别叫做BigInteger和BigDecimal。从类的名称可以看出:它们分别用来表示超大整数和超大浮点数。这两个类都位于java.math这个包下,并且它们与表示数字的包装类一样,都是Number类的子类。

11.2.1创建BigInteger类对象

与Integer一样,BigInteger类在也可以通过构造方法和valueOf()方法完成对象的创建。BigInteger类的构造方法版本虽然很多,但在实际开发过程中一般都使用以字符串为参数的版本完成对象的创建。当以字符串为参数创建BigInteger类对象时,还可以指定数字的进制。下面的【例11_06】展示了创建BigInteger类对象以及把BigInteger类对象转换为各种进制的过程。​

【例11_06 创建BigInteger类对象】

Exam11_06.java​

import java.math.BigInteger;
public class Exam11_06 {
public static void main(String[] args) {
//以字符串为参数创建超大整数
BigInteger b1 = new BigInteger("123400000000000000000000000000000");
BigInteger b2 = new BigInteger("123",16);//创建十六进制的BigInteger对象
BigInteger b3 = BigInteger.valueOf(123456789L);//以long型参数创建BigInteger对象
System.out.println("b1:"+b1);
System.out.println("b2:"+b2);
System.out.println("b2的十六进制形式:"+b2.toString(16));
System.out.println("b2的八进制形式:"+b2.toString(8));
System.out.println("b3:"+b3);
}
}

【例11_06】的运行结果如图11-5所示。​

第十一章《Java实战常用类》第2节:BigInteger类和BigDecimal类

图11-5【例11_06】运行结果​

从图11-5可以看出,以字符串为参数可以创建出一个超大整数,这个整数的值远远超过了long类型数据的最大值。b2对象是以十六进制的形式创建的,但如果直接输出b2的值,则仍然以十进制的形式输出。如果想把一个整数的值以其他进制的形式输出,就必须调用toString()方法,这个方法的参数就代表整数的进制。toString()方法的参数范围是2-36,因此程序员可以把一个整数最高转换为36进制。​

11.2.2 BigInteger类对象的相关运算

BigInteger不仅仅能够表示一个超大整数,还可以完成相关的运算操作。这些计算主要包括:数学运算、比较运算和位运算。下面的表11-2展示了用于超大整数各种运算的方法。​

11-2 BigInteger类运算相关方法​

方法名称

作用

运算分类

add()

两数求和





数学运算

subtract()

两数求差

multiply()

两数求积

divide()

两数求商

remainder()

两数求模(余数)

divideAndRemainder()

两数求商和模,其返回值为数组

mod()

求非负余数

pow()

求大整数的N次幂

abs()

求绝对值

negate()

求相反数

min()

两个数求最小值

max()

两个数求最大值

gcd()

最大公约数

compareTo()

比较两个数的大小关系(返回值为-1/0/1)

比较运算


signum()

获得数字正负属性(以返回值-1/0/1标识)

and()

按位与



位运算

or()

按位或

xor()

按位异或

not()

取反

shiftLeft()

左移

shiftRight()

带符号右移

需要说明:用于实现数学运算和位运算的方法所产生的返回值也都是BigInteger类的对象。表11-2所列的方法当中,mod()方法的作用最不好理解,此处单独进行讲解。我们知道:求余数是在两个数做除法操作时产生的结果。如果做除法的被除数和除数都是正数,那么mod()方法和remainder()方法没有任何区别,都是返回余数。但是,如果除数是负数,执行mod()方法时就会抛出异常,而当被除数为负数、除数为正数时,mod()方法的运算结果是余数与除数之和。此外,divideAndRemainder()方法的返回值是一个数组,数组中下标为0元素是除法运算的商,而下标为1的元素是除法运算产生的余数。下面的【例11_07】展示了BigInteger类对象调用方法完成各种计算的过程。​

【例11_07 BigInteger类的相关运算】

Exam11_07.java​

import java.math.BigInteger;
public class Exam11_07 {
public static void main(String[] args) {
BigInteger b1 = new BigInteger("723600000000");
BigInteger b2 = new BigInteger("1800000000");
BigInteger b3 = new BigInteger("-7");
System.out.println("b1与b2之和:"+b1.add(b2));
System.out.println("b1与b2之差:"+b1.subtract(b2));
System.out.println("b1与b2之积:"+b1.multiply(b2));
System.out.println("b1与b2之商:"+b1.divide(b2));
System.out.println("b3与b2之模:"+b3.remainder(b2));
System.out.println("b3与b2之非负余数:"+b3.mod(b2));//①
System.out.println("b1的5次方:"+b1.pow(5));
System.out.println("b1与b2按位与:"+b1.and(b2));
System.out.println("b1与b2按位或:"+b1.or(b2));
System.out.println("b1与b2按位异或:"+b1.xor(b2));
System.out.println("b1取反:"+b1.not());
System.out.println("b1左移2位:"+b1.shiftLeft(2));
System.out.println("b1右移2位:"+b1.shiftRight(2));
}
}

【例11_07】的运行结果如图11-6所示。​

第十一章《Java实战常用类》第2节:BigInteger类和BigDecimal类

图11-6【例11_07】运行结果​

从图11-6可以看出:b1的5次方是一个很大的数字,但使用BigInteger类对象仍然能够正确的表示出这个数字。但BigInteger类对象并不是可以表示任意大的整数,它所能表示的最大值受到计算机内存大小和计算能力的制约,因此在不同计算机中,BigInteger类对象所能表示的最大整数也各不相同。​

11.2.3 BigInteger类对象转换为基础类型数据

BigInteger类对象也可以转换为基础类型的数据,与表示数字的包装类一样,BigInteger类对象转换为基础类型数据的方法是也xxxValue()。需要注意,当进行转换的过程中如果出现溢出时程序并不会抛出异常,这导致程序员很难发现转换过程中出现了错误,以至于很有可能用一个转换所得到的错误结果进行后续的运算,从而导致最终的计算结果也是错误的。为了能够在转换出错时能够及时提示程序员,BigInteger类定义了一系列xxxValueExact()方法,这些方法不仅能够完成正常的转换操作,还能在转换出现错误时抛出异常。下面的【例11_08】展示了把BigInteger类对象转换为基础类型数据的操作。​

【例11_08 BigInteger类对象转换为基础类型数据】

Exam11_08.java​

import java.math.BigInteger;
public class Exam11_08 {
public static void main(String[] args) {
BigInteger b1 = new BigInteger("12345678900");
BigInteger b2 = b1.pow(1000);
System.out.println("用byteValue()方法把b1被转为byte:"+b1.byteValue());
System.out.println("用intValue方法把b1转为int:"+b1.intValue());
System.out.println("用longValue()方法把b1转为long:"+b1.longValue());
System.out.println("用doubleValue()方法把b2转为double:"+b2.doubleValue());
System.out.println("用longValueExact()方法把b1转为long"+b1.longValueExact());
System.out.println("用intValueExact()方法把b1转为int"+b1.intValueExact());
}
}

【例11_08】的运行结果如图11-7所示。​

第十一章《Java实战常用类》第2节:BigInteger类和BigDecimal类

图11-7【例11_08】运行结果​

从图11-7可以看出:当使用xxxValue()方法把BigInteger类对象转为整数时,如果出现溢出直接返回一个错误的计算结果,而把BigInteger类对象转为浮点数时,如果出现溢出则显示“Infinity”,Infinity意为“无穷”,以这个单词来表示数字过大。当使用longValueExact()方法把BigInteger类对象转换为long型数据时,因没有产生溢出所以输出了正确的转换结果,而使用intValueExact()方法把BigInteger对象转换为int型数据时,因溢出而导致程序抛出异常。异常的出现能够及时阻止后续代码的执行,避免了错误数据参与计算。​

11.2.4 使用BigDecimal类处理浮点数计算精度问题

BigInteger和BigDecimal这两个类都位于java.math这个包下,它们一个表示超大整数,一个表示超大浮点数。这两个类有很多相同之处,例如它们都可以进行加减乘除、求绝对值,求相反数这些常规的数学运算,它们都可以完成相互比较的操作等等。此外,这两个类还有一些不相同之处,主要区别是:BigDecimal没有位运算功能和数制转换功能。这是因为位运算和数制转换的操作都是针对整数而言的,而BigDecimal表示的是超大浮点数,所以它自然没有这些功能。BigInteger和BigDecimal这两个类的相同功能在此不赘述,此处重点讲解使用BigDecimal类对象处理浮点数计算不精确问题。​

实际开发过程中经常会遇到浮点数计算不精确的问题,例如:​

double r = 5;
double pi = 3.14;
System.out.println(r*pi);

以上代码的运行结果本应该是“15.7”,但实际运行结果却是“15.700000000000001”,这就是一个典型的浮点数计算不精确的问题。实际上,使用浮点数进行加、减、乘、除这样的数学运算都会产生计算不精确的情况。使用BigDecimal类对象进行数学运算就能得到精确的计算结果,但必须强调:如果想要得到精确的计算结果,必须使用字符串为参数来创建BigDecimal类对象。下面的【例11_09】展示了如何使用BigDecimal类解决浮点数计算不精确的问题。​

【例11_09 使用BigDecimal类解决浮点数计算不精确问题】

Exam11_09.java​

import java.math.BigDecimal;
public class Exam11_09 {
public static void main(String[] args) {
double r = 5;
double pi = 3.14;
BigDecimal br1 = new BigDecimal(5);
BigDecimal bpi1 = new BigDecimal(3.14);
BigDecimal br2 = new BigDecimal("5");
BigDecimal bpi2 = new BigDecimal("3.14");
System.out.println("语句①的运算结果:"+r*pi);//①
System.out.println("语句②的运算结果:"+br1.multiply(bpi1));//②
System.out.println("语句③的运算结果:"+br2.multiply(bpi2));//③
}
}

【例11_09】中,语句①直接使用浮点数进行计算,语句②使用以浮点数为参数创建的BigDecimal类对象进行计算,语句③使用以字符串为参数创建的BigDecimal类对象进行计算。【例11_09】的运行结果如图11-8所示。​

第十一章《Java实战常用类》第2节:BigInteger类和BigDecimal类

图11-8【例11_09】运行结果​

从图11-8可以明显的看出:只有使用以字符串为参数创建的BigDecimal类对象进行计算才能获得正确的结果。但语句③的运算结果虽然正确,但最后一位小数是0,末尾小数的0并没有实际的数学意义,程序员可以调用计算结果的doubleValue()方法去除末位小数的0。​

11.2.5 使用BigDecimal类处理浮点数保留位数问题

BigDecimal类虽然能够解决浮点数计算不精确的问题,但它自身也有一个明显的缺陷,那就是在完成浮点数的计算时不能对无限小数进行舍弃,以至于小数的位数超出了表示范围而导致程序出现异常,因此,在使用BigDecimal类对象完成除法运算时,最好为运算结果指定保留小数的位数。下面的【例11_10】展示了使用BigDecimal类对象完成除法运算时为运算结果指定小数位数的操作过程。​

【例11_10对运算结果设置小数位数】

Exam11_10.java​

import java.math.BigDecimal;
import java.math.RoundingMode;
public class Exam11_10 {
public static void main(String[] args) {
BigDecimal b1 = new BigDecimal("5");
BigDecimal b2 = new BigDecimal("3.14");
System.out.println("直接使用浮点数计算5/3.14:"+5/3.14);//①
System.out.print("使用BigDecimal类对象计算5/3.14(保留3位小数):");
System.out.println(b1.divide(b2,3, RoundingMode.HALF_UP));//②
System.out.println("使用BigDecimal类对象计算5/3.14(不设置保留小数位数):");
System.out.println(b1.divide(b2));//③
}
}

【例11_10】中,语句①直接用浮点数计算了5/3.14,语句②使用BigDecimal类对象计算5/3.14,并且设置运算结果保留3位小数。这种设置需要给divide()方法传递3个参数,第一个参数是除数,第二个参数是保留小数的位数,第三个参数是保留小数位数的方式。小数位数的保留方式以枚举的形式表示,RoundingMode就是一个枚举,HALF_UP是它的一个枚举值,表示四舍五入的保留方式。语句③也做了与语句②相同的除法操作,但没有设置任何保留小数位数的方式。【例11_10】的运行结果如图11-9所示。​

第十一章《Java实战常用类》第2节:BigInteger类和BigDecimal类

图11-9 【例11_10】运行结果​

从图11-9可以看出:直接使用浮点数做除法运算,虽然运行结果是一个无限小数,但虚拟机会自动保留一定的小数位数。而如果使用BigDecimal类对象做除法运算,如果没有设置保留小数的位数,那么一旦运行结果是一个无限小数,将会导致程序出现异常,导致异常的原因是小数超出了BigDecimal类的表示范围。由此可见,为避免异常,最好在做除法操作时为运算结果设置保留小数的位数。​

【例11_10】中语句②为两个数字相除运算结果设置了小数位数。有时候程序员需要在不做除法的情况下直接对一个浮点数设置小数位数,这个操作可以调用BigDecimal类的setScale()方法完成。程序员在调用setScale()方法时,需要给方法传递两个参数,第一个参数是保留小数的位数,第二个参数是小数位数的保留方式。实际上,程序员不给setScale()方法设置第二个参数也不会出现语法错误,但虚拟机在实际运行这个方法时会出现异常。下面的【例11_11】展示了setScale()方法的执行效果。​

【例11_11设置浮点数的小数位数】

Exam11_11.java​

import java.math.BigDecimal;
import java.math.RoundingMode;
public class Exam11_11 {
public static void main(String[] args) {
BigDecimal b = new BigDecimal("12.345");
System.out.println("原数字:"+b);
System.out.println("设置保留1位小数:"+b.setScale(1, RoundingMode.HALF_UP));
System.out.println("设置保留2位小数:"+b.setScale(2, RoundingMode.HALF_UP));
System.out.println("未指定小数位数的保留方式:");
System.out.println(b.setScale(1));
}
}

【例11_11】的运行结果如图11-10所示。​

第十一章《Java实战常用类》第2节:BigInteger类和BigDecimal类

图11-10【例11_11】运行结果​

从图11-10可以看出:调用setScale()方法时,如果设置了小数位数的保留方式,setScale()方法能够按照规定的方式正确的保留浮点数的小数位数,而如果不设置小数位数的保留方式,虽然语法没有错误,但运行时会出现异常。

本文字版教程还配有更详细的视频讲解,小伙伴们可以点击这里观看。