一、BigInteger类
1.定义
BigInteger可以表示非常大的数,虽然也有上限,但是这个上限可以看作是无穷大的。
2.方法
(1)构造方法
① public BigInteger(int num, Random r)
public class Demo {
public static void main(String[] args) {
//1.public BigInteger(int num, Random r) 获取一个随机的大整数
Random r = new Random();
BigInteger bd1 = new BigInteger(4, r);//获取[0,2^4)中的随机整数
System.out.println(bd1);
}
}
② public BigInteger(String val)
public class Demo {
public static void main(String[] args) {
//2.public BigInteger(String val) 获取一个指定的大整数
BigInteger bd2=new BigInteger("99999999999999999999999999");
System.out.println(bd2);//99999999999999999999999999
}
}
注:字符串必须是整数,否则会报错
③ public BigInteger(String val, int radix)
public class Demo {
public static void main(String[] args) {
//3.public BigInteger(String val, int radix) 获取指定进制的大整数
BigInteger bd4=new BigInteger("100",2);
System.out.println(bd4);//4(二进制的100转换成十进制就是4)
}
}
注:字符串中的数字必须要和进制相吻合(比如二进制,只有0和1,不能有其他数字)
(2)静态方法
public class Demo {
public static void main(String[] args) {
BigInteger bd1 = BigInteger.valueOf(100);
System.out.println(bd1);//100
}
}
注:
① 能表示范围较小,只能在long的取值范围之内,如果超出long的范围就不行了
通过源码可以发现,valueof 方法的形参就是 long类型的。
这里因为已经超出了Int的最大表示范围,所以数字末尾要加上L。
long 类型的最大值输出正常,+1之后就会报错
**************************************************************************************************************
② 在内部对常用的数字(-16 ~ 16)进行了优化,提前把这些常用数字创建号BigInteger的对象,如果多次获取不会重新创建新的对象。
public class Demo {
public static void main(String[] args) {
BigInteger bd1 = BigInteger.valueOf(16);
BigInteger bd2 = BigInteger.valueOf(16);
System.out.println(bd1 == bd2);//true
BigInteger bd3 = BigInteger.valueOf(17);
BigInteger bd4 = BigInteger.valueOf(17);
System.out.println(bd3 == bd4);//false
}
}
bd1和bd2都是常用数字16,指向同一个 BigInteger对象,所以地址值相同,结果为true
而bd3和bd4是17,多次获取会创建新的BigInteger对象,所以地址值不同,结果为false
Question:为什么是16,怎么实现的呢?
在valueof 方法内部,会先进行判断。
判断值是否为0,是小于 MAX_CONSTANT,还是大于 -MAX_CONSTANT
在 BigInteger 类的成员变量中,可以得知 MAX_CONSTANT 的值为16
此外,成员变量中也创建了两个数组 posConst 和 negConst
在BigInteger 类的静态代码块中,可知:
posConst 用于存放 1 ~ 16 的正整数 BigInteger 对象
negConst 用于存放 -1 ~ -16 的负整数 BigInteger 对象
那么0呢?
BigInteger 类还有一个静态变量叫做 ZERO ,专门存放 0 的BigInteger 对象
这时再回头看valueof 方法,就会很清晰了
传入的形参为0,就返回ZERO
为正数且<=16,就返回 posConst 数组中对应的 BigInteger 对象
为负数且>=-16,就返回 negConst 数组中对应的 BigInteger 对象
否则,再创建新的 BigInteger 对象
(3)成员方法
① 加减乘除、余
public class Demo {
public static void main(String[] args) {
//创建两个BigInteger对象
BigInteger bd1=BigInteger.valueOf(10);
BigInteger bd2=BigInteger.valueOf(5);
//1.add 加法
BigInteger bd3=bd1.add(bd2);
System.out.println(bd3);//15
//2.subtract 减法
BigInteger bd4=bd1.subtract(bd2);
System.out.println(bd4);//5
//3.multiply 乘法
BigInteger bd5=bd1.multiply(bd2);
System.out.println(bd5);//50
//4.divide 除法
BigInteger bd6=bd1.divide(bd2);
System.out.println(bd6);//2
//5.获取商和余数
BigInteger[] arr=bd1.divideAndRemainder(bd2);
System.out.println(arr[0]);//商:2
System.out.println(arr[1]);//余数:0
//验证bd1和bd2的值是否发生改变
System.out.println(bd1);//10
System.out.println(bd2);//5
}
}
② equals方法
BigInteger类也重写了Object类的equals方法,可以比较对象内部的数据是否相同
public class Demo {
public static void main(String[] args) {
//创建两个BigInteger对象
BigInteger bd1=BigInteger.valueOf(10);
BigInteger bd2=BigInteger.valueOf(5);
BigInteger bd3=BigInteger.valueOf(10);
//equals 比较是否相等
boolean result1=bd1.equals(bd2);
boolean result2=bd1.equals(bd3);
System.out.println(result1);//false
System.out.println(result2);//true
}
}
③ pow方法
public class Demo4 {
public static void main(String[] args) {
//创建两个BigInteger对象
BigInteger bd1 = BigInteger.valueOf(10);
BigInteger bd2 = BigInteger.valueOf(5);
//pow 次幂
//注意次幂只能传int数据,不能传BigInteger对象
BigInteger bd3 = bd1.pow(2);
System.out.println(bd3);//100
//验证bd1和bd2的值是否发生改变
System.out.println(bd1);//10
System.out.println(bd2);//5
}
}
细节:pow()方法中的实参只能传int数据,不能传BigInteger对象,否则报错
**************************************************************************************************************
结论:
BigInteger对象一旦创建,内部记录的只将不能在发生改变。
只要进行计算都会产生一个新的BigInteger对象。
④ max/min方法
public class Demo{
public static void main(String[] args) {
//创建两个BigInteger对象
BigInteger bd1 = BigInteger.valueOf(10);
BigInteger bd2 = BigInteger.valueOf(5);
BigInteger bd3 = BigInteger.valueOf(10)
//max/min 返回较大值/较小值 --> 没有创建新对象,而是将结果对象返回
BigInteger bd5 = bd1.max(bd2);
System.out.println(bd1 == bd5);//true
System.out.println(bd1 == bd2);//false
}
}
细节:这个方法比较特殊,没有创建新对象,而是将结果对象返回。
⑤ intValue方法
public class Demo4 {
public static void main(String[] args) {
//intValue 转为int类型数据,超出范围数据有误
BigInteger bd1=BigInteger.valueOf(2147483647L);
BigInteger bd2=BigInteger.valueOf(2147483648L);
int x1=bd1.intValue();
int x2=bd2.intValue();
System.out.println(x1);//2147483647
System.out.println(x2);//-2147483648
}
}
可以发现,当BigInteger对象中的数据超出 int 类型的最大表示范围时,intValueg方法的结果就会产生错误。
如果想要正确返回数据,可以使用对应的转换方法,比如 longValue、doubleValue
3.底层存储方式
对于任意一个数,比如 27670116110564327424
① 首先将它转换成二进制,然后从右往左,每32位为一组
② 每组再各自转换成对应的十进制
③ 再按照顺序存储到一个数组中
对于 27670116110564327424 和 -27670116110564327424
可以看出,正负号由一个专门的变量signum存储,数据存放的数组为mag,所以两个mag数组都是 [1, -2147483648, 0]
**************************************************************************************************************
question:为什么说 BigInteger 可以表示非常大的数据,甚至接近于无穷大?
由上可知,mag数组的每一个元素都可以表示32位的数据,范围为:-2147483648 ~ 2147483647
而数组的最大长度是 int 的最大值:2147483647
所以说,BigInteger能表示的最大数字:42亿的21亿次方之多
但不可能有一台电脑的内存能支撑的起这么大的数据,所以说 BigInteger表示的数据可以看作是无穷大!
二、BigDecimal类
1.引言
对于以下代码,结果如何呢?
运行结果:
可以发现,运算结果均发生错误,这是为什么呢?
★★★★★★★★★★★★★★★★★ 小数在计算机中的存储方式 ★★★★★★★★★★★★★★★★★
计算机中不论是存储还是运算,都是将数据转换成二进制来进行操作的
对于任意一个浮点数,转成二进制会有整数和小数两部分
整数部分的次幂从0开始,小数部分的次幂从-1开始
那么对于0.9和0.226,二进制如何表示呢?
然而,对于 float 类型和 double 类型,所能够表示的小数部分的 bit 位数分别是23和52
因为0.226已经超出了 double 的小数部分最大表示范围,在这种情况下进行运算,必然会造成精度丢失。
然而,在现实中很多涉及金钱的地方,都需要小数的精确计算,BigDecimal类 能够解决这个问题。
2.定义
细节:
① BigDecimal 类和BigInteger 类一样,一旦创建对象就不可再改变。
② 不论进行何种运算,对象都不会在改变了,而是创建一个新的对象。
③ BigDecimal 类可以表示任意精度的有符号十进制数
3.方法
(1)构造方法
① public BigDecimal(double val)
该方法接受一个double类型的值作为参数,但是由于double类型使用二进制来表示小数。
有些小数在二进制中是无限循环小数,不能精确表示(例如 0.1).
因此,当将double类型的值转换为BigDecimal时,可能会出现精度损失,导致值不精确。
public class Demo1 {
public static void main(String[] args) {
//通过传递double类型的小数来创建对象
BigDecimal bd1 = new BigDecimal(0.1);
BigDecimal bd2 = new BigDecimal(0.9);
System.out.println(bd1);
System.out.println(bd2);
}
}
运行结果:
结论:这种方式有可能是不精确的,所以不建议使用
② public BigDecimal(String val)
该方法接受一个字符串作为参数,可以确保数值的精确性,因为字符串本身保存了数值的精确表示。
在创建对象时,参数中的字符串会被解析并转换为一个准确的数值表示,不会出现精度损失。
public class Demo1 {
public static void main(String[] args) {
//通过传递字符串表示的小数来创建对象
BigDecimal bd1 = new BigDecimal("0.1");
BigDecimal bd2 = new BigDecimal("0.9");
BigDecimal bd3 = bd1.add(bd2);
System.out.println(bd1);
System.out.println(bd2);
System.out.println(bd3);
}
}
运行结果:
结论:首选方法,因为它不会遇到 BigDecimal(double val) 构造方法的不可预知问题。
(2)静态方法
public class Demo2 {
public static void main(String[] args) {
BigDecimal bd1 = BigDecimal.valueOf(10);
BigDecimal bd2 = BigDecimal.valueOf(0.1);
System.out.println(bd1);//10
System.out.println(bd2);//0.1
}
}
question:为什么这里 0.1输出成功了呢?
通过源码可以看出,这里首先将double类型的 0.1 转换成了字符串,再创建BigDecimal 对象 ,等同于调用了public BigDecimal(String val)构造方法,所以结果是精确的。
结论:
注意这里静态方法的形参是double类型,所以
① 如果要表示的数组不大,没有超出double的取值范围,建议使用静态方法
② 如果要表示的数组比较大,超出了double的取值范围,建议使用构造方法
question:BigDecimal 的valueOf 方法是否像BigInteger 的valueOf 方法一样,也进行了内部优化呢?
通过源码可以发现,当形参是 long 类型时,也先进行了if 判断。
判断这个 val 是否小于ZERO_THROUGH_TEN 的长度,那这个ZERO_THROUGH_TEN 是什么呢?
在该类的静态变量里,发现 ZERO_THROUGH_TEN 是一个事先定义好的 BigDecimal 对象数组,从0到10,共11个,所以 ZERO_THROUGH_TEN.length等于11
再回来看 valueOf 方法,会发现:
首先判断 val 值是否在 0 ~ 10之间,是则返回对应的已经创建好的对象
如果是其他情况,则重新创建对象
public class Demo2 {
public static void main(String[] args) {
BigDecimal bd1 = BigDecimal.valueOf(10);
BigDecimal bd2 = BigDecimal.valueOf(10);
BigDecimal bd3 = BigDecimal.valueOf(10.0);
BigDecimal bd4 = BigDecimal.valueOf(10.0);
System.out.println(bd1 == bd2);//true
System.out.println(bd3 == bd4);//false
}
}
可以发现,由于 10 的BigDecimal对象已经创建,所以bd1和bd2指向同一个对象,地址值是相等的
而由上可知,实参时10.0,也就是形参是double类型时,是创建了一个新的对象,所以bd3和bd4是不相等的
结论:如果传递的是[0,10]之间的整数,那么方法会返回已经创建好的对象,不会重新new
(3)成员方法
① 加减乘除
public class Demo3 {
public static void main(String[] args) {
BigDecimal bd1 = BigDecimal.valueOf(10.0);
BigDecimal bd2 = BigDecimal.valueOf(2.0);
//1.add 加法
BigDecimal bd3 = bd1.add(bd2);
System.out.println(bd3);//12.0
//2.subtract 减法
BigDecimal bd4 = bd1.subtract(bd2);
System.out.println(bd4);//8.0
//3.multiply 乘法
BigDecimal bd5 = bd1.multiply(bd2);
System.out.println(bd5);//20.00
//4.divide 除法
BigDecimal bd6 = bd1.divide(bd2);
System.out.println(bd6);//5
}
}
② 除法
一般情况下,除法可以正常运算,但如果是除不尽的情况下,就会报错
public class Demo4 {
public static void main(String[] args) {
BigDecimal bd1 = BigDecimal.valueOf(10.0);
BigDecimal bd2 = BigDecimal.valueOf(3.0);
BigDecimal bd3= bd1.divide(bd2);
System.out.println(bd3);
}
}
运行结果:
在这中情况下,我们通常采用 divide 的重载方法
其中scale 表示要保留几位小数,RoundingMode表示舍入的方式,如下:
常用的舍入方式就是 HALF_UP,就是我们常说的四舍五入
public class Demo4 {
public static void main(String[] args) {
BigDecimal bd1 = BigDecimal.valueOf(10.0);
BigDecimal bd2 = BigDecimal.valueOf(3.0);
BigDecimal bd3 = bd1.divide(bd2, 2, RoundingMode.HALF_UP);
System.out.println(bd3);//0.33
}
}
运行结果:
4.底层存储方式
BigDecimal 的底层存储不同于 BigInteger
对于 0.266 一个这么短的数,转换成二进制之后就高达55位。
那如果小数是成百上千位,在转换成二进制之后,就会非常长。
如果和BigInteger 一样,按每32位为一段进行划分,效率会非常低。
对于0.226,实际的存储方式是先将每一位拆分,再转换成其ASCII码,存储到数组中
再看3个例子:
通过Debug调试:
由于也是用数组存储,而数组的最大长度是 int 的最大值:2147483647
也不可能有任何一台电脑能容纳的下这么大的内存,所以 BigDecimal 能表示的数据也同样是能看作是无穷大的!