一、简介
Java 提供了一些原语,例如 int 或 long 来执行整数运算。但有时,我们需要存储数字,这会超出这些数据类型的可用限制。
在本教程中,我们将深入研究 BigInteger 类。我们将通过查看源代码来检查其结构并回答问题 - 是否可以存储超出可用原始数据类型限制的大量数字?
2.BigInteger 类
众所周知, BigInteger 类用于数学运算,涉及比原始 long 类型更大的整数计算。它 表示不可变的任意精度整数 。
在进一步讨论之前,让我们记住,在 Java 中,所有字节都 使用big-endian 表示法在二进制补码系统中 表示。它将字的最高有效字节存储在最小内存地址(最低索引)处。而且,字节的第一位也是符号位。让我们检查示例字节值:
- 1000 0000 代表 -128
- 0111 1111 代表127
- 1111 1111 代表 -1
现在,让我们检查源代码并解释它如何存储超出可用基元限制的给定数字。
2.1. 整型符号
Signum 属性 确定 BigInteger 的符号 。三个整数值代表值的符号: -1 表示负数, 0 表示零, 1 表示正数:
assertEquals(1, BigInteger.TEN.signum());
assertEquals(-1, BigInteger.TEN.negate().signum());
assertEquals(0, BigInteger.ZERO.signum());
让我们注意,由于幅度数组, BigInteger.ZERO 的 符号 必须为 0 。该值确保 每个 BigInteger 值都只有一种表示形式 。
2.2. int[] 磁力
BigInteger 类的所有魔力都始于 mag 属性。它 使用二进制表示将给定值存储在数组中 ,这允许省略原始数据类型限制。
此外, BigInteger 将它们分组为 32 位部分 ——一组四个字节。因此,类定义中的大小被声明为 int 数组:
int[] mag;
该数组 以大端表示法保存给定值的大小 。该数组的第 0 个元素是该值的最高有效整数。让我们使用 BigInteger(byte[] bytes) 检查它:
assertEquals(new BigInteger("1"), new BigInteger(new byte[]{0b1}))
assertEquals(new BigInteger("2"), new BigInteger(new byte[]{0b10}))
assertEquals(new BigInteger("4"), new BigInteger(new byte[]{0b100}))
此构造函数将包含补码二进制表示形式的给定字节数组转换为值。
由于存在符号量值变量 ( signum ),因此我们 不使用第一位作为值的符号位 。让我们快速检查一下:
byte[] bytes = { -128 }; // 1000 0000
assertEquals(new BigInteger("128"), new BigInteger(1, bytes));
assertEquals(new BigInteger("-128"), new BigInteger(-1, bytes));
我们使用 BigInteger(intsignum, byte[]magnitude) 构造函数创建了两个不同的值。它将符号-数值表示形式转换为 BigInteger。 我们重复使用相同的字节数组,仅更改符号值。
我们还可以使用 toString(int radix) 方法打印幅度:
assertEquals("10000000", new BigInteger(1, bytes));
assertEquals("-10000000", new BigInteger(-1, bytes));
请注意,对于负值,会添加减号。
最后, 幅度的最高有效 整数 必须是非零 。这意味着 BigInteger.ZERO 有一个零长度的 mag 数组:
assertEquals(0, BigInteger.ZERO.bitCount());
assertEquals(BigInteger.ZERO, new BigInteger(0, new byte[]{}));
现在,我们将跳过检查其他属性。由于冗余,它们被标记为已弃用,仅用作内部缓存。
现在让我们直接看更复杂的示例,并检查 BigInteger 如何通过基本数据类型存储数字。
3. BigInteger 大于 Long.MAX_VALUE。
我们已经知道, long 数据类型是 64 位二进制补码整数 。有符号长整型的最小值为 -263 (1000 0000 … 0000) ,最大值为 263-1 (0111 1111 … 1111)。 要创建超过这些限制的数字,我们需要使用 BigInteger 类。
现在让我们创建一个比 Long.MAX_VALUE 大一的值,等于 263。根据上一章的信息,它需要具有:
- 符号 属性设置为 1,
- mag 数组,总共 64 位,其中仅设置最高有效位 (1000 0000 … 0000)。
首先,让我们使用 setBit(int n) 函数创建一个 BigInteger :
BigInteger bi1 = BigInteger.ZERO.setBit(63);
String str = bi1.toString(2);
assertEquals(64, bi1.bitLength());
assertEquals(1, bi1.signum());
assertEquals("9223372036854775808", bi1.toString());
assertEquals(BigInteger.ONE, bi1.substract(BigInteger.valueOf(Long.MAX_VALUE)));
assertEquals(64, str.length());
assertTrue(str.matches("^10{63}$")); // 1000 0000 ... 0000
请记住,在二进制表示系统中,位是从右到左排序的,从 0 开始。虽然 BigInteger.ZERO 有一个空的幅度数组,但设置第 63 位同时使其成为最高有效位 - 的第 0 个元素。 64 长度数组。 符号 自动设置为 1。
另一方面,相同的位序列由 Long.MIN_VALUE 表示。让我们将此常量转换为 byte[] 数组并创建构造 BigInteger:
byte[] bytes = ByteBuffer.allocate(Long.BYTES).putLong(Long.MIN_VALUE).array();
BigInteger bi2 = new BigInteger(1, bytes);
assertEquals(bi1, bi2);
...
正如我们所看到的,两个值相等,因此应用相同的断言包。
最后,我们可以检查内部 int[] mag 变量。目前,Java 不提供 API 来获取该值,但我们可以通过调试器中的评估工具来完成:
我们使用两个整数(两包 32 位)将值存储在数组中。第零个元素等于 Integer.MIN_VALUE ,另一个元素为零。
4。结论
在本快速教程中,我们重点关注 BigInteger 类的实现细节。我们首先提醒一些有关数字、原语和二进制表示规则的信息。
然后我们检查了 BigInteger的源代码。 我们检查了 signum 和 mag 属性。我们还了解了 BigInteger 如何存储给定值,使我们能够提供比可用原始数据类型更大的数字。
与往常一样,我们可以在 GitHub 上找到所有代码片段和测试。