1.概述
在处理大数字时,我们常常受限于int
和long
类型的大小。Java通过BigInteger
类提供了一种解决方案。然而,有时API并不支持我们希望使用的所有算术运算。
求一个大数的平方根虽然常见,但往往颇具挑战性。本教程将介绍如何实现这一操作,并分析各种方法的优缺点。
2. 计算平方根
让我们来看看几种计算方法。
2.1. Java 9 BigInteger
API
首先,我们从Java 9开始,它引入了最直接的方法。从这个版本开始,BigInteger
提供了两个有用的方法:sqrt()
和sqrtAndReminder()
。我们先来看第一个:
BigInteger integer = new BigInteger(bigDecimalNumber);
BigInteger root = integer.sqrt();
第二个方法类似,但提供了关于余数的额外信息:
BigInteger integer = new BigInteger(bigDecimalNumber);
BigInteger[] rootAndReminder = integer.sqrtAndRemainder();
这两个方法提供了简单直观的计算根的方法。然而,它们需要特定的Java版本,对于某些应用可能是个问题。
2.2. Guava的BigIntegerMath
另一种不升级Java版本的方法是使用Guava库:
BigInteger integer = new BigInteger(bigDecimalNumber);
BigInteger root = BigIntegerMath.sqrt(integer, RoundingMode.DOWN);
这个方法接受RoundingMode
参数来确定舍入逻辑。Guava库没有提供简单的方式来获取余数,如果需要,我们需要额外步骤:
BigInteger integer = new BigInteger(bigDecimalNumber);
BigInteger root = BigIntegerMath.sqrt(integer, RoundingMode.DOWN);
BigInteger reminder = integer.subtract(root.multiply(root));
2.3. NewtonPlus算法
虽然可以自定义实现大整数平方根的查找,但通常不建议这样做。自定义实现往往复杂度较高,处理效率较低。有一些简单但效率低下的算法,如二分查找法(Java查找平方根是否为整数)和牛顿法(Java查找平方根是否为整数`)。
然而,有一种算法NewtonPlus在标准Java和Guava实现上表现出更好的性能。这个算法由Ryan Scott White提出,基于牛顿法,对于大于5 x 10^14的数字显示出性能提升。
性能比较
为了比较Java、Guava库、NewtonPlus和牛顿法的性能差异,我们将在三个不同的值上运行性能测试:
private static final String BIG_NUMBER = "1797693130000000000000000000000..." // 309 digits
private static final String VERY_BIG_NUMBER = "32473927492374927934284792..." // 1802 digits
private static final String INSANELY_BIG_NUMBER = "3247392749237492793428..." // 3604 digits
执行JMH基准测试后,我们得到以下结果:
Benchmark (number) Mode Cnt Score Error Units
BigIntegerSquareRootBenchmark.calculateRootWithNewton BIG_NUMBER thrpt 2668.642 ops/s
BigIntegerSquareRootBenchmark.calculateRootWithJava BIG_NUMBER thrpt 25417.428 ops/s
BigIntegerSquareRootBenchmark.calculateRootWithGuava BIG_NUMBER thrpt 144117.671 ops/s
BigIntegerSquareRootBenchmark.calculateRootWithNewtonPlus BIG_NUMBER thrpt 308933.077 ops/s
BigIntegerSquareRootBenchmark.calculateRootWithNewton VERY_BIG_NUMBER thrpt 33.627 ops/s
BigIntegerSquareRootBenchmark.calculateRootWithJava VERY_BIG_NUMBER thrpt 1376.668 ops/s
BigIntegerSquareRootBenchmark.calculateRootWithGuava VERY_BIG_NUMBER thrpt 5349.748 ops/s
BigIntegerSquareRootBenchmark.calculateRootWithNewtonPlus VERY_BIG_NUMBER thrpt 12283.677 ops/s
BigIntegerSquareRootBenchmark.calculateRootWithNewton INSANELY_BIG_NUMBER thrpt 9.135 ops/s
BigIntegerSquareRootBenchmark.calculateRootWithJava INSANELY_BIG_NUMBER thrpt 553.475 ops/s
BigIntegerSquareRootBenchmark.calculateRootWithGuava INSANELY_BIG_NUMBER thrpt 1713.520 ops/s
BigIntegerSquareRootBenchmark.calculateRootWithNewtonPlus INSANELY_BIG_NUMBER thrpt 3252.308 ops/s
NewtonPlus方法表现出最佳性能,对于需要大量此类计算的应用来说可能是不错的选择。然而,如果在代码库中添加高度优化的自定义代码不太吸引人,我们可以选择性能相对较好的Guava的BigIntegerMath
。
另外,像牛顿这样的简单算法效率低下,应避免使用。总的来说,二分查找法的性能不如牛顿法,因为它收敛速度较慢。
结论
在处理大数时,我们需要一种方便的方式来在其上执行标准数学运算。从数学角度看,无论数字大小如何,运算都是相同的,但计算机科学会带来一些限制。这就是为什么我们需要特定的库和算法来处理BigIntegers
。
根据具体任务的需求,我们可以选择不同的解决方案,从标准库到针对应用需求精心调整的自定义解决方案。然而,过早优化通常并不好,甚至有害。因此,我们在选择时应保持合理。
如往常一样,所有的代码可以在GitHub上找到。