diff --git a/make/modules/java.base/gensrc/GensrcValueClasses.gmk b/make/modules/java.base/gensrc/GensrcValueClasses.gmk index 6a5b6864b67..21725080dcc 100644 --- a/make/modules/java.base/gensrc/GensrcValueClasses.gmk +++ b/make/modules/java.base/gensrc/GensrcValueClasses.gmk @@ -1,5 +1,5 @@ # -# Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2023, 2026, 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 @@ -37,6 +37,8 @@ java.base-VALUE_CLASS-REPLACEMENTS := \ java/lang/Character.java \ java/lang/Number.java \ java/lang/Record.java \ + java/math/UInt64.java \ + java/math/UInt128.java \ java/util/Optional.java \ java/util/OptionalInt.java \ java/util/OptionalLong.java \ diff --git a/src/java.base/share/classes/java/math/UInt128.java b/src/java.base/share/classes/java/math/UInt128.java new file mode 100644 index 00000000000..5dff27cd0ba --- /dev/null +++ b/src/java.base/share/classes/java/math/UInt128.java @@ -0,0 +1,1117 @@ +/* + * Copyright (c) 2026, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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 java.math; + +import jdk.internal.vm.annotation.Stable; + +import static java.lang.Character.*; +import static java.lang.Long.divideUnsigned; +import static java.lang.Long.toUnsignedString; +import static java.math.UInt64.*; + +/** + * Immutable unsigned 128-bit integers ({@link #SIZE} = 128). + *

+ * All operations are performed modulo 2{@link #SIZE}, + * unless specified otherwise. + *

+ * WARNING: + *

+ */ +// TODO: once in the platform, declare value class and remove useless final +@jdk.internal.MigratedValueClass +@jdk.internal.ValueBased +public final class UInt128 implements Comparable { + + @jdk.internal.MigratedValueClass + @jdk.internal.ValueBased + private record Quo64Rem64(long quo, long rem) { + + QuoRem toQuoRem() { + return new QuoRem(new UInt128(quo), new UInt128(rem)); + } + + } + + @jdk.internal.MigratedValueClass + @jdk.internal.ValueBased + private record Quo64Rem128(long quo, UInt128 rem) { + + QuoRem toQuoRem() { + return new QuoRem(new UInt128(quo), rem); + } + + } + + @jdk.internal.MigratedValueClass + @jdk.internal.ValueBased + private record Quo128Rem64(UInt128 quo, long rem) { + + QuoRem toQuoRem() { + return new QuoRem(quo, new UInt128(rem)); + } + + } + + /** + * A carrier for the quotient and remainder of a division. + * + * @param quo the quotient + * @param rem the remainder + */ + @jdk.internal.MigratedValueClass + @jdk.internal.ValueBased + public record QuoRem(UInt128 quo, UInt128 rem) { + + /** + * As a pair of hexadecimal strings. + * + * @return the result described above. + */ + public String toHexString() { + return "QuoRem[quo=0x" + quo.toHexString() + + ", rem=0x" + rem.toHexString() + + "]"; + } + + } + + /** + * log2 {@link #SIZE}. + */ + public static final int LOG2_SIZE = 7; + + /** + * The number of bits used to represent a value of this class in binary form. + */ + public static final int SIZE = 1 << LOG2_SIZE; + + /** + * max{e ∈ ℕ : 10e ≤ 2{@link #SIZE}} + */ + public static final int DEC_SIZE = 38; + + // TODO: once in the platform, add it to the AOT cache + @Stable + private static final UInt128[] POW_10; // powers of 10, up to 10^DEC_SIZE + + static { + POW_10 = new UInt128[DEC_SIZE + 1]; + POW_10[0] = new UInt128(1L); + for (int i = 1; i < POW_10.length; ++i) { + POW_10[i] = multiply(POW_10[i - 1], 10); + } + } + + /* The value is c0 + c1 2^64 */ + final long c0; + final long c1; + + /** + * Constructs the instance with value {@code c0} + {@code c1}⋅264. + * + * @param c0 the low bits + * @param c1 the high bits + */ + public UInt128(long c0, long c1) { + this.c0 = c0; + this.c1 = c1; + } + + /** + * Constructs the instance with value {@code c0}. + * + * @param c0 the low bits + */ + public UInt128(long c0) { + this(c0, 0); + } + + /*** + * {@return the low bits} + */ + public long c0() { + return c0; + } + + /** + * {@return the high bits} + */ + public long c1() { + return c1; + } + + /** + * Returns the instance representing {@code v}. + * + * @param v must meet 0 ≤ {@code v} < 2{@link #SIZE}. + * @throws IllegalArgumentException if {@code v} is out of range. + */ + public UInt128(BigInteger v) { + if (v.signum() < 0 || v.bitLength() > SIZE) { + throw new IllegalArgumentException("the argument is out of range"); + } + byte[] va = v.toByteArray(); + int from = va[0] != 0 ? 0 : 1; + int len = va.length - from; + byte[] ba = new byte[SIZE / Byte.SIZE]; + System.arraycopy(va, from, ba, ba.length - len, len); + this( + UInt64.getLong(ba, 1 * 8), + UInt64.getLong(ba, 0 * 8)); + } + + /** + * Returns the instance representing the unsigned integer in + * radix {@code radix} of the digits in the portion of {@code s} + * between index {@code begin} (included) and {@code end} (excluded). + *

+ * The integer value v must meet 0 ≤ v < 2{@link #SIZE}. + *

+ * Each character that appears in the portion must be ASCII and must + * represent a digit in the given radix, or must be the {@code separator} + * (if ≥ 0). + * The first and last characters of the input must be valid digits. + *

+ * The characters in the portion are read once, from lower to higher indices, + * until either an exception is thrown or the end of the portion is reached. + * + * @param s the input + * @param begin the starting index, included + * @param end the ending index, excluded + * @param radix the radix + * @param separator the separator character, negative if none + * @return the result described above. + * @throws IllegalArgumentException if radix is not in the interval + * [{@link Character#MIN_RADIX}, {@link Character#MAX_RADIX}]. + * Or if the characters in the portion are neither valid ASCII digits, + * nor separators. + * @throws ArithmeticException if the integer value in the portion overflows. + * @throws IndexOutOfBoundsException if any index used to access a + * character in the given portion falls outside {@code s}. + */ + public static UInt128 parse(CharSequence s, int begin, int end, + int radix, int separator) { + if (MIN_RADIX > radix || radix > MAX_RADIX) { + throw new IllegalArgumentException("radix must be in the interval " + + "[" + MIN_RADIX + ", " + MAX_RADIX + "]"); + } + return parse(s, begin, end, begin, radix, separator, -1); + } + + /* + * Parses the input s between i (included) and end (excluded). + * Assumes + * begin ≤ i ≤ begin + 2 + * begin = i iff c < 0 + * begin < i iff c = s[i-1] + * begin = i - 2 iff s[begin] = '0' + */ + private static UInt128 parse(CharSequence s, int begin, int end, + int i, int radix, int separator, int c) { + if (c < 0) { + if (i >= end) { + throw new IndexOutOfBoundsException(); + } + c = s.charAt(i++); + } + /* c = s[i-1] */ + if (c == separator && i - begin != 2) { + throw new IllegalArgumentException("leading separator"); + } + /* + * This if statement, while logically unnecessary, prevents tons + * of computations for a huge prefix of useless zeros and separators. + */ + if (c == '0' || c == separator && i - begin == 2) { + while (i < end + && ((c = s.charAt(i++)) == '0' || c == separator)); //empty body + } + UInt128 v = c != separator ? new UInt128(asDigit(c, radix)) : _0(); + while (i < end) { + c = s.charAt(i++); + if (c != separator) { + v = addExact(multiplyExact(v, radix), asDigit(c, radix)); + } + } + if (c == separator) { + throw new IllegalArgumentException("trailing separator"); + } + return v; + } + + /** + * Returns the instance representing the unsigned integer in the portion of {@code s} + * between index {@code begin} (included) and {@code end} (excluded). + * The syntax follows the Java Language Specification for integer literals, + * albeit with no suffix. + *

+ * The characters in the portion are read once, from lower to higher indices, + * until either an exception is thrown or the end of the portion is reached. + *

+ * The integer value v must meet 0 ≤ v < 2{@link #SIZE}. + * + * @param s the input + * @param begin the starting index, included + * @param end the ending index, excluded + * @return the result described above. + * @jls 3.10.1 Integer Literals + * @throws IllegalArgumentException if radix is not in the interval [2, 36]. + * Or if the character in the portion are not ASCII and not digits. + * @throws ArithmeticException if the integer value in the portion overflows. + * @throws IndexOutOfBoundsException if any index used to access a + * character in the given portion falls outside {@code s}. + */ + public static UInt128 parseLiteral(CharSequence s, int begin, int end) { + if (begin >= end) { + throw new IndexOutOfBoundsException(); + } + int i = begin; + int c = s.charAt(i++); + if (c != '0' || i == end) { + return parse(s, begin, end, i, 10, '_', c); + } + c = s.charAt(i++); + int lc = c | 0b10_0000; // to lower case + if (lc == 'x') { + return parse(s, i, end, i, 16, '_', -1); + } + if (lc == 'b') { + return parse(s, i, end, i, 2, '_', -1); + } + return parse(s, begin, end, i, 8, '_', c); + } + + private static int asDigit(int c, int radix) { + int d; + if (c < 0x80 && (d = digit(c, radix)) >= 0) { + return d; + } + throw new IllegalArgumentException("illegal digit for radix=" + radix); + } + + /** + * Returns a {@link BigInteger} with the same value as {@code this}. + * + * @return the result described above. + */ + public BigInteger toBigInteger() { + byte[] ba = new byte[SIZE / Byte.SIZE]; + UInt64.putLong(ba, 0, c1); + UInt64.putLong(ba, 8, c0); + return new BigInteger(1, ba); + } + + /** + * Returns a {@link String} representing {@code this}. + * + * @return the result described above. + */ + @Override + public String toString() { + UInt128 pow10 = pow10(UInt64.DEC_SIZE); + QuoRem qr = divideAndRemainder(this, pow10); + long d0 = qr.rem.c0; // qr.rem < 10^19 < 2^64 + qr = divideAndRemainder(qr.quo, pow10); + long d1 = qr.rem.c0; // qr.rem < 10^19 < 2^64 + long d2 = qr.quo.c0; // qr.quo < 10 + return d2 != 0 + ? toUnsignedString(d2) + toPadString(d1) + toPadString(d0) + : d1 != 0 + ? toUnsignedString(d1) + toPadString(d0) + : toUnsignedString(d0); + } + + private static String toPadString(long v) { + String s = toUnsignedString(v); + return s.length() == UInt64.DEC_SIZE + ? s // likely case, fast path + : "0".repeat(UInt64.DEC_SIZE - s.length()) + s; + } + + /** + * Returns a {@link String} representing {@code this} in hexadecimal notation. + * + * @return the result described above. + */ + public String toHexString() { + return c1 != 0 + ? Long.toHexString(c1) + toPadHexString(c0) + : Long.toHexString(c0); + } + + private static String toPadHexString(long v) { + String s = Long.toHexString(v); + return s.length() == UInt64.SIZE / 4 // likely case, fast path + ? s + : "0".repeat(UInt64.SIZE / 4 - s.length()) + s; + } + + /** + * {@return the value 0} + */ + public static UInt128 _0() { + return new UInt128(0); + } + + /** + * {@return the value 1} + */ + public static UInt128 _1() { + return new UInt128(1); + } + + /** + * Returns the value 2{@link #SIZE} - 1. + * + * @return the result described above. + */ + public static UInt128 MAX_VALUE() { + return new UInt128(-1, -1); + } + + /** + * Returns 10{@code e}. + * + * @param e must meet 0 ≤ {@code e} ≤ {@link #DEC_SIZE}. + * @return the result described above. + * @throws RuntimeException if {@code e} is out of range. + */ + public static UInt128 pow10(int e) { + return POW_10[e]; + } + + /** + * Returns {@code x} << {@code sh}. + * + * @param x the parameter + * @param sh must meet 0 ≤ {@code sh} < 64. + * @return the result described above. + */ + static UInt128 shiftLeftSmall(UInt128 x, int sh) { + /* Branchless code. */ + return new UInt128( + x.c0 << sh, + x.c1 << sh | x.c0 >>> 63 - sh >>> 1); + } + + /** + * Returns {@code x} << {@link UInt64#S S} | {@code y} + * @param x the 1st parameter + * @param y the 2nd parameter + * @return the result described above. + */ + private static UInt128 shiftLeftSOr(UInt128 x, long y) { + return new UInt128( + x.c0 << S | y, + x.c1 << S | x.c0 >>> S); + } + + /** + * Returns {@code x} >>> {@code sh}. + * + * @param x the paramter + * @param sh only the 6 least significant bits are relevant. + * @return the result described above. + */ + static UInt128 shiftRightSmall(UInt128 x, int sh) { + /* Branchless code. */ + return new UInt128( + x.c0 >>> sh | x.c1 << 63 - sh << 1, + x.c1 >>> sh); + } + + /** + * Returns {@code x} >>> {@link UInt64#S S}. + * + * @param x the parameter + * @return the result described above. + */ + private static UInt128 shiftRightS(UInt128 x) { + return new UInt128( + x.c0 >>> S | x.c1 << S, + x.c1 >>> S); + } + + /** + * Returns -{@code x}. + * + * @param x the parameter + * @return the result described above. + */ + public static UInt128 negate(UInt128 x) { + long z0, z1; + z0 = -x.c0; + z1 = -x.c1 - (x.c0 != 0 ? 1L : 0L); + return new UInt128(z0, z1); + } + + /** + * Returns {@code x} + {@code y}. + * + * @param x the 1st parameter + * @param y the 2nd parameter + * @return the result described above. + */ + public static UInt128 add(UInt128 x, long y) { + long z0, z1; + z0 = x.c0 + y; + z1 = x.c1 + carry(z0, y); + return new UInt128(z0, z1); + } + + /** + * Returns {@code x} + {@code y}, throwing on overflow. + * + * @param x the 1st parameter + * @param y the 2nd parameter + * @return the result described above. + * @throws ArithmeticException on overflow. + */ + public static UInt128 addExact(UInt128 x, long y) { + long z0, z1; + z0 = x.c0 + y; + z1 = UInt64.addExact(x.c1, carry(z0, y)); + return new UInt128(z0, z1); + } + + /** + * Returns {@code x} + {@code y}. + * + * @param x the 1st parameter + * @param y the 2nd parameter + * @return the result described above. + */ + public static UInt128 add(UInt128 x, UInt128 y) { + long z0, z1; + z0 = x.c0 + y.c0; + z1 = x.c1 + y.c1 + carry(z0, y.c0); + return new UInt128(z0, z1); + } + + /** + * Returns {@code x} - {@code y}. + * + * @param x the 1st parameter + * @param y the 2nd parameter + * @return the result described above. + */ + public static UInt128 subtract(UInt128 x, long y) { + long z0, z1; + z0 = x.c0 - y; + z1 = x.c1 - carry(x.c0, y); + return new UInt128(z0, z1); + } + + /** + * Returns {@code x} - {@code y}. + * + * @param x the 1st parameter + * @param y the 2nd parameter + * @return the result described above. + */ + public static UInt128 subtract(UInt128 x, UInt128 y) { + long z0, z1; + z0 = x.c0 - y.c0; + z1 = x.c1 - y.c1 - carry(x.c0, y.c0); + return new UInt128(z0, z1); + } + + /** + * Performs an integer division. + * + * @param x the dividend. + * @param y the divisor. + * @return the quotient q = ⌊{@code x} / {@code y}⌋. + * @throws ArithmeticException when the divisor is 0. + */ + public static UInt128 divide(UInt128 x, UInt128 y) { + return divideAndRemainder(x, y).quo; + } + + /** + * Performs an unsigned integer division. + * + * @param x the dividend. + * @param y the divisor. + * @return the remainder {@code x} - {@link #divide divide(x, y)} {@code y}. + * @throws ArithmeticException when the divisor is 0. + */ + public static UInt128 remainder(UInt128 x, UInt128 y) { + return divideAndRemainder(x, y).rem; + } + + /** + * Performs an unsigned integer division with remainder. + * + * @param x the dividend. + * @param y the divisor. + * @return the quotient q = ⌊{@code x} / {@code y}⌋ and its + * remainder {@code x} - q {@code y}, packed as a {@link QuoRem QuoRem}. + * @throws ArithmeticException when the divisor is 0. + */ + public static QuoRem divideAndRemainder(UInt128 x, UInt128 y) { + return (x.c1 | y.c1) == 0 + ? divideAndRemainder64x64(x.c0, y.c0).toQuoRem() // x, y < 2^64 + : lessThan(x, y) + ? new QuoRem(_0(), x) // x < y + : y.c1 != 0 + ? UInt192.divideAndRemainder(x, y).toQuoRem() // y ≥ 2^64 + : (UInt64.lessThan(y.c0, B) + ? divideAndRemainder128x32(x, y.c0) // y < 2^32 + : divideAndRemainder128x64(x, y.c0)).toQuoRem(); // 2^32 ≤ y < 2^64 + } + + /** + * Returns the full product {@code x}⋅{@code y}. + * + * @param x the 1st parameter + * @param y the 2nd parameter + * @return the result described above. + */ + public static UInt128 multiply(long x, long y) { + return new UInt128(x * y, Math.unsignedMultiplyHigh(x, y)); + } + + /** + * Returns {@code x}⋅{@code y}. + * + * @param x the 1st parameter + * @param y the 2nd parameter + * @return the result described above. + */ + public static UInt128 multiply(UInt128 x, long y) { + long z0, z1, t0; + z0 = x.c0 * y; + z1 = Math.unsignedMultiplyHigh(x.c0, y); + t0 = x.c1 * y; + z1 += t0; + return new UInt128(z0, z1); + } + + /** + * Returns {@code x}⋅{@code y}, throwing on overflow. + * + * @param x the 1st parameter + * @param y the 2nd parameter + * @return the result described above. + * @throws ArithmeticException on overflow. + */ + public static UInt128 multiplyExact(UInt128 x, long y) { + long z0, z1, t0; + z0 = x.c0 * y; + z1 = Math.unsignedMultiplyHigh(x.c0, y); + t0 = Math.unsignedMultiplyExact(x.c1, y); + z1 = UInt64.addExact(z1, t0); + return new UInt128(z0, z1); + } + + /** + * Returns {@code x}⋅{@code y}. + * + * @param x the 1st parameter + * @param y the 2nd parameter + * @return the result described above. + */ + public static UInt128 multiply(UInt128 x, UInt128 y) { + /* + * The most significant digit of a 2 by 1 digits product cannot be BB - 1, + * where BB is the radix of the multidigit representation (here BB = 2^64). + */ + long z0, z1, t0; + + z0 = x.c0 * y.c0; + z1 = Math.unsignedMultiplyHigh(x.c0, y.c0); + + t0 = x.c1 * y.c0; + z1 += t0; + t0 = x.c0 * y.c1; + z1 += t0; + return new UInt128(z0, z1); + } + + /** + * Performs an integer division with remainder. + * + * @param x the dividend. + * @param y the divisor. + * @return the quotient ⌊{@code x} / {@code y}⌋ and its remainder. + */ + private static Quo64Rem64 divideAndRemainder64x64(long x, long y) { + long q = divideUnsigned(x, y); + long r = x - q * y; + return new Quo64Rem64(q, r); + } + + /** + * Performs an integer division with remainder. + * + * @param x the dividend. + * @param y the divisor. + * Must meet {@code y} < 232 (not checked). + * @return the quotient q = ⌊{@code x} / {@code y}⌋ + * and its remainder r = {@code x} - q {@code y}. + */ + private static Quo128Rem64 divideAndRemainder128x32(UInt128 x, long y) { + /* + * This computation can be performed by divideAndRemainder128x64(), + * but when the divisor consist of just one B-based digit, like here, + * it's even simpler. + */ + long u = x.c1; + long q23 = divideUnsigned(u, y); // q23 < B^2 + u = u - q23 * y << S | x.c0 >>> S; // u < y < B + long q1 = divideUnsigned(u, y); // q1 < B + u = u - q1 * y << S | x.c0 & MASK; // u < y < B + long q0 = divideUnsigned(u, y); // q0 < B + u = u - q0 * y; // u < y < B + long q01 = q1 << S | q0; + return new Quo128Rem64(new UInt128(q01, q23), u); + } + + /** + * Performs an integer division with remainder. + * + * @param x the dividend. + * @param y the divisor. + * @return the quotient q = ⌊{@code x} / {@code y}⌋ + * and its remainder r = {@code x} - q {@code y}. + */ + private static Quo128Rem64 divideAndRemainder128x64(UInt128 x, long y) { + /* Multidigit integer division, as described by Knuth. */ + long q1 = divideUnsigned(x.c1, y); + x = new UInt128(x.c0, x.c1 - q1 * y); // x < y B + int sh = Long.numberOfLeadingZeros(y); + y <<= sh; // B^2 / 2 ≤ y < B^2 + long yh0 = y & MASK; + long yh1 = y >>> S; + x = shiftLeftSmall(x, sh); + long x0 = x.c0 & MASK; + + /* + * Performs the 1st 3-by-2 B-based digits division, and therefore + * never needs the last, extremely rare correcton described by Knuth. + * The quotient meets q < B, the final remainder fits in 64 bits, + * so can be computed in 64 bit arithmetic. + * + * Both x and y are also viewed as integers in radix B. + * Since x < y B implies x < B^3, we get + * x = (xh0 + xh1 B) + xh2 B^2 + * y = (yh0 + yh1 B), yh1 ≥ 2^31 + */ + x = shiftRightS(x); // x < B^3 + long xh0 = x.c0 & MASK; + long xh12 = x.c0 >>> S | x.c1 << S; // xh12 = ⌊x / B⌋ ≤ x / B < y + long q = divideUnsigned(xh12, yh1); // q ≤ B + 1 + long r = xh12 - q * yh1; // q yh1 ≤ (B + 1) (B - 1) < B^2, r < yh1 < B + if (UInt64.lessThan(r << S | xh0, q * yh0)) { // q yh0 < B^2 + q -= 1; + r += yh1; // r < 2 yh1 < 2 B + if (r < B && UInt64.lessThan(r << S | xh0, q * yh0)) { + q -= 1; + } + } + long q0 = q; + + /* Performs the 2nd 3-by-2 B-based digits division. */ + x = shiftLeftSOr(new UInt128(x.c0 - q * y), x0); + xh0 = x.c0 & MASK; + xh12 = x.c0 >>> S | x.c1 << S; + q = divideUnsigned(xh12, yh1); + r = xh12 - q * yh1; + if (UInt64.lessThan(r << S | xh0, q * yh0)) { + q -= 1; + r += yh1; + if (r < B && UInt64.lessThan(r << S | xh0, q * yh0)) { + q -= 1; + } + } + return new Quo128Rem64(new UInt128(q0 << S | q, q1), x.c0 - q * y >>> sh); + } + + /** + * Returns the bit length of {@code x}. + * + * @param x the argument. + * @return the result described above. + */ + public static int len2(UInt128 x) { + int sz = x.c1 != 0 ? SIZE : UInt64.SIZE; + long c = x.c1 != 0 ? x.c1 : x.c0; + return sz - Long.numberOfLeadingZeros(c); + } + + /** + * Returns min{e ∈ ℕ : {@code x} < 10e}. + * + * @param x the parameter + * @return the value as described above. + */ + public static int len10(UInt128 x) { + int e = UInt64.flog10pow2(len2(x)); + return lessThan(x, POW_10[e]) ? e : e + 1; + } + + /** + * Returns whether {@code x} = 0. + * + * @param x the parameter + * @return the result described above. + */ + public static boolean isZero(UInt128 x) { + return (x.c1 | x.c0) == 0; + } + + /** + * Returns whether {@code x} = {@code y}. + * + * @param x the 1st parameter + * @param y the 2nd parameter + * @return the result described above. + */ + public static boolean equal(UInt128 x, UInt128 y) { + return x.c1 == y.c1 & x.c0 == y.c0; + } + + /** + * Returns whether {@code x} < {@code y}. + * + * @param x the 1st parameter + * @param y the 2nd parameter + * @return the result described above. + */ + public static boolean lessThan(UInt128 x, UInt128 y) { + return x.c1 != y.c1 + ? UInt64.lessThan(x.c1, y.c1) + : UInt64.lessThan(x.c0, y.c0); + } + + /** + * Returns whether {@code x} ≤ {@code y}. + * + * @param x the 1st parameter + * @param y the 2nd parameter + * @return the result described above. + */ + public static boolean lessEqual(UInt128 x, UInt128 y) { + return !lessThan(y, x); + } + + /** + * Returns whether {@code x} > {@code y}. + * + * @param x the 1st parameter + * @param y the 2nd parameter + * @return the result described above. + */ + public static boolean greaterThan(UInt128 x, UInt128 y) { + return lessThan(y, x); + } + + /** + * Returns whether {@code x} ≥ {@code y}. + * + * @param x the 1st parameter + * @param y the 2nd parameter + * @return the result described above. + */ + public static boolean greaterEqual(UInt128 x, UInt128 y) { + return !lessThan(x, y); + } + + @Override + public int compareTo(UInt128 o) { + return lessThan(this, o) ? -1 : equal(this, o) ? 0 : 1; + } + + /** + * Returns ~{@code x}. + * + * @param x the parameter + * @return the result described above. + */ + public static UInt128 not(UInt128 x) { + return new UInt128(~x.c0, ~x.c1); + } + + /** + * Returns {@code x} & {@code y}. + * + * @param x the 1st parameter + * @param y the 2nd parameter + * @return the result described above. + */ + public static UInt128 and(UInt128 x, UInt128 y) { + return new UInt128(x.c0 & y.c0, x.c1 & y.c1); + } + + /** + * Returns {@code x} & ~{@code y}. + * + * @param x the 1st parameter + * @param y the 2nd parameter + * @return the result described above. + */ + public static UInt128 andNot(UInt128 x, UInt128 y) { + return new UInt128(x.c0 & ~y.c0, x.c1 & ~y.c1); + } + + /** + * Returns {@code x} | {@code y}. + * + * @param x the 1st parameter + * @param y the 2nd parameter + * @return the result described above. + */ + public static UInt128 or(UInt128 x, UInt128 y) { + return new UInt128(x.c0 | y.c0, x.c1 | y.c1); + } + + /** + * Returns {@code x} ^ {@code y}. + * + * @param x the 1st parameter + * @param y the 2nd parameter + * @return the result described above. + */ + public static UInt128 xor(UInt128 x, UInt128 y) { + return new UInt128(x.c0 ^ y.c0, x.c1 ^ y.c1); + } + + /** + * Returns {@code x} << {@code sh}. + * + * @param x the argument. + * @param sh only the {@link #LOG2_SIZE} least significant bits are relevant. + * @return the result described above. + */ + public static UInt128 shiftLeft(UInt128 x, int sh) { + sh &= SIZE - 1; + return sh >= 64 + ? new UInt128(0, x.c0 << sh) + : shiftLeftSmall(x, sh); + } + + /** + * Returns {@code x} >>> {@code sh}. + * + * @param x the argument. + * @param sh only the {@link #LOG2_SIZE} least significant bits are relevant. + * @return the result described above. + */ + public static UInt128 shiftRight(UInt128 x, int sh) { + sh &= SIZE - 1; + return sh >= 64 + ? new UInt128(x.c1 >>> sh) + : shiftRightSmall(x, sh); + } + + /* + * Unsigned 192-bit integers. + * This is just to support UInt128 division, not a fully fledged class. + */ + @jdk.internal.MigratedValueClass + @jdk.internal.ValueBased + private static final class UInt192 { + + private static final int SIZE = 3 * Long.SIZE; + + /* + * The value of the instance is + * c0 + c1 2^64 + c2 2^128 + */ + private final long c0; + private final long c1; + private final long c2; + + private UInt192(long c0, long c1, long c2) { + this.c0 = c0; + this.c1 = c1; + this.c2 = c2; + } + + /* For debugging purposes. */ + @Override + public String toString() { + return toBigInteger().toString(); + } + + /* For interchange. */ + private BigInteger toBigInteger() { + byte[] ba = new byte[SIZE / Byte.SIZE]; + UInt64.putLong(ba, 0 * 8, c2); + UInt64.putLong(ba, 1 * 8, c1); + UInt64.putLong(ba, 2 * 8, c0); + return new BigInteger(1, ba); + } + + /** + * Performs an integer division with remainder. + * + * @param x the dividend. + * @param y the divisor. + * Must meet {@code y} ≥ 264. + * @return the result described above. + */ + static Quo64Rem128 divideAndRemainder(UInt128 x, UInt128 y) { + /* Multidigit integer division, as described by Knuth. */ + int sh = Long.numberOfLeadingZeros(y.c1); // 0 ≤ sh < 2 S + y = shiftLeftSmall(y, sh); // B^4 / 2 ≤ y < B^4 + long yh2 = y.c1 & MASK; + long yh3 = y.c1 >>> S; // yh3 ≥ B / 2 + UInt192 xp = shiftLeft(x, sh); // xp < B^6 / 2 + long xp0 = xp.c0 & MASK; + + /* + * Performs the 1st of two 5-by-4 B-based digits division. + * + * Both xp and y are also viewed as integers in radix B. + * As xp < B^6, after the right shift we get xp < B^5, thus + * xp = (xh0 + xh1 B) + (xh2 B^2 + xh3 B^3) + xh4 B^4, xh4 < B / 2 + * y = (yh0 + yh1 B) + (yh2 B^2 + yh3 B^3), yh3 ≥ B / 2 + */ + xp = shiftRightS(xp); + long xh2 = xp.c1 & MASK; + long xh34 = xp.c2 << S | xp.c1 >>> S; // xh3 + xh4 B < B^2 / 2 + long q = divideUnsigned(xh34, yh3); // q < B + long r = xh34 - q * yh3; // r < yh3 < B + if (UInt64.lessThan(r << S | xh2, q * yh2)) { + q -= 1; + r += yh3; // r < 2 yh3 < 2 B + if (r < B && UInt64.lessThan(r << S | xh2, q * yh2)) { + q -= 1; + } + } + xp = subtract(xp, multiply(y, q)); + if (xp.c2 < 0) { + q -= 1; + xp = add(xp, y); + } + long q0 = q; // q = qr.quo < B + + /* Performs the 2nd of two 5-by-4 B-based digits division. */ + xp = shiftLeftSOr(xp, xp0); + xh2 = xp.c1 & MASK; + xh34 = xp.c2 << S | xp.c1 >>> S; + q = divideUnsigned(xh34, yh3); + r = xh34 - q * yh3; + if (UInt64.lessThan(r << S | xh2, q * yh2)) { + q -= 1; + r += yh3; + if (r < B && UInt64.lessThan(r << S | xh2, q * yh2)) { + q -= 1; + } + } + xp = subtract(xp, multiply(y, q)); + if (xp.c2 < 0) { + q -= 1; + xp = add(xp, y); + } + return new Quo64Rem128(q0 << S | q, shiftRightSmall(new UInt128(xp.c0, xp.c1), sh)); + } + + private static UInt192 shiftRightS(UInt192 x) { + return new UInt192( + x.c0 >>> S | x.c1 << S, + x.c1 >>> S | x.c2 << S, + x.c2 >>> S); + } + + private static UInt192 add(UInt192 x, UInt128 y) { + long z0, z1, z2; + z0 = x.c0 + y.c0; + z1 = carry(z0, x.c0); + z1 += x.c1; + z2 = carry(z1, x.c1); + z1 += y.c1; + z2 += carry(z1, y.c1); + z2 += x.c2; + return new UInt192(z0, z1, z2); + } + + private static UInt192 subtract(UInt192 x, UInt192 y) { + long z0, z1, z2; + z0 = x.c0 - y.c0; + z1 = z2 = -carry(x.c0, y.c0); + z1 += x.c1; + z2 += carry(z1, x.c1); + z2 -= carry(z1, y.c1); + z1 -= y.c1; + z2 += x.c2; + z2 -= y.c2; + return new UInt192(z0, z1, z2); + } + + private static UInt192 shiftLeft(UInt128 x, int sh) { + /* Branchless code. */ + return new UInt192( + x.c0 << sh, + x.c1 << sh | x.c0 >>> 63 - sh >>> 1, + x.c1 >>> 63 - sh >>> 1); + } + + /** + * Returns {@code x} << 32 | {@code y} + * @param x the 1st parameter + * @param y the 2nd parameter + * @return the result described above. + */ + private static UInt192 shiftLeftSOr(UInt192 x, long y) { + return new UInt192( + x.c0 << S | y, + x.c1 << S | x.c0 >>> S, + x.c1 >>> S); + } + + /** + * Returns {@code x}⋅{@code y}. + * + * @param x the 1st parameter + * @param y the 2nd parameter + * @return the result described above. + */ + private static UInt192 multiply(UInt128 x, long y) { + long z0, z1, z2, t0; + z0 = x.c0 * y; + z1 = Math.unsignedMultiplyHigh(x.c0, y); + t0 = x.c1 * y; + z2 = Math.unsignedMultiplyHigh(x.c1, y); + z1 += t0; + z2 += carry(z1, t0); + return new UInt192(z0, z1, z2); + } + + } + +} diff --git a/src/java.base/share/classes/java/math/UInt64.java b/src/java.base/share/classes/java/math/UInt64.java new file mode 100644 index 00000000000..c52ecbfd3b2 --- /dev/null +++ b/src/java.base/share/classes/java/math/UInt64.java @@ -0,0 +1,252 @@ +/* + * Copyright (c) 2026, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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 java.math; + +import jdk.internal.vm.annotation.Stable; + +/** + * Static methods operating on unsigned longs. + *

+ * All operations are performed modulo 2{@link #SIZE}, + * unless specified otherwise. + *

+ * WARNING: + *

+ */ +@jdk.internal.MigratedValueClass +@jdk.internal.ValueBased +// TODO: once in the platform, declare as value class and remove useless final +public final class UInt64 { + + /* + * Except when noted otherwise, this class assumes that long values + * are unsigned. + */ + + /** + * The number of bits used to represent a value of this class in binary form. + */ + public static final int SIZE = Long.SIZE; + + /** + * max{e ∈ ℕ : 10e ≤ 2{@link #SIZE}} + */ + public static final int DEC_SIZE = 19; // max{e ∈ ℤ: 10^e ≤ 2^SIZE} + + /* + * For division, we need a 2 digits by 1 digit division primitive. + * Unfortunately, the platform does not provide it for digits of + * radix 2^SIZE. + * Resort to a smaller base B = 2^S of S bits, and smaller digits. + */ + static final int S = Integer.SIZE; + static final long B = 1L << S; + static final long MASK = B - 1; + + // TODO: once in the platform, add it to the AOT cache + @Stable + private static final long[] POW_10; + + static { + POW_10 = new long[DEC_SIZE + 1]; + POW_10[0] = 1L; + for (int i = 1; i < POW_10.length; ++i) { + POW_10[i] = POW_10[i - 1] * 10L; + } + } + + private UInt64() { + } + + /** + * Returns 10{@code e}. + * + * @param e must meet 0 ≤ {@code e} ≤ {@link #DEC_SIZE}. + * @return the result described above. + * @throws RuntimeException if {@code e} is out of range. + */ + public static long pow10(int e) { + return POW_10[e]; + } + + /** + * Returns 10{@code e}. + * + * @param e must meet 0 ≤ {@code e} ≤ {@link #DEC_SIZE}. + * @return the result described above. + * @throws RuntimeException if {@code e} is out of range. + */ + public static long pow10(long e) { + return POW_10[(int) e]; + } + + /** + * Puts all 8 bytes of {@code c} into {@code ba} in big-endian order, + * replacing the bytes starting at index {@code i}. + * + * @param ba the (big-endian ordered) container. + * @param i the index to start at. + * @param c the value to put. + */ + static void putLong(byte[] ba, int i, long c) { + ba[i] = (byte) (c >>> -1 * 8); + ba[i + 1] = (byte) (c >>> -2 * 8); + ba[i + 2] = (byte) (c >>> -3 * 8); + ba[i + 3] = (byte) (c >>> -4 * 8); + ba[i + 4] = (byte) (c >>> -5 * 8); + ba[i + 5] = (byte) (c >>> -6 * 8); + ba[i + 6] = (byte) (c >>> -7 * 8); + ba[i + 7] = (byte) c; + } + + /** + * Fetches the returned value from {@code ba} in big-endian order, + * reading 8 bytes starting at index {@code i}. + * + * @param ba the (big-endian ordered) container. + * @param i the index to start at. + * @return the fetched value. + */ + static long getLong(byte[] ba, int i) { + return (ba[i] & 0xffL) << -1 * 8 + | (ba[i + 1] & 0xffL) << -2 * 8 + | (ba[i + 2] & 0xffL) << -3 * 8 + | (ba[i + 3] & 0xffL) << -4 * 8 + | (ba[i + 4] & 0xffL) << -5 * 8 + | (ba[i + 5] & 0xffL) << -6 * 8 + | (ba[i + 6] & 0xffL) << -7 * 8 + | (ba[i + 7] & 0xffL); + } + + /** + * Returns the carry/borrow of a sum/difference. + * + * @param x the sum (for addition), or the minuend (for subtraction). + * @param y an addend (for addition), or the subtrahend (for subtraction). + * @return the carry/borrow, either 0 (no carry/borrow) or 1. + */ + static long carry(long x, long y) { + return lessThan(x, y) ? 1L : 0L; + } + + /** + * Returns {@code x} + {@code y}, throwing on overflow. + * + * @param x the 1st parameter + * @param y the 2nd parameter + * @return the result described above. + * @throws ArithmeticException on overflow. + */ + static long addExact(long x, long y) { + long z = x + y; + if (carry(z, x) == 0) { + return z; + } + throw new ArithmeticException("overflow"); + } + + /** + * Returns whether {@code x} < {@code y}. + * + * @param x the 1st parameter + * @param y the 2nd parameter + * @return the result described above. + */ + static boolean lessThan(long x, long y) { + /* +Long.MIN_VALUE translates directly to unsigned comparison. */ + return (x + Long.MIN_VALUE) < (y + Long.MIN_VALUE); + } + + /** + * Returns whether {@code x} ≤ {@code y}. + * + * @param x the 1st parameter + * @param y the 2nd parameter + * @return the result described above. + */ + public static boolean lessEqual(long x, long y) { + /* +Long.MIN_VALUE translates directly to unsigned comparison. */ + return (x + Long.MIN_VALUE) <= (y + Long.MIN_VALUE); + } + + /** + * Returns whether {@code x} > {@code y}. + * + * @param x the 1st parameter + * @param y the 2nd parameter + * @return the result described above. + */ + public static boolean greaterThan(long x, long y) { + /* +Long.MIN_VALUE translates directly to unsigned comparison. */ + return (x + Long.MIN_VALUE) > (y + Long.MIN_VALUE); + } + + /** + * Returns whether {@code x} ≥ {@code y}. + * + * @param x the 1st parameter + * @param y the 2nd parameter + * @return the result described above. + */ + public static boolean greaterEqual(long x, long y) { + /* s+Long.MIN_VALUE translates directly to unsigned comparison. */ + return (x + Long.MIN_VALUE) >= (y + Long.MIN_VALUE); + } + + private static final int Q = 21; + private static final int C = 631_305; + + /** + * Returns max{e ∈ ℤ : 10e ≤ 2{@code q}}. + * Equivalently, returns ⌊{@code q} log102⌋. + * + * @param q must meet |{@code q}| ≤ 2_135 (not checked). + * @return the result described above. + */ + static int flog10pow2(int q) { + return q * C >> Q; + } + + /** + * Returns min{e ∈ ℕ : {@code c} < 10e}. + * + * @param c the parameter + * @return the value as described above. + */ + public static int len10(long c) { + int e = flog10pow2(Long.SIZE - Long.numberOfLeadingZeros(c)); + return lessThan(c, POW_10[e]) ? e : e + 1; + } + +} diff --git a/test/jdk/java/math/UInt/UInt128Test.java b/test/jdk/java/math/UInt/UInt128Test.java new file mode 100644 index 00000000000..1a91e454088 --- /dev/null +++ b/test/jdk/java/math/UInt/UInt128Test.java @@ -0,0 +1,591 @@ +/* + * Copyright (c) 2026, 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. + */ + +/* + * @test + * @bug 8383809 + * @summary mostly random tests, checked against BigInteger. + * @comment Use -DCOUNT=n to specify the number of iterations for each test + * (default is 100_000) + * @run junit UInt128Test + */ + +import org.junit.jupiter.api.Test; + +import java.math.BigInteger; +import java.math.UInt128; +import java.math.UInt64; +import java.util.Locale; +import java.util.random.RandomGenerator; + +import static java.lang.Integer.signum; +import static java.math.BigInteger.valueOf; +import static java.math.UInt128.*; +import static org.junit.jupiter.api.Assertions.*; + +public class UInt128Test { + + private static final BigInteger MASK_64 = valueOf(-1).shiftLeft(64); + private static final BigInteger MASK_128 = valueOf(-1).shiftLeft(128); + private static final RandomGenerator rnd = RandomGenerator.getDefault(); + + private static final int COUNT; + + static { + COUNT = Integer.getInteger("COUNT", 100_000); + } + + @Test + void testLen10() { + for (int e = 0; e <= DEC_SIZE; ++e) { + UInt128 pow10 = pow10(e); + assertEquals(e, len10(subtract(pow10, 1))); + assertEquals(e + 1, len10(pow10)); + assertEquals(e + 1, len10(add(pow10, 1))); + } + } + + @Test + void testParse() { + testParse("0"); + testParse("1"); + testParse("0000"); + testParse("00_00"); + testParse("000000000000001"); + testParse("123_456"); + testParse("123__456"); + testParse("000_123_456"); + testParse("000____123_456"); + testParse("000____123____456"); + testParse("0".repeat(1_000_000)); + testParse("9".repeat(DEC_SIZE)); + testParse("340282366920938463463374607431768211455"); // 2^128 - 1 + assertThrows(IllegalArgumentException.class, + () -> testParse("-1")); + assertThrows(IllegalArgumentException.class, + () -> testParse(" 0")); // non digit chars + assertThrows(IllegalArgumentException.class, + () -> testParse("000 ")); // non digit chars + assertThrows(IllegalArgumentException.class, + () -> testParse("cafe_babe")); // non digit chars + assertThrows(IllegalArgumentException.class, + () -> testParse("๑๒๓๔")); // non ASCII digits + assertThrows(IllegalArgumentException.class, + () -> testParse("_000")); // leading _ + assertThrows(IllegalArgumentException.class, + () -> testParse("_123")); // leading _ + assertThrows(IllegalArgumentException.class, + () -> testParse("000_")); // trailing _ + assertThrows(IllegalArgumentException.class, + () -> testParse("123_")); // trailing _ + assertThrows(ArithmeticException.class, + () -> testParse("340282366920938463463374607431768211456")); // 2^128 + assertThrows(IndexOutOfBoundsException.class, + () -> testParse("")); + for (int i = 0; i < COUNT; ++i) { + String s = (randomLong() + "" + randomLong()).replace("-", ""); + testParse(s); + } + } + + private void testParse(String s) { + UInt128 x = UInt128.parse(s, 0, s.length(), 10, '_'); + assertEqualsUInt128(x, new BigInteger(s.replace("_", "")), x); + } + + @Test + void testParseLiteral() { + testParseLiteral("0"); + testParseLiteral("1"); + testParseLiteral("0000"); + testParseLiteral("00_00"); + testParseLiteral("000000000000001"); + testParseLiteral("123_456"); + testParseLiteral("123__456"); + testParseLiteral("000_123_456"); + testParseLiteral("000____123_456"); + testParseLiteral("000____123____456"); + testParseLiteral("0" + "_0".repeat(1_000_000)); + testParseLiteral("9".repeat(DEC_SIZE)); + testParseLiteral("340282366920938463463374607431768211455"); // 2^128 - 1 + testParseLiteral("0xffff_FFFF__ffff_fFfF___ffff_FFFF__ffff_fFfF"); // 2^128 - 1 + testParseLiteral("0B" + "1".repeat(128)); // 2^128 - 1 + assertThrows(IllegalArgumentException.class, + () -> testParseLiteral("-1")); + assertThrows(IllegalArgumentException.class, + () -> testParseLiteral("0B_000")); // leading _ + assertThrows(IllegalArgumentException.class, + () -> testParseLiteral("0x_123")); // leading _ + assertThrows(IllegalArgumentException.class, + () -> testParseLiteral("000_")); // trailing _ + assertThrows(IllegalArgumentException.class, + () -> testParseLiteral("123_")); // trailing _ + assertThrows(IllegalArgumentException.class, + () -> testParseLiteral("08")); + assertThrows(IllegalArgumentException.class, + () -> testParseLiteral("000000009")); + assertThrows(IndexOutOfBoundsException.class, + () -> testParseLiteral("")); + assertThrows(ArithmeticException.class, + () -> testParseLiteral("340282366920938463463374607431768211456")); // 2^128 + assertThrows(ArithmeticException.class, + () -> testParseLiteral("0X1" + "0".repeat(32))); // 2^128 + assertThrows(ArithmeticException.class, + () -> testParseLiteral("0B1" + "0".repeat(128))); // 2^128 + for (int i = 0; i < COUNT; ++i) { + String s = (randomNonzeroLong() + "" + randomLong()).replace("-", ""); + testParseLiteral(s); + } + } + + private void testParseLiteral(String s) { + UInt128 x = parseLiteral(s, 0, s.length()); + s = s.replace("_", "").toLowerCase(Locale.ROOT); + int radix = + s.indexOf("0x") == 0 + ? 16 + : s.indexOf("0b") == 0 + ? 2 + : s.indexOf("0") == 0 + ? 8 + : 10; + if (radix == 16 || radix == 2) { + s = s.substring(2); + } + assertEqualsUInt128(x, new BigInteger(s, radix), x); + } + + @Test + void testToString() { + testToString(_0()); + testToString(_1()); + testToString(MAX_VALUE()); + testToString(parse("9".repeat(UInt64.DEC_SIZE - 1))); + testToString(parse("9".repeat(UInt64.DEC_SIZE))); + testToString(parse("9".repeat(UInt64.DEC_SIZE + 1))); + testToString(parse("9".repeat(2 * UInt64.DEC_SIZE - 1))); + testToString(parse("9".repeat(2 * UInt64.DEC_SIZE))); + testToString(parse("1".repeat(2 * UInt64.DEC_SIZE + 1))); + for (int i = 0; i < COUNT; ++i) { + testToString(randomUInt128()); + } + } + + private UInt128 parse(String s) { + return UInt128.parse(s, 0, s.length(), 10, -1); + } + + private void testToString(UInt128 x) { + assertEquals(x.toBigInteger().toString(), x.toString()); + } + + @Test + void testAdd() { + for (int i = 0; i < COUNT; ++i) { + testAdd(randomUInt128(), randomLong()); + } + + for (int i = 0; i < COUNT; ++i) { + testAdd(randomUInt128(), randomUInt128()); + } + } + + private void testAdd(UInt128 x, long y) { + BigInteger xp = x.toBigInteger(); + BigInteger yp = unsignedToBigInteger(y); + assertEqualsUInt128(x, y, xp.add(yp), add(x, y)); + } + + private void testAdd(UInt128 x, UInt128 y) { + BigInteger xp = x.toBigInteger(); + BigInteger yp = y.toBigInteger(); + assertEqualsUInt128(x, y, xp.add(yp), add(x, y)); + } + + @Test + void testSubtract() { + for (int i = 0; i < COUNT; ++i) { + testSubtract(randomUInt128(), randomLong()); + } + + for (int i = 0; i < COUNT; ++i) { + testSubtract(randomUInt128(), randomUInt128()); + } + } + + private void testSubtract(UInt128 x, long y) { + BigInteger xp = x.toBigInteger(); + BigInteger yp = unsignedToBigInteger(y); + assertEqualsUInt128(x, y, xp.subtract(yp), subtract(x, y)); + } + + private void testSubtract(UInt128 x, UInt128 y) { + BigInteger xp = x.toBigInteger(); + BigInteger yp = y.toBigInteger(); + assertEqualsUInt128(x, y, xp.subtract(yp), subtract(x, y)); + } + + @Test + void testMultiply() { + testMultiply(0, randomLong()); + for (int i = 0; i < COUNT; ++i) { + testMultiply(randomLong(), randomLong()); + } + + testMultiply(_0(), randomLong()); + for (int i = 0; i < COUNT; ++i) { + testMultiply(randomUInt128(), randomLong()); + } + + testMultiply(_0(), randomUInt128()); + for (int i = 0; i < COUNT; ++i) { + testMultiply(randomUInt128(), randomUInt128()); + } + } + + private void testMultiply(long x, long y) { + BigInteger xp = unsignedToBigInteger(x); + BigInteger yp = unsignedToBigInteger(y); + assertEqualsUInt128(x, y, xp.multiply(yp), multiply(x, y)); + } + + private void testMultiply(UInt128 x, long y) { + BigInteger xp = x.toBigInteger(); + BigInteger yp = unsignedToBigInteger(y); + assertEqualsUInt128(x, y, xp.multiply(yp), multiply(x, y)); + } + + private void testMultiply(UInt128 x, UInt128 y) { + BigInteger xp = x.toBigInteger(); + BigInteger yp = y.toBigInteger(); + assertEqualsUInt128(x, y, xp.multiply(yp), multiply(x, y)); + } + + @Test + void testAnd() { + testAnd(_0(), randomUInt128()); + testAnd(MAX_VALUE(), randomUInt128()); + for (int i = 0; i < COUNT; ++i) { + testAnd(randomUInt128(), randomUInt128()); + } + } + + private void testAnd(UInt128 x, UInt128 y) { + BigInteger xp = x.toBigInteger(); + BigInteger yp = y.toBigInteger(); + assertEqualsUInt128(x, y, xp.and(yp), and(x, y)); + } + + @Test + void testAndNot() { + testAndNot(_0(), randomUInt128()); + testAndNot(MAX_VALUE(), randomUInt128()); + testAndNot(randomUInt128(), _0()); + testAndNot(randomUInt128(), new UInt128(-1)); + testAndNot(randomUInt128(), MAX_VALUE()); + for (int i = 0; i < COUNT; ++i) { + testAndNot(randomUInt128(), randomUInt128()); + } + } + + private void testAndNot(UInt128 x, UInt128 y) { + BigInteger xp = x.toBigInteger(); + BigInteger yp = y.toBigInteger(); + assertEqualsUInt128(x, y, xp.andNot(yp), andNot(x, y)); + } + + @Test + void testOr() { + testOr(_0(), randomUInt128()); + testOr(MAX_VALUE(), randomUInt128()); + for (int i = 0; i < COUNT; ++i) { + testOr(randomUInt128(), randomUInt128()); + } + } + + private void testOr(UInt128 x, UInt128 y) { + BigInteger xp = x.toBigInteger(); + BigInteger yp = y.toBigInteger(); + assertEqualsUInt128(x, y, xp.or(yp), or(x, y)); + } + + @Test + void testXor() { + testXor(_0(), randomUInt128()); + testXor(MAX_VALUE(), randomUInt128()); + for (int i = 0; i < COUNT; ++i) { + testXor(randomUInt128(), randomUInt128()); + } + } + + private void testXor(UInt128 x, UInt128 y) { + BigInteger xp = x.toBigInteger(); + BigInteger yp = y.toBigInteger(); + assertEqualsUInt128(x, y, xp.xor(yp), xor(x, y)); + } + + @Test + void testNegate() { + testNegate(_0()); + testNegate(_1()); + testNegate(MAX_VALUE()); + for (int i = 0; i < COUNT; ++i) { + testNegate(randomUInt128()); + } + } + + private void testNegate(UInt128 x) { + BigInteger xp = x.toBigInteger(); + assertEqualsUInt128(x, xp.negate(), negate(x)); + } + + @Test + void testNot() { + testNot(_0()); + testNot(MAX_VALUE()); + for (int i = 0; i < COUNT; ++i) { + testNot(randomUInt128()); + } + } + + private void testNot(UInt128 x) { + BigInteger xp = x.toBigInteger(); + assertEqualsUInt128(x, xp.not(), not(x)); + } + + @Test + void testShiftLeft() { + for (int i = 0; i < COUNT; ++i) { + testShiftLeft(randomUInt128(), rnd.nextInt()); + } + } + + private void testShiftLeft(UInt128 x, int sh) { + BigInteger xp = x.toBigInteger(); + assertEqualsUInt128(x, sh, + xp.shiftLeft(sh & SIZE - 1), shiftLeft(x, sh)); + } + + @Test + void testShiftRight() { + for (int i = 0; i < COUNT; ++i) { + testShiftRight(randomUInt128(), rnd.nextInt()); + } + } + + private void testShiftRight(UInt128 x, int sh) { + BigInteger xp = x.toBigInteger(); + assertEqualsUInt128(x, sh, + xp.shiftRight(sh & SIZE - 1), shiftRight(x, sh)); + } + + @Test + void testDivideAndRemainder64_64() { + testDivideAndRemainder(_0(), new UInt128(randomNonzeroLong())); + + for (int i = 0; i < COUNT; ++i) { + UInt128 x = new UInt128(randomNonzeroLong()); + testDivideAndRemainder(x, x); + } + + for (int i = 0; i < COUNT; ++i) { + testDivideAndRemainder( + new UInt128(randomLong()), + new UInt128(randomNonzeroLong())); + } + } + + @Test + void testDivideAndRemainder128_32() { + testDivideAndRemainder(_0(), new UInt128(randomNonzeroInt())); + + for (int i = 0; i < COUNT; ++i) { + UInt128 x = new UInt128(randomNonzeroInt()); + testDivideAndRemainder(x, x); + } + + for (int i = 0; i < COUNT; ++i) { + testDivideAndRemainder( + randomUInt128(), + new UInt128(randomNonzeroInt())); + } + } + + @Test + void testDivideAndRemainder() { + assertThrows(ArithmeticException.class, + () -> divideAndRemainder(_0(), _0())); + assertThrows(ArithmeticException.class, + () -> divideAndRemainder(randomUInt128(), _0())); + + /* Triggers last rare correction only in the 1st part of the division algorithm. */ + testDivideAndRemainder( + new UInt128(0x0000_0000_0000_0000L, 0x8000_0000_0000_0000L), + new UInt128(0x0000_0000_0000_0001L, 0x0000_0000_4000_0000L)); + + /* Triggers last rare correction only in the 2nd part of the division algorithm. */ + testDivideAndRemainder( + new UInt128(0x0000_0000_0000_0000L, 0x0000_0001_0000_0000L), + new UInt128(0x0000_0000_0000_0001L, 0x0000_0000_8000_0000L)); + + /* Triggers last rare correction in both parts of the division algorithm. */ + testDivideAndRemainder( + new UInt128(0x0000_0000_0000_0000L, 0x8000_0000_0000_0000L), + new UInt128(0x0000_0000_0000_0001L, 0x0000_0000_8000_0000L)); + + testDivideAndRemainder(_0(), randomNonzeroUInt128()); + + for (int i = 0; i < COUNT; ++i) { + UInt128 x = randomNonzeroUInt128(); + testDivideAndRemainder(x, x); + } + + for (int i = 0; i < COUNT; ++i) { + testDivideAndRemainder( + randomUInt128(), + randomNonzeroUInt128()); + } + } + + @Test + void testComparisons() { + testComparisons(_0(), _0()); + testComparisons(_0(), randomUInt128()); + testComparisons(_1(), _1()); + testComparisons(_1(), randomUInt128()); + testComparisons(MAX_VALUE(), MAX_VALUE()); + testComparisons(MAX_VALUE(), randomUInt128()); + UInt128 x = randomUInt128(); + testComparisons(x, x); + + testComparisons(_0(), new UInt128(1, -1)); + testComparisons(new UInt128(1, -1), new UInt128(2, -1)); + + for (int i = 0; i < COUNT; ++i) { + testComparisons(randomUInt128(), randomUInt128()); + } + } + + private void testComparisons(UInt128 x, UInt128 y) { + int cmp = x.toBigInteger().compareTo(y.toBigInteger()); + assertEquals(cmp == 0, equal(x, y)); + assertEquals(cmp != 0, !equal(x, y)); + assertEquals(cmp < 0, lessThan(x, y)); + assertEquals(cmp <= 0, lessEqual(x, y)); + assertEquals(cmp > 0, greaterThan(x, y)); + assertEquals(cmp >= 0, greaterEqual(x, y)); + assertEquals(signum(cmp), signum(x.compareTo(y))); + } + + private static void testDivideAndRemainder(UInt128 x, UInt128 y) { + QuoRem actual = divideAndRemainder(x, y); + assertTrue(lessThan(actual.rem(), y)); + assertEqualsUInt128(x, y, x, add(multiply(actual.quo(), y), actual.rem())); + + BigInteger xp = x.toBigInteger(); + BigInteger yp = y.toBigInteger(); + BigInteger[] expected = xp.divideAndRemainder(yp); + assertEqualsUInt128(x, y, expected[0], actual.quo()); + assertEqualsUInt128(x, y, expected[1], actual.rem()); + } + + private static long randomLong() { + return rnd.nextLong(); + } + + private static long randomNonzeroLong() { + for (int i = 0; i < 1_000; ++i) { + long x = randomLong(); + if (x != 0) { + return x; + } + } + throw new RuntimeException("can't get a non-zero random number!"); + } + + private static long randomInt() { + return rnd.nextInt() & 0xffff_ffffL; + } + + private static long randomNonzeroInt() { + for (int i = 0; i < 1_000; ++i) { + long x = randomInt(); + if (x != 0) { + return x; + } + } + throw new RuntimeException("can't get a non-zero random number!"); + } + + private static UInt128 randomUInt128() { + return new UInt128(rnd.nextLong(), rnd.nextLong()); + } + + private static UInt128 randomNonzeroUInt128() { + for (int i = 0; i < 1_000; ++i) { + UInt128 x = randomUInt128(); + if (!isZero(x)) { + return x; + } + } + throw new RuntimeException("can't get a non-zero random number!"); + } + + private static BigInteger unsignedToBigInteger(long v) { + return valueOf(v).andNot(MASK_64); + } + + private static void assertEqualsUInt128(UInt128 x, UInt128 y, + UInt128 expected, UInt128 actual) { + if (!equal(expected, actual)) { + throw new RuntimeException(String.format("x=%s, y=%s, expected=%s, actual=%s", x, y, expected, actual)); + } + } + + private static void assertEqualsUInt128(UInt128 x, UInt128 y, + BigInteger expectedp, UInt128 actual) { + UInt128 expected = new UInt128(expectedp.andNot(MASK_128)); + if (!equal(expected, actual)) { + throw new RuntimeException(String.format("x=%s, y=%s, expected=%s, actual=%s", x, y, expected, actual)); + } + } + + private static void assertEqualsUInt128(UInt128 x, + BigInteger expectedp, UInt128 actual) { + UInt128 expected = new UInt128(expectedp.andNot(MASK_128)); + if (!equal(expected, actual)) { + throw new RuntimeException(String.format("x=%s, expected=%s, actual=%s", x, expected, actual)); + } + } + + private static void assertEqualsUInt128(long x, long y, + BigInteger expected, UInt128 actual) { + assertEqualsUInt128(new UInt128(x), new UInt128(y), expected, actual); + } + + private static void assertEqualsUInt128(UInt128 x, long y, + BigInteger expected, UInt128 actual) { + assertEqualsUInt128(x, new UInt128(y), expected, actual); + } + +} diff --git a/test/jdk/java/math/UInt/UInt64Test.java b/test/jdk/java/math/UInt/UInt64Test.java new file mode 100644 index 00000000000..5bf152602c9 --- /dev/null +++ b/test/jdk/java/math/UInt/UInt64Test.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2026, 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. + */ + +/* + * @test + * @bug 8383809 + * @run junit UInt64Test + */ + +import org.junit.jupiter.api.Test; + +import java.math.UInt64; + +import static org.junit.jupiter.api.Assertions.*; + +public class UInt64Test { + + @Test + void testLen10() { + for (int e = 0; e <= UInt64.DEC_SIZE; ++e) { + long pow10 = UInt64.pow10(e); + assertEquals(e, UInt64.len10(pow10 - 1)); + assertEquals(e + 1, UInt64.len10(pow10)); + assertEquals(e + 1, UInt64.len10(pow10 + 1)); + } + } + +}