This is an automated email from the ASF dual-hosted git repository. aherbert pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/commons-numbers.git
The following commit(s) were added to refs/heads/master by this push: new fdcef0ec NUMBERS-204: Update DoubleSplitPerformance with the sub-normal upscaling fdcef0ec is described below commit fdcef0ece359c54f8eb600f6b3d9d50e600a7116 Author: aherbert <aherb...@apache.org> AuthorDate: Thu Aug 31 10:22:39 2023 +0100 NUMBERS-204: Update DoubleSplitPerformance with the sub-normal upscaling --- .../numbers/examples/jmh/core/DoublePrecision.java | 162 +++++++++++++++++++++ .../examples/jmh/core/DoubleSplitPerformance.java | 11 +- 2 files changed, 171 insertions(+), 2 deletions(-) diff --git a/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/core/DoublePrecision.java b/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/core/DoublePrecision.java index c364f654..9df6c387 100644 --- a/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/core/DoublePrecision.java +++ b/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/core/DoublePrecision.java @@ -60,6 +60,9 @@ final class DoublePrecision { * Assuming the multiplier is above 2^27 and the maximum exponent is 1023 then a safe * limit is a value with an exponent of (1023 - 27) = 2^996. */ private static final double SAFE_UPPER = 0x1.0p996; + /** The lower limit for a product {@code x * y} below which the round-off component may be + * sub-normal. This is set as 2^-1022 * 2^54. */ + private static final double SAFE_LOWER = 0x1.0p-968; /** The scale to use when down-scaling during a split into a high part. * This must be smaller than the inverse of the multiplier and a power of 2 for exact scaling. */ @@ -69,6 +72,13 @@ final class DoublePrecision { * This is the inverse of {@link #DOWN_SCALE}. */ private static final double UP_SCALE = 0x1.0p30; + /** The upscale factor squared. */ + private static final double UP_SCALE2 = 0x1.0p60; + /** The downscale factor squared. */ + private static final double DOWN_SCALE2 = 0x1.0p-60; + /** The safe upper limit so the product {@code x * y} can be upscaled by 2^60. */ + private static final double SAFE_UPPER_S = 0x1.0p963; + /** The mask to zero the lower 27-bits of a long . */ private static final long ZERO_LOWER_27_BITS = 0xffff_ffff_f800_0000L; @@ -402,6 +412,158 @@ final class DoublePrecision { // This could be done at a higher limit (e.g. Math.abs(xy) > Double.MAX_VALUE / 4) // but is included here to have a single low probability branch condition. + // Add the absolute inputs for a single comparison. The sum will not be more than + // 3-fold higher than any component. + final double a = Math.abs(x); + final double b = Math.abs(y); + final double ab = Math.abs(xy); + if (a + b + ab >= SAFE_UPPER) { + // Only required to scale the largest number as x*y does not overflow. + if (a > b) { + return productLowUnscaled(x * DOWN_SCALE, y, xy * DOWN_SCALE) * UP_SCALE; + } + return productLowUnscaled(x, y * DOWN_SCALE, xy * DOWN_SCALE) * UP_SCALE; + } + + // The result is computed using a product of the low parts. + // To avoid underflow in the low parts we note that these are approximately a factor + // of 2^27 smaller than the original inputs so their product will be ~2^54 smaller + // than the product xy. Ensure the product is at least 2^54 above a sub-normal. + if (ab <= SAFE_LOWER) { + // Scaling up here is safe: the largest magnitude cannot be above SAFE_LOWER / MIN_VALUE. + return productLowUnscaled(x * UP_SCALE, y * UP_SCALE, xy * UP_SCALE2) * DOWN_SCALE2; + } + + // No scaling required + return productLowUnscaled(x, y, xy); + } + + + /** + * Compute the low part of the double length number {@code (z,zz)} for the exact + * product of {@code x} and {@code y}. This is equivalent to computing a {@code double} + * containing the magnitude of the rounding error when converting the exact 106-bit + * significand of the multiplication result to a 53-bit significand. + * + * <p>The method is written to be functionally similar to using a fused multiply add (FMA) + * operation to compute the low part, for example JDK 9's Math.fma function (note the sign + * change in the input argument for the product): + * <pre> + * double x = ...; + * double y = ...; + * double xy = x * y; + * double low1 = Math.fma(x, y, -xy); + * double low2 = DoublePrecision.productLow(x, y, xy); + * </pre> + * + * <p>Special cases: + * + * <ul> + * <li>If {@code x * y} is sub-normal or zero then the result is 0.0. + * <li>If {@code x * y} is infinite or NaN then the result is NaN. + * </ul> + * + * @param x First factor. + * @param y Second factor. + * @param xy Product of the factors (x * y). + * @return the low part of the product double length number + * @see #highPart(double) + * @see #productLow(double, double, double, double, double) + */ + static double productLowS(double x, double y, double xy) { + // Verify the input. This must be NaN safe. + //assert Double.compare(x * y, xy) == 0 + + // If the number is sub-normal, inf or nan there is no round-off. + if (isNotNormal(xy)) { + // Returns 0.0 for sub-normal xy, otherwise NaN for inf/nan: + return xy - xy; + } + + // The result xy is finite and normal. + // Use Dekker's mul12 algorithm that splits the values into high and low parts. + // Dekker's split using multiplication will overflow if the value is within 2^27 + // of double max value. It can also produce 26-bit approximations that are larger + // than the input numbers for the high part causing overflow in hx * hy when + // x * y does not overflow. So we must scale down big numbers. + // We only have to scale the largest number as we know the product does not overflow + // (if one is too big then the other cannot be). + // We also scale if the product is close to overflow to avoid intermediate overflow. + // This could be done at a higher limit (e.g. Math.abs(xy) > Double.MAX_VALUE / 4) + // but is included here to have a single low probability branch condition. + + // Add the absolute inputs for a single comparison. The sum will not be more than + // 3-fold higher than any component. + + // Note: To drop a branch to check for upscaling, we use a lower threshold than + // SAFE_UPPER in productLow + final double a = Math.abs(x); + final double b = Math.abs(y); + if (a + b + Math.abs(xy) >= SAFE_UPPER_S) { + // Only required to scale the largest number as x*y does not overflow. + if (a > b) { + return productLowUnscaled(x * DOWN_SCALE, y, xy * DOWN_SCALE) * UP_SCALE; + } + return productLowUnscaled(x, y * DOWN_SCALE, xy * DOWN_SCALE) * UP_SCALE; + } + + // Scaling up here is safe + return productLowUnscaled(x * UP_SCALE, y * UP_SCALE, xy * UP_SCALE2) * DOWN_SCALE2; + } + + /** + * Compute the low part of the double length number {@code (z,zz)} for the exact + * product of {@code x} and {@code y}. This is equivalent to computing a {@code double} + * containing the magnitude of the rounding error when converting the exact 106-bit + * significand of the multiplication result to a 53-bit significand. + * + * <p>The method is written to be functionally similar to using a fused multiply add (FMA) + * operation to compute the low part, for example JDK 9's Math.fma function (note the sign + * change in the input argument for the product): + * <pre> + * double x = ...; + * double y = ...; + * double xy = x * y; + * double low1 = Math.fma(x, y, -xy); + * double low2 = DoublePrecision.productLow(x, y, xy); + * </pre> + * + * <p>Special cases: + * + * <ul> + * <li>If {@code x * y} is sub-normal or zero then the result is 0.0. + * <li>If {@code x * y} is infinite or NaN then the result is NaN. + * </ul> + * + * @param x First factor. + * @param y Second factor. + * @param xy Product of the factors (x * y). + * @return the low part of the product double length number + * @see #highPart(double) + * @see #productLow(double, double, double, double, double) + */ + static double productLow0(double x, double y, double xy) { + // Verify the input. This must be NaN safe. + //assert Double.compare(x * y, xy) == 0 + + // If the number is sub-normal, inf or nan there is no round-off. + if (isNotNormal(xy)) { + // Returns 0.0 for sub-normal xy, otherwise NaN for inf/nan: + return xy - xy; + } + + // The result xy is finite and normal. + // Use Dekker's mul12 algorithm that splits the values into high and low parts. + // Dekker's split using multiplication will overflow if the value is within 2^27 + // of double max value. It can also produce 26-bit approximations that are larger + // than the input numbers for the high part causing overflow in hx * hy when + // x * y does not overflow. So we must scale down big numbers. + // We only have to scale the largest number as we know the product does not overflow + // (if one is too big then the other cannot be). + // We also scale if the product is close to overflow to avoid intermediate overflow. + // This could be done at a higher limit (e.g. Math.abs(xy) > Double.MAX_VALUE / 4) + // but is included here to have a single low probability branch condition. + // Add the absolute inputs for a single comparison. The sum will not be more than // 3-fold higher than any component. final double a = Math.abs(x); diff --git a/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/core/DoubleSplitPerformance.java b/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/core/DoubleSplitPerformance.java index 0cefa627..8fa46f29 100644 --- a/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/core/DoubleSplitPerformance.java +++ b/commons-numbers-examples/examples-jmh/src/main/java/org/apache/commons/numbers/examples/jmh/core/DoubleSplitPerformance.java @@ -355,8 +355,9 @@ public class DoubleSplitPerformance { * The name of the method. */ @Param({NONE, "multiply", "multiplyUnscaled", - "productLow", "productLow1", "productLow2", "productLow3", "productLowSplit", - "productLowUnscaled"}) + "productLow", "productLowS", + "productLow0", "productLow1", "productLow2", "productLow3", "productLowSplit", + "productLowUnscaled", "fma"}) private String name; /** The function. */ @@ -388,6 +389,10 @@ public class DoubleSplitPerformance { }; } else if ("productLow".equals(name)) { fun = (x, y) -> DoublePrecision.productLow(x, y, x * y); + } else if ("productLowS".equals(name)) { + fun = (x, y) -> DoublePrecision.productLowS(x, y, x * y); + } else if ("productLow0".equals(name)) { + fun = (x, y) -> DoublePrecision.productLow0(x, y, x * y); } else if ("productLow1".equals(name)) { fun = (x, y) -> DoublePrecision.productLow1(x, y, x * y); } else if ("productLow2".equals(name)) { @@ -404,6 +409,8 @@ public class DoubleSplitPerformance { }; } else if ("productLowUnscaled".equals(name)) { fun = (x, y) -> DoublePrecision.productLowUnscaled(x, y, x * y); + } else if ("fma".equals(name)) { + fun = (x, y) -> Math.fma(x, y, -x * y); } else { throw new IllegalStateException("Unknown round-off method: " + name); }