一、简介

Java 提供了一些原语,例如 intlong 来执行整数运算。但有时,我们需要存储数字,这会超出这些数据类型的可用限制。

在本教程中,我们将深入研究 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 来获取该值,但我们可以通过调试器中的评估工具来完成:

贝尔 4920 1

我们使用两个整数(两包 32 位)将值存储在数组中。第零个元素等于 Integer.MIN_VALUE ,另一个元素为零。

4。结论

在本快速教程中,我们重点关注 BigInteger 类的实现细节。我们首先提醒一些有关数字、原语和二进制表示规则的信息。

然后我们检查了 BigInteger的源代码。 我们检查了 signummag 属性。我们还了解了 BigInteger 如何存储给定值,使我们能够提供比可用原始数据类型更大的数字。

与往常一样,我们可以在 GitHub 上找到所有代码片段和测试。