Implementation of `ulp` and `nextAfter` for platforms where these are missing
The code looks neat enough, well formatted, etc. That's great. There is one common issue you have in your code, and that is the else-if for early-return blocks.

The code:

if (conditionA) {
    ... do something
    return something;
} else if (conditionB) {
    ... do something
    return something;
} else if .....


has redundant if blocks. The logic is right, but, it would be better as:

if (conditionA) {
    ... do something
    return something;
}
if (conditionB) {
    ... do something
    return something;
}
if .....


You have that pattern in a lot of places.

A second general concern I have is that you do not specify 'final' for your parameters in places where it can make sense. The 'final' concept is debatable for performance, but I have found that it makes a difference in both the disciple, and occasionally the performance. For example, the function:


private boolean isSameSign(double x, double y) {
    return copySign(x, y) == x;
}



would be better as:

private boolean isSameSign(final double x, final double y) {
    return copySign(x, y) == x;
}


ulp()

It would be convenient in your ULP to do the d = Math.abs(d) earlier in the function as it simplifies some logic. Consider the following:

public double ulp(final double input) {
    final double d = Math.abs(input);
    if (Double.isNaN(d)) {
        // If the argument is NaN, then the result is NaN.
        return Double.NaN;
    }
    if (Double.isInfinite(d)) {
        // If the argument is positive or negative infinity, then the
        // result is positive infinity.
        return Double.POSITIVE_INFINITY;
    }
    if (d == 0.0) {
        // If the argument is positive or negative zero, then the result is Double.MIN_VALUE.
        return Double.MIN_VALUE;
    }
    if (d == Double.MAX_VALUE) {
        //If the argument is ±Double.MAX_VALUE, then the result is equal to 2^971.
        return MAX_ULP;
    }
    return nextAfter(d, Double.MAX_VALUE) - d;
}


I assume that your research on \$2^{971}\$ is accurate for Double.MAX_VALUE.

copySign()

Edit: This method confused me, and lead me to introduce a bug in my code (now fixed).

It is my opinion that the arguments are the wrong way around. I would expect copySign(x, y) to copy the sign from x to y, but your code implements it the other way around. Instead of arguments x and y, they should have better names, and the method should be named differently as well:

public double copySignToFrom(final double target, final double sign) {...}


My preference would be to just reverse the arguments, but that method is overridden, and the implementation is not actually shown here (Note: Show all your code next time...).

nextAfter()

Again, like ulp(), this function would benefit from removing the 'else if' construct. The early return makes it unnecessary.

The logic in here looks good. I have tried to see if there's a better way of doing things, and, I think there may be... by recording the signs of the start &amp; direction, you can do all the math in absolute terms, and re-conevrt the signs later.... It's a toss up as to whether the performance will be better to do the conversion each time, and then have simpler conditions on the logic.

So, the only real criticism I have here is the else if, but, for interest, consider the following:

Edit: revised my code example because of copySign usage bug...

public class DoublePlay {

    private static final long MASK = Long.MIN_VALUE;

//    @Override
    public double copySign(double target, double source) {
        if ((Double.doubleToLongBits(target) &amp; MASK) == (Double.doubleToLongBits(source) &amp; MASK)) {
            return target;
        }
        return Double.longBitsToDouble(Double.doubleToLongBits(target) ^ MASK);
    }

    private boolean isSameSign(double x, double y) {
        return copySign(x, y) == x;
    }

    public double nextAfter(final double start, final double direction) {
        if (Double.isNaN(start) || Double.isNaN(direction)) {
            // If either argument is a NaN, then NaN is returned.
            return Double.NaN;
        }
        if (start == direction) {
            // If both arguments compare as equal the second argument is returned.
            return direction;
        }

        final double absstart = Math.abs(start);
        final double absdir = Math.abs(direction);
        final boolean tozero = !isSameSign(start, direction) || absdir &lt; absstart;

        if (tozero) {
            // we are reducing the magnitude, going toward zero.
            if (absstart == Double.MIN_VALUE) {
                return copySign(0.0, start);
            }
            if (Double.isInfinite(absstart)) {
                return copySign(Double.MAX_VALUE, start);
            }
            return copySign(Double.longBitsToDouble(Double.doubleToLongBits(absstart) - 1L), start);
        }

        // we are increasing the magnitude, toward +-Infinity
        if (absstart == Double.MAX_VALUE) {
            return copySign(Double.POSITIVE_INFINITY, start);
        }
        return copySign(Double.longBitsToDouble(Double.doubleToLongBits(absstart) +1L), start);

    }

    private static final void testValue(DoublePlay dp, final double base, final double direction) {
        double result = dp.nextAfter(base, direction);
        System.out.printf("From: %016x %s%nStep: %016x %s%nTo  : %016x %s%n%n", 
                Double.doubleToLongBits(base), String.valueOf(base),
                Double.doubleToLongBits(direction), String.valueOf(direction),
                Double.doubleToLongBits(result), String.valueOf(result)
                );
    }

    public static void main(String[] args) {
        DoublePlay dp = new DoublePlay();
        testValue(dp, Double.MIN_VALUE, Double.POSITIVE_INFINITY);
        testValue(dp, Double.MIN_VALUE, Double.NEGATIVE_INFINITY);
        testValue(dp, -Double.MIN_VALUE, Double.POSITIVE_INFINITY);
        testValue(dp, -Double.MIN_VALUE, Double.NEGATIVE_INFINITY);
    }

}


The above code makes the decision 'early' about whether the magnitude of the nextAfter() is increasing (to infinity), or decreasing (to zero). It simplifies the rest of the conditions.

Added an Ideone with the code as well