Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JDK-8277175 : Add a parallel multiply method to BigInteger #6391

Closed
wants to merge 4 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
148 changes: 115 additions & 33 deletions src/java.base/share/classes/java/math/BigInteger.java
Original file line number Diff line number Diff line change
@@ -29,21 +29,23 @@

package java.math;

import jdk.internal.math.DoubleConsts;
import jdk.internal.math.FloatConsts;
import jdk.internal.vm.annotation.ForceInline;
import jdk.internal.vm.annotation.IntrinsicCandidate;
import jdk.internal.vm.annotation.Stable;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamField;
import java.io.Serial;
import java.util.Arrays;
import java.util.Objects;
import java.util.Random;
import java.util.concurrent.RecursiveTask;
import java.util.concurrent.ThreadLocalRandom;

import jdk.internal.math.DoubleConsts;
import jdk.internal.math.FloatConsts;
import jdk.internal.vm.annotation.ForceInline;
import jdk.internal.vm.annotation.IntrinsicCandidate;
import jdk.internal.vm.annotation.Stable;

/**
* Immutable arbitrary-precision integers. All operations behave as if
* BigIntegers were represented in two's-complement notation (like Java's
@@ -1065,7 +1067,7 @@ private static BigInteger lucasLehmerSequence(int z, BigInteger k, BigInteger n)
for (int i=k.bitLength()-2; i >= 0; i--) {
u2 = u.multiply(v).mod(n);

v2 = v.square().add(d.multiply(u.square())).mod(n);
v2 = v.square(false).add(d.multiply(u.square(false))).mod(n);
if (v2.testBit(0))
v2 = v2.subtract(n);

@@ -1581,7 +1583,22 @@ private static int[] subtract(int[] big, int[] little) {
* @return {@code this * val}
*/
public BigInteger multiply(BigInteger val) {
return multiply(val, false);
return multiply(val, false, false);
}

/**
* Returns a BigInteger whose value is {@code (this * val)}.
* When both {@code this} and {@code val} are large, typically
* in the thousands of bits, parallel multiply might be used.
*
* @implNote An implementation may offer better algorithmic
* performance when {@code val == this}.
*
* @param val value to be multiplied by this BigInteger.
* @return {@code this * val}
*/
public BigInteger parallelMultiply(BigInteger val) {
return multiply(val, false, true);
}

/**
@@ -1590,16 +1607,17 @@ public BigInteger multiply(BigInteger val) {
*
* @param val value to be multiplied by this BigInteger.
* @param isRecursion whether this is a recursive invocation
* @param parallel whether the multiply should be done in parallel
* @return {@code this * val}
*/
private BigInteger multiply(BigInteger val, boolean isRecursion) {
private BigInteger multiply(BigInteger val, boolean isRecursion, boolean parallel) {
if (val.signum == 0 || signum == 0)
return ZERO;

int xlen = mag.length;

if (val == this && xlen > MULTIPLY_SQUARE_THRESHOLD) {
return square();
return square(parallel);
}

int ylen = val.mag.length;
@@ -1677,7 +1695,7 @@ private BigInteger multiply(BigInteger val, boolean isRecursion) {
}
}

return multiplyToomCook3(this, val);
return multiplyToomCook3(this, val, parallel);
}
}
}
@@ -1844,6 +1862,33 @@ private static BigInteger multiplyKaratsuba(BigInteger x, BigInteger y) {
}
}

private static final class RecursiveMultiply extends RecursiveTask<BigInteger> {
@Serial
private static final long serialVersionUID = 0L;
private final BigInteger a;
private final BigInteger b;
private final boolean isRecursive;
private final boolean parallel;

private RecursiveMultiply(BigInteger a, BigInteger b, boolean isRecursive, boolean parallel) {
this.a = a;
this.b = b;
this.isRecursive = isRecursive;
this.parallel = parallel;
}

@Override
protected BigInteger compute() {
return a.multiply(b, isRecursive, parallel);
}

public static RecursiveMultiply create(BigInteger a, BigInteger b, boolean isRecursive, boolean parallel) {
var result = new RecursiveMultiply(a, b, isRecursive, parallel);
if (parallel) result.fork(); else result.invoke();
return result;
}
}

/**
* Multiplies two BigIntegers using a 3-way Toom-Cook multiplication
* algorithm. This is a recursive divide-and-conquer algorithm which is
@@ -1872,7 +1917,7 @@ private static BigInteger multiplyKaratsuba(BigInteger x, BigInteger y) {
* LNCS #4547. Springer, Madrid, Spain, June 21-22, 2007.
*
*/
private static BigInteger multiplyToomCook3(BigInteger a, BigInteger b) {
private static BigInteger multiplyToomCook3(BigInteger a, BigInteger b, boolean parallel) {
int alen = a.mag.length;
int blen = b.mag.length;

@@ -1896,16 +1941,22 @@ private static BigInteger multiplyToomCook3(BigInteger a, BigInteger b) {

BigInteger v0, v1, v2, vm1, vinf, t1, t2, tm1, da1, db1;

v0 = a0.multiply(b0, true);
var v0_task = RecursiveMultiply.create(a0, b0, true, parallel);
// v0 = a0.multiply(b0, true, parallel);
da1 = a2.add(a0);
db1 = b2.add(b0);
vm1 = da1.subtract(a1).multiply(db1.subtract(b1), true);
var vm1_task = RecursiveMultiply.create(da1.subtract(a1), db1.subtract(b1), true, parallel);
// vm1 = da1.subtract(a1).multiply(db1.subtract(b1), true, parallel);
da1 = da1.add(a1);
db1 = db1.add(b1);
v1 = da1.multiply(db1, true);
var v1_task = RecursiveMultiply.create(da1, db1, true, parallel);
// v1 = da1.multiply(db1, true, parallel);
v2 = da1.add(a2).shiftLeft(1).subtract(a0).multiply(
db1.add(b2).shiftLeft(1).subtract(b0), true);
vinf = a2.multiply(b2, true);
db1.add(b2).shiftLeft(1).subtract(b0), true, parallel);
vinf = a2.multiply(b2, true, parallel);
v0 = v0_task.join();
vm1 = vm1_task.join();
v1 = v1_task.join();

// The algorithm requires two divisions by 2 and one by 3.
// All divisions are known to be exact, that is, they do not produce
@@ -2070,8 +2121,8 @@ private BigInteger getUpper(int n) {
*
* @return <code>this<sup>2</sup></code>
*/
private BigInteger square() {
return square(false);
private BigInteger square(boolean parallel) {
return square(false, parallel);
}

/**
@@ -2081,7 +2132,7 @@ private BigInteger square() {
* @param isRecursion whether this is a recursive invocation
* @return <code>this<sup>2</sup></code>
*/
private BigInteger square(boolean isRecursion) {
private BigInteger square(boolean isRecursion, boolean parallel) {
if (signum == 0) {
return ZERO;
}
@@ -2103,7 +2154,7 @@ private BigInteger square(boolean isRecursion) {
}
}

return squareToomCook3();
return squareToomCook3(parallel);
}
}
}
@@ -2223,11 +2274,36 @@ private BigInteger squareKaratsuba() {
BigInteger xl = getLower(half);
BigInteger xh = getUpper(half);

BigInteger xhs = xh.square(); // xhs = xh^2
BigInteger xls = xl.square(); // xls = xl^2
BigInteger xhs = xh.square(false); // xhs = xh^2
BigInteger xls = xl.square(false); // xls = xl^2

// xh^2 << 64 + (((xl+xh)^2 - (xh^2 + xl^2)) << 32) + xl^2
return xhs.shiftLeft(half*32).add(xl.add(xh).square().subtract(xhs.add(xls))).shiftLeft(half*32).add(xls);
return xhs.shiftLeft(half*32).add(xl.add(xh).square(false).subtract(xhs.add(xls))).shiftLeft(half*32).add(xls);
}

private static final class RecursiveSquare extends RecursiveTask<BigInteger> {
@Serial
private static final long serialVersionUID = 0L;
private final BigInteger num;
private final boolean isRecursive;
private final boolean parallel;

private RecursiveSquare(BigInteger a, boolean isRecursive, boolean parallel) {
this.num = a;
this.isRecursive = isRecursive;
this.parallel = parallel;
}

@Override
protected BigInteger compute() {
return num.square(isRecursive, parallel);
}

public static RecursiveSquare create(BigInteger a, boolean isRecursive, boolean parallel) {
var result = new RecursiveSquare(a, isRecursive, parallel);
if (parallel) result.fork(); else result.invoke();
return result;
}
}

/**
@@ -2237,7 +2313,7 @@ private BigInteger squareKaratsuba() {
* that has better asymptotic performance than the algorithm used in
* squareToLen or squareKaratsuba.
*/
private BigInteger squareToomCook3() {
private BigInteger squareToomCook3(boolean parallel) {
int len = mag.length;

// k is the size (in ints) of the lower-order slices.
@@ -2254,13 +2330,19 @@ private BigInteger squareToomCook3() {
a0 = getToomSlice(k, r, 2, len);
BigInteger v0, v1, v2, vm1, vinf, t1, t2, tm1, da1;

v0 = a0.square(true);
var v0_fork = RecursiveSquare.create(a0, true, parallel);
// v0 = a0.square(true, parallel);
da1 = a2.add(a0);
vm1 = da1.subtract(a1).square(true);
var vm1_fork = RecursiveSquare.create(da1.subtract(a1), true, parallel);
// vm1 = da1.subtract(a1).square(true, parallel);
da1 = da1.add(a1);
v1 = da1.square(true);
vinf = a2.square(true);
v2 = da1.add(a2).shiftLeft(1).subtract(a0).square(true);
var v1_fork = RecursiveSquare.create(da1, true, parallel);
// v1 = da1.square(true, parallel);
vinf = a2.square(true, parallel);
v2 = da1.add(a2).shiftLeft(1).subtract(a0).square(true, parallel);
v0 = v0_fork.join();
vm1 = vm1_fork.join();
v1 = v1_fork.join();

// The algorithm requires two divisions by 2 and one by 3.
// All divisions are known to be exact, that is, they do not produce
@@ -2515,7 +2597,7 @@ public BigInteger pow(int exponent) {
}

if ((workingExponent >>>= 1) != 0) {
partToSquare = partToSquare.square();
partToSquare = partToSquare.square(false);
}
}
// Multiply back the (exponentiated) powers of two (quickly,
@@ -2574,7 +2656,7 @@ public BigInteger sqrt() {
*/
public BigInteger[] sqrtAndRemainder() {
BigInteger s = sqrt();
BigInteger r = this.subtract(s.square());
BigInteger r = this.subtract(s.square(false));
assert r.compareTo(BigInteger.ZERO) >= 0;
return new BigInteger[] {s, r};
}
@@ -3250,7 +3332,7 @@ private BigInteger modPow2(BigInteger exponent, int p) {
result = result.multiply(baseToPow2).mod2(p);
expOffset++;
if (expOffset < limit)
baseToPow2 = baseToPow2.square().mod2(p);
baseToPow2 = baseToPow2.square(false).mod2(p);
}

return result;
43 changes: 9 additions & 34 deletions src/java.desktop/share/classes/java/awt/geom/CubicCurve2D.java
Original file line number Diff line number Diff line change
@@ -311,23 +311,6 @@ public void setCurve(float x1, float y1,
this.y2 = y2;
}

/**
* {@inheritDoc}
* @since 1.2
*/
public Rectangle2D getBounds2D() {
float left = Math.min(Math.min(x1, x2),
Math.min(ctrlx1, ctrlx2));
float top = Math.min(Math.min(y1, y2),
Math.min(ctrly1, ctrly2));
float right = Math.max(Math.max(x1, x2),
Math.max(ctrlx1, ctrlx2));
float bottom = Math.max(Math.max(y1, y2),
Math.max(ctrly1, ctrly2));
return new Rectangle2D.Float(left, top,
right - left, bottom - top);
}

/**
* Use serialVersionUID from JDK 1.6 for interoperability.
*/
@@ -558,23 +541,6 @@ public void setCurve(double x1, double y1,
this.y2 = y2;
}

/**
* {@inheritDoc}
* @since 1.2
*/
public Rectangle2D getBounds2D() {
double left = Math.min(Math.min(x1, x2),
Math.min(ctrlx1, ctrlx2));
double top = Math.min(Math.min(y1, y2),
Math.min(ctrly1, ctrly2));
double right = Math.max(Math.max(x1, x2),
Math.max(ctrlx1, ctrlx2));
double bottom = Math.max(Math.max(y1, y2),
Math.max(ctrly1, ctrly2));
return new Rectangle2D.Double(left, top,
right - left, bottom - top);
}

/**
* Use serialVersionUID from JDK 1.6 for interoperability.
*/
@@ -1509,6 +1475,15 @@ public boolean contains(Rectangle2D r) {
return contains(r.getX(), r.getY(), r.getWidth(), r.getHeight());
}


/**
* {@inheritDoc}
* @since 1.2
*/
public Rectangle2D getBounds2D() {
return Path2D.getBounds2D(getPathIterator(null));
}

/**
* {@inheritDoc}
* @since 1.2
182 changes: 134 additions & 48 deletions src/java.desktop/share/classes/java/awt/geom/Path2D.java
Original file line number Diff line number Diff line change
@@ -795,30 +795,6 @@ public final void transform(AffineTransform at) {
at.transform(floatCoords, 0, floatCoords, 0, numCoords / 2);
}

/**
* {@inheritDoc}
* @since 1.6
*/
public final synchronized Rectangle2D getBounds2D() {
float x1, y1, x2, y2;
int i = numCoords;
if (i > 0) {
y1 = y2 = floatCoords[--i];
x1 = x2 = floatCoords[--i];
while (i > 0) {
float y = floatCoords[--i];
float x = floatCoords[--i];
if (x < x1) x1 = x;
if (y < y1) y1 = y;
if (x > x2) x2 = x;
if (y > y2) y2 = y;
}
} else {
x1 = y1 = x2 = y2 = 0.0f;
}
return new Rectangle2D.Float(x1, y1, x2 - x1, y2 - y1);
}

/**
* {@inheritDoc}
* <p>
@@ -1587,30 +1563,6 @@ public final void transform(AffineTransform at) {
at.transform(doubleCoords, 0, doubleCoords, 0, numCoords / 2);
}

/**
* {@inheritDoc}
* @since 1.6
*/
public final synchronized Rectangle2D getBounds2D() {
double x1, y1, x2, y2;
int i = numCoords;
if (i > 0) {
y1 = y2 = doubleCoords[--i];
x1 = x2 = doubleCoords[--i];
while (i > 0) {
double y = doubleCoords[--i];
double x = doubleCoords[--i];
if (x < x1) x1 = x;
if (y < y1) y1 = y;
if (x > x2) x2 = x;
if (y > y2) y2 = y;
}
} else {
x1 = y1 = x2 = y2 = 0.0;
}
return new Rectangle2D.Double(x1, y1, x2 - x1, y2 - y1);
}

/**
* {@inheritDoc}
* <p>
@@ -2129,6 +2081,140 @@ public final Rectangle getBounds() {
return getBounds2D().getBounds();
}

/**
* {@inheritDoc}
* @since 1.6
*/
public Rectangle2D getBounds2D() {
return getBounds2D(getPathIterator(null));
}

/**
* Returns a high precision bounding box of the specified PathIterator.
* <p>
* This method provides a basic facility for implementors of the {@link Shape} interface to
* implement support for the {@link Shape#getBounds2D()} method.
* </p>
* @return an instance of {@code Rectangle2D} that is a high-precision bounding box of the
* {@code PathIterator}.
* @see Shape#getBounds2D()
*/
public static Rectangle2D getBounds2D(PathIterator pi) {
// define x and y parametric coefficients where:
// x(t) = x_coeff[0] + x_coeff[1] * t + x_coeff[2] * t^2 + x_coeff[3] * t^3
double[] x_coeff = new double[4];
double[] y_coeff = new double[4];

double[] coords = new double[6];
double[] tExtrema = new double[3];
boolean isEmpty = true;
double leftX = 0.0;
double rightX = 0.0;
double topY = 0.0;
double bottomY = 0.0;
double lastX = 0.0;
double lastY = 0.0;

pathIteratorLoop : while (!pi.isDone()) {
int type = pi.currentSegment(coords);
pi.next();
double endX, endY;
switch (type) {
case PathIterator.SEG_MOVETO, PathIterator.SEG_LINETO:
endX = coords[0];
endY = coords[1];
break;
case PathIterator.SEG_QUADTO:
endX = coords[2];
endY = coords[3];
break;
case PathIterator.SEG_CUBICTO:
endX = coords[4];
endY = coords[5];
break;
default:
continue pathIteratorLoop;
}

if (isEmpty) {
// we're seeding our bounds for the first time:
isEmpty = false;
leftX = rightX = endX;
topY = bottomY = endY;
} else {
// extend our rectangle to cover the point at t = 1:
leftX = (endX < leftX) ? endX : leftX;
rightX = (endX > rightX) ? endX : rightX;
topY = (endY < topY) ? endY : topY;
bottomY = (endY > bottomY) ? endY : bottomY;
}

// here's the slightly trickier part: examine quadratic and cubic
// segments for extrema where t is between (0, 1):

boolean definedParametricEquations;
if (type == PathIterator.SEG_QUADTO) {
definedParametricEquations = true;

x_coeff[3] = 0.0;
x_coeff[2] = lastX - 2.0 * coords[0] + coords[2];
x_coeff[1] = -2.0 * lastX + 2.0 * coords[0];
x_coeff[0] = lastX;

y_coeff[3] = 0;
y_coeff[2] = lastY - 2.0 * coords[1] + coords[3];
y_coeff[1] = -2.0 * lastY + 2.0 * coords[1];
y_coeff[0] = lastY;
} else if (type == PathIterator.SEG_CUBICTO) {
definedParametricEquations = true;

x_coeff[3] = -lastX + 3.0 * coords[0] - 3.0 * coords[2] + coords[4];
x_coeff[2] = 3.0 * lastX - 6.0 * coords[0] + 3.0 * coords[2];
x_coeff[1] = -3.0 * lastX + 3.0 * coords[0];
x_coeff[0] = lastX;

y_coeff[3] = -lastY + 3.0 * coords[1] - 3.0 * coords[3] + coords[5];
y_coeff[2] = 3.0 * lastY - 6.0 * coords[1] + 3.0 * coords[3];
y_coeff[1] = -3.0 * lastY + 3.0 * coords[1];
y_coeff[0] = lastY;
} else {
definedParametricEquations = false;
}

if (definedParametricEquations) {
int tExtremaCount = Curve.findExtrema(x_coeff, tExtrema);
for(int i = 0; i < tExtremaCount; i++) {
double t = tExtrema[i];
if (t > 0 && t < 1) {
double x = x_coeff[0] + t * (x_coeff[1] + t * (x_coeff[2] + t * x_coeff[3]));
leftX = (x < leftX) ? x : leftX;
rightX = (x > rightX) ? x : rightX;
}
}

tExtremaCount = Curve.findExtrema(y_coeff, tExtrema);
for(int i = 0; i < tExtremaCount; i++) {
double t = tExtrema[i];
if (t > 0 && t < 1) {
double y = y_coeff[0] + t * (y_coeff[1] + t * (y_coeff[2] + t * y_coeff[3]));
topY = (y < topY) ? y : topY;
bottomY = (y > bottomY) ? y : bottomY;
}
}
}

lastX = endX;
lastY = endY;
}
if (!isEmpty) {
return new Rectangle2D.Double(leftX, topY, rightX - leftX, bottomY - topY);
}

// there's room to debate what should happen here, but historically we return a zeroed
// out rectangle here. So for backwards compatibility let's keep doing that:
return new Rectangle2D.Double();
}

/**
* Tests if the specified coordinates are inside the closed
* boundary of the specified {@link PathIterator}.
34 changes: 8 additions & 26 deletions src/java.desktop/share/classes/java/awt/geom/QuadCurve2D.java
Original file line number Diff line number Diff line change
@@ -238,19 +238,6 @@ public void setCurve(float x1, float y1,
this.y2 = y2;
}

/**
* {@inheritDoc}
* @since 1.2
*/
public Rectangle2D getBounds2D() {
float left = Math.min(Math.min(x1, x2), ctrlx);
float top = Math.min(Math.min(y1, y2), ctrly);
float right = Math.max(Math.max(x1, x2), ctrlx);
float bottom = Math.max(Math.max(y1, y2), ctrly);
return new Rectangle2D.Float(left, top,
right - left, bottom - top);
}

/**
* Use serialVersionUID from JDK 1.6 for interoperability.
*/
@@ -428,19 +415,6 @@ public void setCurve(double x1, double y1,
this.y2 = y2;
}

/**
* {@inheritDoc}
* @since 1.2
*/
public Rectangle2D getBounds2D() {
double left = Math.min(Math.min(x1, x2), ctrlx);
double top = Math.min(Math.min(y1, y2), ctrly);
double right = Math.max(Math.max(x1, x2), ctrlx);
double bottom = Math.max(Math.max(y1, y2), ctrly);
return new Rectangle2D.Double(left, top,
right - left, bottom - top);
}

/**
* Use serialVersionUID from JDK 1.6 for interoperability.
*/
@@ -1335,6 +1309,14 @@ public boolean contains(Rectangle2D r) {
return contains(r.getX(), r.getY(), r.getWidth(), r.getHeight());
}

/**
* {@inheritDoc}
* @since 1.2
*/
public Rectangle2D getBounds2D() {
return Path2D.getBounds2D(getPathIterator(null));
}

/**
* {@inheritDoc}
* @since 1.2
66 changes: 66 additions & 0 deletions src/java.desktop/share/classes/sun/awt/geom/Curve.java
Original file line number Diff line number Diff line change
@@ -712,6 +712,72 @@ public static int rectCrossingsForCubic(int crossings,
return crossings;
}

/**
* Return the t values that correspond to possible extrema in a given cubic function.
* <p>
* If the coefficient of the t^3 is large then the polynomial is a cubic and up to
* two values may be returned. If that coefficient is zero then the polynomial
* is a quadratic and up to one value may be returned. But if that coefficient is
* small then this method considers the possibility it could be either, so in that
* scenario this method may return up to three values. For ex: if the leading
* coefficient is .000001 that might be because the polynomial really is a cubic, or
* it might be because of rounding error and the polynomial is basically a quadratic.
* </p>
*
* @param coefficients four coefficients for a cubic polynomial equation. The nth element in this array is
* the coefficient for (t^n).
* @param dest an array to store the t values in. This must be at least 3 elements.
* @return the number of t-values that were stored in dest. This will be between 0-3.
*/
public static int findExtrema(double[] coefficients, double[] dest) {

int returnValue = 0;

if (coefficients[3] != 0.0) {
// evaluate this as a cubic, where:

// f(t) = c[3] * t^3 + c[2] * t^2 + c[1] * t + c[0]
// df/dt = 3 * c[3] * t^2 + 2 * c[2] * t + c[1]

// so we have a quadratic polynomial:
// df/dt = A * t^2 + B * t + C

// ... where:
// A = 3 * c[3]
// B = 2 * c[2]
// C = c[1]

double[] eqn = new double[]{ coefficients[1],
2.0 * coefficients[2],
3.0 * coefficients[3] };
returnValue = QuadCurve2D.solveQuadratic(eqn, dest);

if (returnValue < 0.0)
returnValue = 0;
}

if (coefficients[3] > -.01 && coefficients[3] < .01 && coefficients[2] != 0.0) {
// evaluate this as if it's a quadratic, where:

// f = c[2] * t^2 + c[1] * t + c[0]

// this only really makes sense if coefficients[3] is close to zero.
// We chose "less than .01" as the threshold for "close to zero". It's
// a very generous threshold, but it should be harmless to err on the
// side of a generously high threshold in this case. The worst-case
// scenario is: we return an extra t-value that isn't really an extrema.

// df/dt = 2 * c[2] * t + c[1]

// so our only extrema is at:
// t = -c[1] / (2*c[2])

double t = -coefficients[1] / (2.0 * coefficients[2]);
dest[returnValue++] = t;
}
return returnValue;
}

public Curve(int direction) {
this.direction = direction;
}
140 changes: 115 additions & 25 deletions test/jdk/java/awt/geom/Path2D/UnitTest.java
Original file line number Diff line number Diff line change
@@ -23,7 +23,7 @@

/*
* @test
* @bug 4172661
* @bug 4172661 8176501
* @summary Tests all public methods of Path2D classes on all 3 variants
* Path2D.Float, Path2D.Double, and GeneralPath.
* REMIND: Note that the hit testing tests will fail
@@ -132,6 +132,16 @@ public static void init() {
makeGeneralPath(WIND_EVEN_ODD, 1.0),
makeGeneralPath(WIND_NON_ZERO, -1.0),
makeGeneralPath(WIND_EVEN_ODD, -1.0),
makeJDK8176501(),

// this shape has a special property: some coefficients to the t^3 term
// are *nearly* zero. And analytically they should be zero, but machine
// error prevented it. In these cases cubic polynomials should degenerate
// into quadratic polynomials, but because the coefficient is not exactly
// zero that may not always be handled correctly:
AffineTransform.getRotateInstance(Math.PI / 4).createTransformedShape(
new Ellipse2D.Float(0, 0, 100, 100))

};

int types[] = new int[100];
@@ -193,6 +203,20 @@ public static GeneralPath makeGeneralPath(int windingrule, double sign) {
return gp;
}

/**
* JDK-8176501 focused on a shape whose bounds included a lot of dead space.
* This recreates that shape, and the unit test testGetBounds2D checks the
* accuracy of {@link Shape#getBounds2D()}
*/
public static Path2D makeJDK8176501() {
Path2D.Double path = new Path2D.Double();
path.moveTo(40, 140);
path.curveTo(40, 60, 160, 60, 160, 140);
path.curveTo(160, 220, 40, 220, 40, 140);
path.closePath();
return path;
}

// Due to odd issues with the sizes of errors when the values
// being manipulated are near zero, we try to avoid values
// near zero by ensuring that both the rpc (positive coords)
@@ -538,33 +562,11 @@ public Shape getTestShape() {
return testshape;
}

private Rectangle2D cachedBounds;
public Rectangle2D getCachedBounds2D() {
if (cachedBounds == null) {
double xmin, ymin, xmax, ymax;
int ci = 0;
xmin = xmax = theCoords[ci++];
ymin = ymax = theCoords[ci++];
while (ci < numCoords) {
double c = theCoords[ci++];
if (xmin > c) xmin = c;
if (xmax < c) xmax = c;
c = theCoords[ci++];
if (ymin > c) ymin = c;
if (ymax < c) ymax = c;
}
cachedBounds = new Rectangle2D.Double(xmin, ymin,
xmax - xmin,
ymax - ymin);
}
return cachedBounds;
}

public Rectangle getBounds() {
return getCachedBounds2D().getBounds();
return getBounds2D().getBounds();
}
public Rectangle2D getBounds2D() {
return getCachedBounds2D().getBounds2D();
return getTestShape().getBounds2D();
}
public boolean contains(double x, double y) {
return getTestShape().contains(x, y);
@@ -1297,6 +1299,7 @@ public static void testBounds(Creator c) {
if (verbose) System.out.println("bounds testing "+sref);
Shape stest = c.makePath(sref);
checkBounds(c.makePath(sref), sref);
testGetBounds2D(stest);
}
testBounds(c, ShortSampleNonZero);
testBounds(c, ShortSampleEvenOdd);
@@ -1312,6 +1315,93 @@ public static void testBounds(Creator c, SampleShape ref) {
checkBounds(ref.makeDoublePath(c), ref);
}

/**
* Make sure the {@link Shape#getBounds2D()} returns a Rectangle2D that tightly fits the
* shape data. It shouldn't contain lots of dead space (see JDK 8176501), and it shouldn't
* leave out any shape path. This test relies on the accuracy of
* {@link Shape#intersects(double, double, double, double)}
*/
public static void testGetBounds2D(Shape shape) {
// first: make sure the shape is actually close to the perimeter of shape.getBounds2D().
// this is the crux of JDK 8176501:

Rectangle2D r = shape.getBounds2D();

if (r.getWidth() == 0 || r.getHeight() == 0) {
// this can happen for completely empty paths, which are part of our
// edge test cases in this class.
return;
}

if (verbose) System.out.println("testGetBounds2D "+shape+", "+r);

double xminInterior = r.getMinX() + .000001;
double yminInterior = r.getMinY() + .000001;
double xmaxInterior = r.getMaxX() - .000001;
double ymaxInterior = r.getMaxY() - .000001;

Rectangle2D topStrip = new Rectangle2D.Double(r.getMinX(), r.getMinY(), r.getWidth(), yminInterior - r.getMinY());
Rectangle2D leftStrip = new Rectangle2D.Double(r.getMinX(), r.getMinY(), xminInterior - r.getMinX(), r.getHeight());
Rectangle2D bottomStrip = new Rectangle2D.Double(r.getMinX(), ymaxInterior, r.getWidth(), r.getMaxY() - ymaxInterior);
Rectangle2D rightStrip = new Rectangle2D.Double(xmaxInterior, r.getMinY(), r.getMaxX() - xmaxInterior, r.getHeight());
if (!shape.intersects(topStrip)) {
if (verbose)
System.out.println("topStrip = "+topStrip);
throw new RuntimeException("the shape must intersect the top strip of its bounds");
}
if (!shape.intersects(leftStrip)) {
if (verbose)
System.out.println("leftStrip = " + leftStrip);
throw new RuntimeException("the shape must intersect the left strip of its bounds");
}
if (!shape.intersects(bottomStrip)) {
if (verbose)
System.out.println("bottomStrip = " + bottomStrip);
throw new RuntimeException("the shape must intersect the bottom strip of its bounds");
}
if (!shape.intersects(rightStrip)) {
if (verbose)
System.out.println("rightStrip = " + rightStrip);
throw new RuntimeException("the shape must intersect the right strip of bounds");
}

// Similarly: make sure our shape doesn't exist OUTSIDE of r, either. To my knowledge this has never
// been a problem, but if it did happen this would be an even more serious breach of contract than
// the former case.

double xminExterior = r.getMinX() - .000001;
double yminExterior = r.getMinY() - .000001;
double xmaxExterior = r.getMaxX() + .000001;
double ymaxExterior = r.getMaxY() + .000001;

// k is simply meant to mean "a large number, functionally similar to infinity for this test"
double k = 10000.0;
leftStrip = new Rectangle2D.Double(xminExterior - k, -k, k, 3 * k);
rightStrip = new Rectangle2D.Double(xmaxExterior, -k, k, 3 * k);
topStrip = new Rectangle2D.Double(-k, yminExterior - k, 3 * k, k);
bottomStrip = new Rectangle2D.Double(-k, ymaxExterior, 3 * k, k);
if (shape.intersects(leftStrip)) {
if (verbose)
System.out.println("leftStrip = " + leftStrip);
throw new RuntimeException("the shape must not intersect anything to the left of its bounds");
}
if (shape.intersects(rightStrip)) {
if (verbose)
System.out.println("rightStrip = " + rightStrip);
throw new RuntimeException("the shape must not intersect anything to the right of its bounds");
}
if (shape.intersects(topStrip)) {
if (verbose)
System.out.println("topStrip = " + topStrip);
throw new RuntimeException("the shape must not intersect anything above its bounds");
}
if (shape.intersects(bottomStrip)) {
if (verbose)
System.out.println("bottomStrip = " + bottomStrip);
throw new RuntimeException("the shape must not intersect anything below its bounds");
}
}

public static void testHits(Creator c) {
for (int i = 0; i < TestShapes.length; i++) {
Shape sref = TestShapes[i];
87 changes: 87 additions & 0 deletions test/jdk/java/math/BigInteger/BigIntegerParallelMultiplyTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* Copyright (c) 1998, 2020, 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
* @run main BigIntegerParallelMultiplyTest
* @summary tests parallelMultiply() method in BigInteger
* @author Heinz Kabutz heinz@javaspecialists.eu
*/

import java.math.BigInteger;
import java.util.function.BinaryOperator;

/**
* This is a simple test class created to ensure that the results
* of multiply() are the same as multiplyParallel(). We calculate
* the Fibonacci numbers using Dijkstra's sum of squares to get
* very large numbers (hundreds of thousands of bits).
*
* @author Heinz Kabutz, heinz@javaspecialists.eu
*/
public class BigIntegerParallelMultiplyTest {
public static BigInteger fibonacci(int n, BinaryOperator<BigInteger> multiplyOperator) {
if (n == 0) return BigInteger.ZERO;
if (n == 1) return BigInteger.ONE;

int half = (n + 1) / 2;
BigInteger f0 = fibonacci(half - 1, multiplyOperator);
BigInteger f1 = fibonacci(half, multiplyOperator);
if (n % 2 == 1) {
BigInteger b0 = multiplyOperator.apply(f0, f0);
BigInteger b1 = multiplyOperator.apply(f1, f1);
return b0.add(b1);
} else {
BigInteger b0 = f0.shiftLeft(1).add(f1);
return multiplyOperator.apply(b0, f1);
}
}

public static void main(String[] args) throws Exception {
for (int n = 0; n <= 10; n++) {
BigInteger fib = fibonacci(n, BigInteger::multiply);
System.out.printf("fibonacci(%d) = %d%n", n, fib);
}

compare(1000, 324);
compare(10_000, 3473);
compare(100_000, 34883);
compare(1_000_000, 347084);
}

private static void compare(int n, int expectedBitCount) {
BigInteger multiplyResult = fibonacci(n, BigInteger::multiply);
BigInteger parallelMultiplyResult = fibonacci(n, BigInteger::parallelMultiply);
checkBitCount(n, expectedBitCount, multiplyResult);
checkBitCount(n, expectedBitCount, parallelMultiplyResult);
if (!multiplyResult.equals(parallelMultiplyResult))
throw new AssertionError("multiply() and parallelMultiply() give different results");
}

private static void checkBitCount(int n, int expectedBitCount, BigInteger number) {
if (number.bitCount() != expectedBitCount)
throw new AssertionError(
"bitCount of fibonacci(" + n + ") was expected to be " + expectedBitCount
+ " but was " + number.bitCount());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package org.openjdk.bench.java.math;

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.State;
import org.openjdk.jmh.annotations.Warmup;

import java.math.BigInteger;
import java.util.concurrent.TimeUnit;
import java.util.function.BinaryOperator;

/**
* Benchmark for checking performance difference between
* sequential and parallel multiply methods in BigInteger,
* using a large Fibonacci calculation of up to n = 100 million.
*
* @author Heinz Kabutz, heinz@javaspecialists.eu
*/
@BenchmarkMode(Mode.SingleShotTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Fork(value = 2, jvmArgsAppend = {"-XX:+UseParallelGC", "-Xmx16g", "-Xms16g", "-XX:+AlwaysPreTouch", "-XX:NewRatio=1", "-XX:SurvivorRatio=1"})
@Warmup(iterations = 2)
@Measurement(iterations = 2) // only 2 iterations because each one takes very long
@State(Scope.Thread)
public class BigIntegerParallelMultiply {
private static BigInteger fibonacci(int n, BinaryOperator<BigInteger> multiplyOperator) {
if (n == 0) return BigInteger.ZERO;
if (n == 1) return BigInteger.ONE;

int half = (n + 1) / 2;
BigInteger f0 = fibonacci(half - 1, multiplyOperator);
BigInteger f1 = fibonacci(half, multiplyOperator);
if (n % 2 == 1) {
BigInteger b0 = multiplyOperator.apply(f0, f0);
BigInteger b1 = multiplyOperator.apply(f1, f1);
return b0.add(b1);
} else {
BigInteger b0 = f0.shiftLeft(1).add(f1);
return multiplyOperator.apply(b0, f1);
}
}

@Param({"1000000", "10000000", "100000000"})
private int n;

@Benchmark
public void multiply() {
fibonacci(n, BigInteger::multiply);
}

@Benchmark
public void parallelMultiply() {
fibonacci(n, BigInteger::parallelMultiply);
}
}