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; + } +}