diff --git a/src/java.base/share/classes/java/math/BigInteger.java b/src/java.base/share/classes/java/math/BigInteger.java
index 98f55ef3c8c..8d12c6de0a7 100644
--- a/src/java.base/share/classes/java/math/BigInteger.java
+++ b/src/java.base/share/classes/java/math/BigInteger.java
@@ -45,6 +45,7 @@ import java.util.concurrent.ThreadLocalRandom;
 
 import jdk.internal.math.DoubleConsts;
 import jdk.internal.math.FloatConsts;
+import jdk.internal.util.ArraysSupport;
 import jdk.internal.vm.annotation.ForceInline;
 import jdk.internal.vm.annotation.IntrinsicCandidate;
 import jdk.internal.vm.annotation.Stable;
@@ -3963,6 +3964,7 @@ public class BigInteger extends Number implements Comparable<BigInteger> {
      * @return -1, 0 or 1 as this BigInteger is numerically less than, equal
      *         to, or greater than {@code val}.
      */
+    @Override
     public int compareTo(BigInteger val) {
         if (signum == val.signum) {
             return switch (signum) {
@@ -3991,12 +3993,9 @@ public class BigInteger extends Number implements Comparable<BigInteger> {
             return -1;
         if (len1 > len2)
             return 1;
-        for (int i = 0; i < len1; i++) {
-            int a = m1[i];
-            int b = m2[i];
-            if (a != b)
-                return ((a & LONG_MASK) < (b & LONG_MASK)) ? -1 : 1;
-        }
+        int i = ArraysSupport.mismatch(m1, m2, len1);
+        if (i != -1)
+            return Integer.compareUnsigned(m1[i], m2[i]) < 0 ? -1 : 1;
         return 0;
     }
 
@@ -4023,7 +4022,7 @@ public class BigInteger extends Number implements Comparable<BigInteger> {
             int a = m1[0];
             int b = (int)val;
             if (a != b) {
-                return ((a & LONG_MASK) < (b & LONG_MASK))? -1 : 1;
+                return Integer.compareUnsigned(a, b) < 0 ? -1 : 1;
             }
             return 0;
         } else {
@@ -4032,12 +4031,12 @@ public class BigInteger extends Number implements Comparable<BigInteger> {
             int a = m1[0];
             int b = highWord;
             if (a != b) {
-                return ((a & LONG_MASK) < (b & LONG_MASK))? -1 : 1;
+                return Integer.compareUnsigned(a, b) < 0 ? -1 : 1;
             }
             a = m1[1];
             b = (int)val;
             if (a != b) {
-                return ((a & LONG_MASK) < (b & LONG_MASK))? -1 : 1;
+                return Integer.compareUnsigned(a, b) < 0 ? -1 : 1;
             }
             return 0;
         }
@@ -4050,6 +4049,7 @@ public class BigInteger extends Number implements Comparable<BigInteger> {
      * @return {@code true} if and only if the specified Object is a
      *         BigInteger whose value is numerically equal to this BigInteger.
      */
+    @Override
     public boolean equals(Object x) {
         // This test is just an optimization, which may or may not help
         if (x == this)
@@ -4061,17 +4061,10 @@ public class BigInteger extends Number implements Comparable<BigInteger> {
         if (xInt.signum != signum)
             return false;
 
-        int[] m = mag;
-        int len = m.length;
-        int[] xm = xInt.mag;
-        if (len != xm.length)
+        if (mag.length != xInt.mag.length)
             return false;
 
-        for (int i = 0; i < len; i++)
-            if (xm[i] != m[i])
-                return false;
-
-        return true;
+        return ArraysSupport.mismatch(mag, xInt.mag, mag.length) == -1;
     }
 
     /**
@@ -4100,17 +4093,12 @@ public class BigInteger extends Number implements Comparable<BigInteger> {
     // Hash Function
 
     /**
-     * Returns the hash code for this BigInteger.
-     *
-     * @return hash code for this BigInteger.
+     * {@return the hash code for this BigInteger}
      */
+    @Override
     public int hashCode() {
-        int hashCode = 0;
-
-        for (int i=0; i < mag.length; i++)
-            hashCode = (int)(31*hashCode + (mag[i] & LONG_MASK));
-
-        return hashCode * signum;
+        return ArraysSupport.vectorizedHashCode(mag, 0, mag.length, 0,
+                ArraysSupport.T_INT) * signum;
     }
 
     /**
diff --git a/test/jdk/java/math/BigInteger/HashCode.java b/test/jdk/java/math/BigInteger/HashCode.java
new file mode 100644
index 00000000000..ecd3b8f368e
--- /dev/null
+++ b/test/jdk/java/math/BigInteger/HashCode.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.math.BigInteger;
+
+/*
+ * @test
+ * @bug 8310813
+ * @summary Check hashCode implementation against reference values
+ */
+public class HashCode {
+
+    // This test guards against inadvertent changes to BigInteger.hashCode,
+    // by checking generated hashCode values against reference values
+    // captured immediately before 8310813
+
+    public static void main(String[] args) {
+        equals(          0, BigInteger.ZERO);
+        equals(          1, BigInteger.ONE);
+        equals(          2, BigInteger.TWO);
+        equals(         10, BigInteger.TEN);
+        equals(       -128, BigInteger.valueOf(Byte.MIN_VALUE));
+        equals(        127, BigInteger.valueOf(Byte.MAX_VALUE));
+        equals(     -32768, BigInteger.valueOf(Short.MIN_VALUE));
+        equals(      32767, BigInteger.valueOf(Short.MAX_VALUE));
+        equals(          0, BigInteger.valueOf(Character.MIN_VALUE));
+        equals(      65535, BigInteger.valueOf(Character.MAX_VALUE));
+        equals(-2147483648, BigInteger.valueOf(Integer.MIN_VALUE));
+        equals( 2147483647, BigInteger.valueOf(Integer.MAX_VALUE));
+        equals(-2147483648, BigInteger.valueOf(Long.MIN_VALUE));
+        equals( 2147483616, BigInteger.valueOf(Long.MAX_VALUE));
+        equals(         -1, BigInteger.valueOf(-1));
+
+        // a 37-byte negative number, generated at random
+        equals( 1428257188, new BigInteger("""
+                -5573526435790097067262357965922443376770234990700620666883\
+                2705705469477701887396205062479"""));
+        // a 123-byte positive number, generated at random
+        equals( -412503667, new BigInteger("""
+                13093241912251296135908856604398494061635394768699286753760\
+                22827291528069076557720973813183142494646514532475660126948\
+                43316474303725664231917408569680292008962577772928370936861\
+                12952691245923210726443405774197400117701581498597123145452\
+                15111774818054200162634242662445757757255702394598235971294\
+                50"""));
+    }
+
+    private static void equals(int expectedHashCode, BigInteger i) {
+        int actualHashCode = i.hashCode();
+        if (expectedHashCode != actualHashCode)
+            throw new AssertionError("%s: expectedHashCode=%s, actual=%s"
+                    .formatted(i, expectedHashCode, actualHashCode));
+    }
+}
diff --git a/test/micro/org/openjdk/bench/java/math/BigIntegerCompareTo.java b/test/micro/org/openjdk/bench/java/math/BigIntegerCompareTo.java
new file mode 100644
index 00000000000..e66ef1ec4d8
--- /dev/null
+++ b/test/micro/org/openjdk/bench/java/math/BigIntegerCompareTo.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package org.openjdk.bench.java.math;
+
+import java.math.BigInteger;
+import java.util.Arrays;
+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.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Param;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.Warmup;
+import org.openjdk.jmh.infra.Blackhole;
+
+@BenchmarkMode(Mode.AverageTime)
+@OutputTimeUnit(TimeUnit.NANOSECONDS)
+@State(Scope.Thread)
+@Warmup(iterations = 3, time = 5)
+@Measurement(iterations = 3, time = 5)
+@Fork(value = 3)
+public class BigIntegerCompareTo {
+
+    public enum Group {S, M, L}
+
+    @Param({"S", "M", "L"})
+    private Group group;
+
+    private static final int MAX_LENGTH = Arrays.stream(Group.values())
+            .mapToInt(p -> getNumbersOfBits(p).length)
+            .max()
+            .getAsInt();
+
+    private BigInteger[] numbers;
+
+    @Setup
+    public void setup() {
+        int[] nBits = getNumbersOfBits(group);
+        numbers = new BigInteger[2 * MAX_LENGTH];
+        for (int i = 0; i < MAX_LENGTH; i++) {
+            var p = Shared.createPair(nBits[i % nBits.length]);
+            numbers[2 * i] = p.x();
+            numbers[2 * i + 1] = p.y();
+        }
+    }
+
+    private static int[] getNumbersOfBits(Group p) {
+        // the below arrays were derived from stats gathered from running tests in
+        // the security area, which is the biggest client of BigInteger in JDK
+        return switch (p) {
+            case S -> new int[]{0, 1, 2, 11, 12, 13, 14, 15, 16, 17, 18, 19};
+            case M -> new int[]{255, 256, 512};
+            case L -> new int[]{1023, 1024, 1534, 1535, 1536};
+        };
+    }
+
+    @Benchmark
+    public void testCompareTo(Blackhole bh) {
+        for (int i = 0; i < numbers.length; i += 2)
+            bh.consume(numbers[i].compareTo(numbers[i + 1]));
+    }
+}
diff --git a/test/micro/org/openjdk/bench/java/math/BigIntegerEquals.java b/test/micro/org/openjdk/bench/java/math/BigIntegerEquals.java
new file mode 100644
index 00000000000..5be2471ea04
--- /dev/null
+++ b/test/micro/org/openjdk/bench/java/math/BigIntegerEquals.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package org.openjdk.bench.java.math;
+
+import java.math.BigInteger;
+import java.util.Arrays;
+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.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Param;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.Warmup;
+import org.openjdk.jmh.infra.Blackhole;
+
+@BenchmarkMode(Mode.AverageTime)
+@OutputTimeUnit(TimeUnit.NANOSECONDS)
+@State(Scope.Thread)
+@Warmup(iterations = 3, time = 5)
+@Measurement(iterations = 3, time = 5)
+@Fork(value = 3)
+public class BigIntegerEquals {
+
+    public enum Group {S, M, L}
+
+    @Param({"S", "M", "L"})
+    private Group group;
+
+    private static final int MAX_LENGTH = Arrays.stream(Group.values())
+            .mapToInt(p -> getNumbersOfBits(p).length)
+            .max()
+            .getAsInt();
+
+    private BigInteger[] numbers;
+
+    @Setup
+    public void setup() {
+        int[] nBits = getNumbersOfBits(group);
+        numbers = new BigInteger[2 * MAX_LENGTH];
+        for (int i = 0; i < MAX_LENGTH; i++) {
+            var p = Shared.createPair(nBits[i % nBits.length]);
+            numbers[2 * i] = p.x();
+            numbers[2 * i + 1] = p.y();
+        }
+    }
+
+    private static int[] getNumbersOfBits(Group p) {
+        // the below arrays were derived from stats gathered from running tests in
+        // the security area, which is the biggest client of BigInteger in JDK
+        return switch (p) {
+            case S -> new int[]{1, 46};
+            case M -> new int[]{129, 130, 251, 252, 253, 254, 255, 256};
+            case L -> new int[]{382, 383, 384, 445, 446, 447, 448, 519, 520, 521};
+        };
+    }
+
+    @Benchmark
+    public void testEquals(Blackhole bh) {
+        for (int i = 0; i < numbers.length; i += 2)
+            bh.consume(numbers[i].equals(numbers[i + 1]));
+    }
+}
diff --git a/test/micro/org/openjdk/bench/java/math/BigIntegerHashCode.java b/test/micro/org/openjdk/bench/java/math/BigIntegerHashCode.java
new file mode 100644
index 00000000000..92ed1b88c28
--- /dev/null
+++ b/test/micro/org/openjdk/bench/java/math/BigIntegerHashCode.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package org.openjdk.bench.java.math;
+
+import java.math.BigInteger;
+import java.util.Arrays;
+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.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Param;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.Warmup;
+import org.openjdk.jmh.infra.Blackhole;
+
+@BenchmarkMode(Mode.AverageTime)
+@OutputTimeUnit(TimeUnit.NANOSECONDS)
+@State(Scope.Thread)
+@Warmup(iterations = 3, time = 5)
+@Measurement(iterations = 3, time = 5)
+@Fork(value = 3)
+public class BigIntegerHashCode {
+
+    public enum Group {S, M, L}
+
+    @Param({"S", "M", "L"})
+    private Group group;
+
+    private static final int MAX_LENGTH = Arrays.stream(Group.values())
+            .mapToInt(p -> getNumbersOfBits(p).length)
+            .max()
+            .getAsInt();
+
+    private BigInteger[] numbers;
+
+    @Setup
+    public void setup() {
+        int[] nBits = getNumbersOfBits(group);
+        numbers = new BigInteger[MAX_LENGTH];
+        for (int i = 0; i < MAX_LENGTH; i++) {
+            numbers[i] = Shared.createSingle(nBits[i % nBits.length]);
+        }
+    }
+
+    private static int[] getNumbersOfBits(Group p) {
+        // the below arrays were derived from stats gathered from running tests in
+        // the security area, which is the biggest client of BigInteger in JDK
+        return switch (p) {
+            case S -> new int[]{2, 7, 13, 64};
+            case M -> new int[]{256, 384, 511, 512, 521, 767, 768};
+            case L -> new int[]{1024, 1025, 2047, 2048, 2049, 3072, 4096, 5120, 6144};
+        };
+    }
+
+    @Benchmark
+    public void testHashCode(Blackhole bh) {
+        for (var n : numbers)
+            bh.consume(n.hashCode());
+    }
+}
diff --git a/test/micro/org/openjdk/bench/java/math/Shared.java b/test/micro/org/openjdk/bench/java/math/Shared.java
new file mode 100644
index 00000000000..2f996d052c5
--- /dev/null
+++ b/test/micro/org/openjdk/bench/java/math/Shared.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package org.openjdk.bench.java.math;
+
+import java.math.BigInteger;
+import java.util.Random;
+
+///////////////////////////////////////////////////////////////////////////////
+// THIS IS NOT A BENCHMARK
+///////////////////////////////////////////////////////////////////////////////
+public final class Shared {
+
+    // General note
+    // ============
+    //
+    // Isn't there a simple way to get a BigInteger of the specified number
+    // of bits of magnitude? It does not seem like it.
+    //
+    // We cannot create a BigInteger of the specified number of bytes,
+    // directly and *cheaply*. This constructor does not do what you
+    // might think it does:
+    //
+    //      BigInteger(int numBits, Random rnd)
+    //
+    //  The only real direct option we have is this constructor:
+    //
+    //      BigInteger(int bitLength, int certainty, Random rnd)
+    //
+    //  But even with certainty == 0, it is not cheap. So, create the
+    //  number with the closest number of bytes and then shift right
+    //  the excess bits.
+
+    private Shared() {
+        throw new AssertionError("This is a utility class");
+    }
+
+    //
+    // Creates a pair of same sign numbers x and y that minimally differ in
+    // magnitude.
+    //
+    // More formally: x.bitLength() == nBits and x.signum() == y.signum()
+    // and either
+    //
+    //   * y.bitLength() == nBits, and
+    //   * x.testBit(0) != y.testBit(0)
+    //
+    // or
+    //
+    //   * y.bitLength() == nBits + 1
+    //
+    // By construction, such numbers are unequal to each other, but the
+    // difference in magnitude is minimal. That way, the comparison
+    // methods, such as equals and compareTo, are forced to examine
+    // the _complete_ number representation.
+    //
+    // Assumptions on BigInteger mechanics
+    // ===================================
+    //
+    // 1. bigLength() is not consulted with for short-circuiting; if it is,
+    //    then we have a problem with nBits={0,1}
+    // 2. new BigInteger(0, new byte[]{0}) and new BigInteger(1, new byte[]{1})
+    //    are not canonicalized to BigInteger.ZERO and BigInteger.ONE,
+    //    respectively; if they are, then internal optimizations might be
+    //    possible (BigInteger is not exactly a value-based class).
+    // 3. Comparison and equality are checked from the most significant bit
+    //    to the least significant bit, not the other way around (for
+    //    comparison it seems natural, but not for equality). If any
+    //    of those are checked in the opposite direction, then the check
+    //    might short-circuit.
+    //
+    public static Pair createPair(int nBits) {
+        if (nBits < 0) {
+            throw new IllegalArgumentException(String.valueOf(nBits));
+        } else if (nBits == 0) {
+            var zero = new BigInteger(nBits, new byte[0]);
+            var one = new BigInteger(/* positive */ 1, new byte[]{1});
+            return new Pair(zero, one);
+        } else if (nBits == 1) {
+            var one = new BigInteger(/* positive */ 1, new byte[]{1});
+            var two = new BigInteger(/* positive */ 1, new byte[]{2});
+            return new Pair(one, two);
+        }
+        int nBytes = (nBits + 7) / 8;
+        var r = new Random();
+        var bytes = new byte[nBytes];
+        r.nextBytes(bytes);
+        // Create a BigInteger of the exact bit length by:
+        // 1. ensuring that the most significant bit is set so that
+        //    no leading zeros are truncated, and
+        // 2. explicitly specifying signum, so it's not calculated from
+        //    the passed bytes, which must represent magnitude only
+        bytes[0] |= (byte) 0b1000_0000;
+        var x = new BigInteger(/* positive */ 1, bytes)
+                .shiftRight(nBytes * 8 - nBits);
+        var y = x.flipBit(0);
+        // do not rely on the assert statement in benchmark
+        if (x.bitLength() != nBits)
+            throw new AssertionError(x.bitLength() + ", " + nBits);
+        return new Pair(x, y);
+    }
+
+    public record Pair(BigInteger x, BigInteger y) {
+        public Pair {
+            if (x.signum() == -y.signum()) // if the pair comprises positive and negative
+                throw new IllegalArgumentException("x.signum()=" + x.signum()
+                        + ", y=signum()=" + y.signum());
+            if (y.bitLength() - x.bitLength() > 1)
+                throw new IllegalArgumentException("x.bitLength()=" + x.bitLength()
+                        + ", y.bitLength()=" + y.bitLength());
+        }
+    }
+
+    public static BigInteger createSingle(int nBits) {
+        if (nBits < 0) {
+            throw new IllegalArgumentException(String.valueOf(nBits));
+        }
+        if (nBits == 0) {
+            return new BigInteger(nBits, new byte[0]);
+        }
+        int nBytes = (nBits + 7) / 8;
+        var r = new Random();
+        var bytes = new byte[nBytes];
+        r.nextBytes(bytes);
+        // Create a BigInteger of the exact bit length by:
+        // 1. ensuring that the most significant bit is set so that
+        //    no leading zeros are truncated, and
+        // 2. explicitly specifying signum, so it's not calculated from
+        //    the passed bytes, which must represent magnitude only
+        bytes[0] |= (byte) 0b1000_0000;
+        var x = new BigInteger(/* positive */ 1, bytes)
+                .shiftRight(nBytes * 8 - nBits);
+        if (x.bitLength() != nBits)
+            throw new AssertionError(x.bitLength() + ", " + nBits);
+        return x;
+    }
+}