From d76083ae0473e78f515f1c48d374eb7cb0c04f14 Mon Sep 17 00:00:00 2001
From: Eugene Kovko <37694937+eukovko@users.noreply.github.com>
Date: Wed, 4 Oct 2023 18:23:38 +0200
Subject: [PATCH] BAEL-6828: Finding the Square Root of a BigInteger in Java
(#14903)
* BAEL-6828: Finding the Square Root of a BigInteger in Java
* BAEL-6828: Remove Java 9 annotation from the test
* BAEL-6828: Remove Java 9 profile
---
core-java-modules/core-java-numbers-6/pom.xml | 7 +-
.../bigintegerroot/BenchmarkRunner.java | 8 ++
.../bigintegerroot/BigIntegerHolder.java | 10 ++
.../BigIntegerSquareRootBenchmark.java | 60 ++++++++++
.../bigintegerroot/algorithms/Newton.java | 29 +++++
.../bigintegerroot/algorithms/NewtonPlus.java | 108 ++++++++++++++++++
.../BigIntegerSquareRootUnitTest.java | 39 +++++++
7 files changed, 260 insertions(+), 1 deletion(-)
create mode 100644 core-java-modules/core-java-numbers-6/src/main/java/com/baeldung/bigintegerroot/BenchmarkRunner.java
create mode 100644 core-java-modules/core-java-numbers-6/src/main/java/com/baeldung/bigintegerroot/BigIntegerHolder.java
create mode 100644 core-java-modules/core-java-numbers-6/src/main/java/com/baeldung/bigintegerroot/BigIntegerSquareRootBenchmark.java
create mode 100644 core-java-modules/core-java-numbers-6/src/main/java/com/baeldung/bigintegerroot/algorithms/Newton.java
create mode 100644 core-java-modules/core-java-numbers-6/src/main/java/com/baeldung/bigintegerroot/algorithms/NewtonPlus.java
create mode 100644 core-java-modules/core-java-numbers-6/src/test/java/com/baeldung/bigintegerroot/BigIntegerSquareRootUnitTest.java
diff --git a/core-java-modules/core-java-numbers-6/pom.xml b/core-java-modules/core-java-numbers-6/pom.xml
index 531f1293d1..7a3b3d4426 100644
--- a/core-java-modules/core-java-numbers-6/pom.xml
+++ b/core-java-modules/core-java-numbers-6/pom.xml
@@ -25,8 +25,12 @@
${commons-codec}
test
+
+ com.google.guava
+ guava
+ ${guava.version}
+
-
core-java-numbers-6
@@ -39,5 +43,6 @@
1.15
+ 32.1.2-jre
\ No newline at end of file
diff --git a/core-java-modules/core-java-numbers-6/src/main/java/com/baeldung/bigintegerroot/BenchmarkRunner.java b/core-java-modules/core-java-numbers-6/src/main/java/com/baeldung/bigintegerroot/BenchmarkRunner.java
new file mode 100644
index 0000000000..2ac57a9c0c
--- /dev/null
+++ b/core-java-modules/core-java-numbers-6/src/main/java/com/baeldung/bigintegerroot/BenchmarkRunner.java
@@ -0,0 +1,8 @@
+package com.baeldung.bigintegerroot;
+
+public class BenchmarkRunner {
+ public static void main(String[] args) throws Exception {
+ org.openjdk.jmh.Main.main(args);
+ }
+}
+
diff --git a/core-java-modules/core-java-numbers-6/src/main/java/com/baeldung/bigintegerroot/BigIntegerHolder.java b/core-java-modules/core-java-numbers-6/src/main/java/com/baeldung/bigintegerroot/BigIntegerHolder.java
new file mode 100644
index 0000000000..c2d2f30827
--- /dev/null
+++ b/core-java-modules/core-java-numbers-6/src/main/java/com/baeldung/bigintegerroot/BigIntegerHolder.java
@@ -0,0 +1,10 @@
+package com.baeldung.bigintegerroot;
+
+public class BigIntegerHolder {
+
+ private BigIntegerHolder() {
+ }
+ public static final String BIG_NUMBER = "179769313000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000";
+ public static final String VERY_BIG_NUMBER = "32473927492374927934284792374923749237498723048012834324739274923749279342847923749237492374987230480128343247392749237492793428479237492374923749872304801283432473927492374927934284792374923749237498723048012834324739274923749279342847923749237492374987230480128343247392749237492793428479237492374923749872304801283432473927492374927934284792374923749237498723048012834324739274923749279342847923749237492374987230480128343247392749237492793428479237492374923749872304801283432473927492374927934284792374923749237498723048012834324739274923749279342847923749237492374987230480128343247392749237492793428479237492374923749872304801283432473927492374927934284792374923749237498723048012834324739274923749279342847923749237492374987230480128343247392749237492793428479237492374923749872304801283432473927492374927934284792374923749237498723048012834324739274923749279342847923749237492374987230480128343247392749237492793428479237492374923749872304801283432473927492374927934284792374923749237498723048012834324739274923749279342847923749237492374987230480128343247392749237492793428479237492374923749872304801283432473927492374927934284792374923749237498723048012834324739274923749279342847923749237492374987230480128343247392749237492793428479237492374923749872304801283432473927492374927934284792374923749237498723048012834324739274923749279342847923749237492374987230480128343247392749237492793428479237492374923749872304801283432473927492374927934284792374923749237498723048012834324739274923749279342847923749237492374987230480128343247392749237492793428479237492374923749872304801283432473927492374927934284792374923749237498723048012834324739274923749279342847923749237492374987230480128343247392749237492793428479237492374923749872304801283432473927492374927934284792374923749237498723048012834";
+ public static final String INSANELY_BIG_NUMBER = "3247392749237492793428479237492374923749872304801283432473927492374927934284792374923749237498723048012834324739274923749279342847923749237492374987230480128343247392749237492793428479237492374923749872304801283432473927492374927934284792374923749237498723048012834324739274923749279342847923749237492374987230480128343247392749237492793428479237492374923749872304801283432473927492374927934284792374923749237498723048012834324739274923749279342847923749237492374987230480128343247392749237492793428479237492374923749872304801283432473927492374927934284792374923749237498723048012834324739274923749279342847923749237492374987230480128343247392749237492793428479237492374923749872304801283432473927492374927934284792374923749237498723048012834324739274923749279342847923749237492374987230480128343247392749237492793428479237492374923749872304801283432473927492374927934284792374923749237498723048012834324739274923749279342847923749237492374987230480128343247392749237492793428479237492374923749872304801283432473927492374927934284792374923749237498723048012834324739274923749279342847923749237492374987230480128343247392749237492793428479237492374923749872304801283432473927492374927934284792374923749237498723048012834324739274923749279342847923749237492374987230480128343247392749237492793428479237492374923749872304801283432473927492374927934284792374923749237498723048012834324739274923749279342847923749237492374987230480128343247392749237492793428479237492374923749872304801283432473927492374927934284792374923749237498723048012834324739274923749279342847923749237492374987230480128343247392749237492793428479237492374923749872304801283432473927492374927934284792374923749237498723048012834324739274923749279342847923749237492374987230480128343247392749237492793428479237492374923749872304801283432473927492374927934284792374923749237498723048012834324739274923749279342847923749237492374987230480128343247392749237492793428479237492374923749872304801283432473927492374927934284792374923749237498723048012834324739274923749279342847923749237492374987230480128343247392749237492793428479237492374923749872304801283432473927492374927934284792374923749237498723048012834324739274923749279342847923749237492374987230480128343247392749237492793428479237492374923749872304801283432473927492374927934284792374923749237498723048012834324739274923749279342847923749237492374987230480128343247392749237492793428479237492374923749872304801283432473927492374927934284792374923749237498723048012834324739274923749279342847923749237492374987230480128343247392749237492793428479237492374923749872304801283432473927492374927934284792374923749237498723048012834324739274923749279342847923749237492374987230480128343247392749237492793428479237492374923749872304801283432473927492374927934284792374923749237498723048012834324739274923749279342847923749237492374987230480128343247392749237492793428479237492374923749872304801283432473927492374927934284792374923749237498723048012834324739274923749279342847923749237492374987230480128343247392749237492793428479237492374923749872304801283432473927492374927934284792374923749237498723048012834324739274923749279342847923749237492374987230480128343247392749237492793428479237492374923749872304801283432473927492374927934284792374923749237498723048012834324739274923749279342847923749237492374987230480128343247392749237492793428479237492374923749872304801283432473927492374927934284792374923749237498723048012834324739274923749279342847923749237492374987230480128343247392749237492793428479237492374923749872304801283432473927492374927934284792374923749237498723048012834";
+}
diff --git a/core-java-modules/core-java-numbers-6/src/main/java/com/baeldung/bigintegerroot/BigIntegerSquareRootBenchmark.java b/core-java-modules/core-java-numbers-6/src/main/java/com/baeldung/bigintegerroot/BigIntegerSquareRootBenchmark.java
new file mode 100644
index 0000000000..645e4eb2dd
--- /dev/null
+++ b/core-java-modules/core-java-numbers-6/src/main/java/com/baeldung/bigintegerroot/BigIntegerSquareRootBenchmark.java
@@ -0,0 +1,60 @@
+package com.baeldung.bigintegerroot;
+
+import static com.baeldung.bigintegerroot.BigIntegerHolder.*;
+
+import com.baeldung.bigintegerroot.algorithms.Newton;
+import com.baeldung.bigintegerroot.algorithms.NewtonPlus;
+import com.google.common.math.BigIntegerMath;
+import java.math.BigInteger;
+import java.math.RoundingMode;
+import java.util.concurrent.TimeUnit;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Fork;
+import org.openjdk.jmh.annotations.Measurement;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.Param;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.Warmup;
+import org.openjdk.jmh.infra.Blackhole;
+
+@Warmup(iterations = 1)
+@Measurement(iterations = 1, time = 1, timeUnit = TimeUnit.MINUTES)
+@Fork(1)
+@State(Scope.Benchmark)
+public class BigIntegerSquareRootBenchmark {
+
+ @Param({BIG_NUMBER, VERY_BIG_NUMBER, INSANELY_BIG_NUMBER})
+ public String number;
+ @Benchmark
+ @BenchmarkMode(Mode.Throughput)
+ public void calculateRootWithJava(Blackhole blackhole) {
+ final BigInteger integer = new BigInteger(number);
+ final BigInteger root = integer.sqrt();
+ blackhole.consume(root);
+ }
+
+ @Benchmark
+ @BenchmarkMode(Mode.Throughput)
+ public void calculateRootWithGuava(Blackhole blackhole) {
+ final BigInteger integer = new BigInteger(number);
+ final BigInteger root = BigIntegerMath.sqrt(integer, RoundingMode.DOWN);
+ blackhole.consume(root);
+ }
+
+ @Benchmark
+ @BenchmarkMode(Mode.Throughput)
+ public void calculateRootWithNewtonPlus(Blackhole blackhole) {
+ final BigInteger integer = new BigInteger(number);
+ final BigInteger root = NewtonPlus.sqrt(integer);
+ blackhole.consume(root);
+ }
+ @Benchmark
+ @BenchmarkMode(Mode.Throughput)
+ public void calculateRootWithNewton(Blackhole blackhole) {
+ final BigInteger integer = new BigInteger(number);
+ final BigInteger root = Newton.sqrt(integer);
+ blackhole.consume(root);
+ }
+}
diff --git a/core-java-modules/core-java-numbers-6/src/main/java/com/baeldung/bigintegerroot/algorithms/Newton.java b/core-java-modules/core-java-numbers-6/src/main/java/com/baeldung/bigintegerroot/algorithms/Newton.java
new file mode 100644
index 0000000000..07acba7537
--- /dev/null
+++ b/core-java-modules/core-java-numbers-6/src/main/java/com/baeldung/bigintegerroot/algorithms/Newton.java
@@ -0,0 +1,29 @@
+package com.baeldung.bigintegerroot.algorithms;
+
+import java.math.BigInteger;
+
+public class Newton {
+
+ private Newton() {
+ }
+
+ public static BigInteger sqrt(BigInteger n) {
+ // Initial approximation
+ BigInteger x = n.divide(BigInteger.TWO);
+
+ // Tolerance level (small positive integer)
+ BigInteger tolerance = BigInteger.ONE;
+
+ while (true) {
+ // x_new = 0.5 * (x + n / x)
+ BigInteger xNew = x.add(n.divide(x)).divide(BigInteger.TWO);
+
+ // Check for convergence within tolerance
+ if (x.subtract(xNew).abs().compareTo(tolerance) <= 0) {
+ return xNew;
+ }
+
+ x = xNew;
+ }
+ }
+}
diff --git a/core-java-modules/core-java-numbers-6/src/main/java/com/baeldung/bigintegerroot/algorithms/NewtonPlus.java b/core-java-modules/core-java-numbers-6/src/main/java/com/baeldung/bigintegerroot/algorithms/NewtonPlus.java
new file mode 100644
index 0000000000..80d50c0ca5
--- /dev/null
+++ b/core-java-modules/core-java-numbers-6/src/main/java/com/baeldung/bigintegerroot/algorithms/NewtonPlus.java
@@ -0,0 +1,108 @@
+package com.baeldung.bigintegerroot.algorithms;
+
+import java.math.BigInteger;
+
+public class NewtonPlus {
+
+ private NewtonPlus() {
+ }
+
+ // A fast square root by Ryan Scott White.
+ public static BigInteger sqrt(BigInteger x) {
+ if (x.compareTo(BigInteger.valueOf(144838757784765629L)) < 0) {
+ long xAsLong = x.longValue();
+ long vInt = (long)Math.sqrt(xAsLong);
+ if (vInt * vInt > xAsLong)
+ vInt--;
+ return BigInteger.valueOf(vInt); }
+
+ double xAsDub = x.doubleValue();
+ BigInteger val;
+ if (xAsDub < 2.1267e37) // 2.12e37 largest here
+ // since sqrt(long.max*long.max) > long.max
+ {
+ long vInt = (long)Math.sqrt(xAsDub);
+ val = BigInteger.valueOf
+ ((vInt + x.divide(BigInteger.valueOf(vInt)).longValue()) >> 1);
+ }
+ else if (xAsDub < 4.3322e127) {
+ // Convert a double to a BigInteger
+ long bits = Double.doubleToLongBits(Math.sqrt(xAsDub));
+ int exp = ((int) (bits >> 52) & 0x7ff) - 1075;
+ val = BigInteger.valueOf((bits & ((1L << 52)) - 1) | (1L << 52)).shiftLeft(exp);
+
+ val = x.divide(val).add(val).shiftRight(1);
+ if (xAsDub > 2e63) {
+ val = x.divide(val).add(val).shiftRight(1); }
+ }
+ else // handle large numbers over 4.3322e127
+ {
+ int xLen = x.bitLength();
+ int wantedPrecision = ((xLen + 1) / 2);
+ int xLenMod = xLen + (xLen & 1) + 1;
+
+ //////// Do the first Sqrt on Hardware ////////
+ long tempX = x.shiftRight(xLenMod - 63).longValue();
+ double tempSqrt1 = Math.sqrt(tempX);
+ long valLong = Double.doubleToLongBits(tempSqrt1) & 0x1fffffffffffffL;
+
+ if (valLong == 0)
+ valLong = 1L << 53;
+
+ //////// Classic Newton Iterations ////////
+ val = BigInteger.valueOf(valLong).shiftLeft(53 - 1)
+ .add((x.shiftRight(xLenMod -
+ (3 * 53))).divide(BigInteger.valueOf(valLong)));
+
+ int size = 106;
+ for (; size < 256; size <<= 1) {
+ val = val.shiftLeft(size - 1).add(x.shiftRight
+ (xLenMod - (3*size)).divide(val));}
+
+ if (xAsDub > 4e254) { // 4e254 = 1<<845.77
+ int numOfNewtonSteps = 31 -
+ Integer.numberOfLeadingZeros(wantedPrecision / size)+1;
+
+ ////// Apply Starting Size ////////
+ int wantedSize = (wantedPrecision >> numOfNewtonSteps) + 2;
+ int needToShiftBy = size - wantedSize;
+ val = val.shiftRight(needToShiftBy);
+
+ size = wantedSize;
+ do {
+ //////// Newton Plus Iteration ////////
+ int shiftX = xLenMod - (3 * size);
+ BigInteger valSqrd = val.multiply(val).shiftLeft(size - 1);
+ BigInteger valSU = x.shiftRight(shiftX).subtract(valSqrd);
+ val = val.shiftLeft(size).add(valSU.divide(val));
+ size *= 2;
+ } while (size < wantedPrecision);
+ }
+ val = val.shiftRight(size - wantedPrecision);
+ }
+
+ // Detect a round ups. This function can be further optimized - see article.
+ // For a ~7% speed bump the following line can be removed but round-ups will occur.
+ if (val.multiply(val).compareTo(x) > 0)
+ val = val.subtract(BigInteger.ONE);
+
+ // Enabling the below will guarantee an error is stopped for larger numbers.
+ // Note: As of this writing, there are no known errors.
+ BigInteger tmp = val.multiply(val);
+ if (tmp.compareTo(x) > 0) {
+ System.out.println("val^2(" + val.multiply(val).toString()
+ + ") ≥ x(" + x.toString()+")");
+ System.console().readLine();
+ //throw new Exception("Sqrt function had internal error - value too high");
+ }
+ if (tmp.add(val.shiftLeft(1)).add(BigInteger.ONE).compareTo(x) <= 0) {
+ System.out.println("(val+1)^2("
+ + val.add(BigInteger.ONE).multiply(val.add(BigInteger.ONE)).toString()
+ + ") ≥ x(" + x.toString() + ")");
+ System.console().readLine();
+ //throw new Exception("Sqrt function had internal error - value too low");
+ }
+
+ return val;
+ }
+}
diff --git a/core-java-modules/core-java-numbers-6/src/test/java/com/baeldung/bigintegerroot/BigIntegerSquareRootUnitTest.java b/core-java-modules/core-java-numbers-6/src/test/java/com/baeldung/bigintegerroot/BigIntegerSquareRootUnitTest.java
new file mode 100644
index 0000000000..edb75b16ef
--- /dev/null
+++ b/core-java-modules/core-java-numbers-6/src/test/java/com/baeldung/bigintegerroot/BigIntegerSquareRootUnitTest.java
@@ -0,0 +1,39 @@
+package com.baeldung.bigintegerroot;
+
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import com.baeldung.bigintegerroot.algorithms.Newton;
+import com.baeldung.bigintegerroot.algorithms.NewtonPlus;
+import com.google.common.math.BigIntegerMath;
+import java.math.BigInteger;
+import java.math.RoundingMode;
+import java.util.stream.Stream;
+import org.apache.commons.math3.util.Pair;
+import org.junit.jupiter.api.condition.EnabledForJreRange;
+import org.junit.jupiter.api.condition.JRE;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+class BigIntegerSquareRootUnitTest {
+
+ @ParameterizedTest
+ @ValueSource(strings = {
+ BigIntegerHolder.BIG_NUMBER,
+ BigIntegerHolder.VERY_BIG_NUMBER,
+ BigIntegerHolder.VERY_BIG_NUMBER
+ })
+ void squareRootTest(String number) {
+ final BigInteger bigInteger = new BigInteger(number);
+ final BigInteger javaRoot = bigInteger.sqrt();
+ final BigInteger guavaRoot = BigIntegerMath.sqrt(bigInteger, RoundingMode.DOWN);
+ final BigInteger newtonRoot = Newton.sqrt(bigInteger);
+ final BigInteger newtonPlusRoot = NewtonPlus.sqrt(bigInteger);
+
+ assertTrue(Stream.of(
+ new Pair<>(javaRoot, guavaRoot),
+ new Pair<>(guavaRoot, newtonRoot),
+ new Pair<>(newtonRoot, newtonPlusRoot)
+ ).allMatch(pair -> pair.getFirst().equals(pair.getSecond())));
+ }
+}
\ No newline at end of file