9

How can one add two long values in Java so that if the result overflows then it is clamped to the range Long.MIN_VALUE..Long.MAX_VALUE?

For adding ints one can perform the arithmetic in long precision and cast the result back to an int, e.g.:

int saturatedAdd(int x, int y) {
  long sum = (long) x + (long) y;
  long clampedSum = Math.max((long) Integer.MIN_VALUE,
                             Math.min(sum, (long) Integer.MAX_VALUE));
  return (int) clampedSum;
}

or

import com.google.common.primitives.Ints;

int saturatedAdd(int x, int y) {
  long sum = (long) x + (long) y;
  return Ints.saturatedCast(sum);
}

but in the case of long there is no larger primitive type that can hold the intermediate (unclamped) sum.

Since this is Java, I cannot use inline assembly (in particular SSE's saturated add instructions.)

It can be implemented using BigInteger, e.g.

static final BigInteger bigMin = BigInteger.valueOf(Long.MIN_VALUE);
static final BigInteger bigMax = BigInteger.valueOf(Long.MAX_VALUE);

long saturatedAdd(long x, long y) {
    BigInteger sum = BigInteger.valueOf(x).add(BigInteger.valueOf(y));
    return bigMin.max(sum).min(bigMax).longValue();
}

however performance is important so this method is not ideal (though useful for testing.)

I don't know whether avoiding branching can significantly affect performance in Java. I assume it can, but I would like to benchmark methods both with and without branching.

Related: How to do saturating addition in C?

Community
  • 1
  • 1
finnw
  • 45,253
  • 22
  • 134
  • 212
  • Actually you can use the assembly, provided that you wrap it in JNI, or JNA. It'd be great to see a performance-wise between the proposed solutions. – janislaw Nov 01 '12 at 10:39

4 Answers4

5

You should be able to to break it into four cases based on the sign of the numbers: If one of the numbers is zero, the answer is the other number. If one is positive and another negative, then you can't over or underflow. If both are positive you can only overflow. If both are negative you can only underflow.

Just do an extra calculation for the last two cases to see if it will result in the undesired case:

if(x == 0 || y == 0 || (x > 0 ^ y > 0)){
  //zero+N or one pos, another neg = no problems
  return x+y;
}else if(x > 0){
  //both pos, can only overflow
  return Long.MAX_VALUE - x < y ? Long.MAX_VALUE : x+y;
}else{
  //both neg, can only underflow
  return Long.MIN_VALUE - x > y ? Long.MIN_VALUE : x+y;
}
M. Jessup
  • 7,746
  • 1
  • 23
  • 29
3

Here's my attempt at a branch-free version:

long saturatedAdd(long x, long y) {
    // Sum ignoring overflow/underflow
    long s = x + y;

    // Long.MIN_VALUE if result positive (potential underflow)
    // Long.MAX_VALUE if result negative (potential overflow)
    long limit = Long.MIN_VALUE ^ (s >> 63);

    // -1 if overflow/underflow occurred, 0 otherwise
    long overflow = ((x ^ s) & ~(x ^ y)) >> 63;

    // limit if overflowed/underflowed, else s
    return ((limit ^ s) & overflow) ^ s;
}
finnw
  • 45,253
  • 22
  • 134
  • 212
2

You can also use the built-in saturation mechanism of type-casting:

int saturatedAdd(int x, int y) {
    return (int)(x + (double) y);
}

x and y are added as double, and casting to int will saturate to the [Integer.MIN_VALUE, Integer.MAX_VALUE] range.

This is not as suitable for longs as precision of double is less than that of long, but if the precision is not as important, it will suffice.

maaartinus
  • 40,991
  • 25
  • 130
  • 292
Lajos Molnar
  • 711
  • 5
  • 8
0

Let's start with a simple form with comments:

long saturatedAdd(long x, long y) {
    long r = x + y;

    // Addition is safe from overflow if x and y have different signs
    if ((x < 0) != (y < 0)) {
        return r;
    }

    // Result has overflowed if the resulting sign is different
    if ((r < 0) != (x < 0)) {
        return x < 0 ? Long.MIN_VALUE : Long.MAX_VALUE;
    }

    // Otherwise result has not overflowed
    return r;
}

Although there's nothing wrong with using this implementation, what follows is an attempt to micro-"optimize" it just for the sake of argument.

The (x < 0) != (y < 0) can be changed to (x ^ y) < 0 which is essentially a bitwise XOR of the sign bit.

    // Addition safe from overflow if x and y have different signs
    if ((x ^ y) < 0) {
        return r;
    }

    // Result has overflowed if resulting sign is different
    if ((r ^ x) < 0) {
        return x < 0 ? Long.MIN_VALUE : Long.MAX_VALUE;
    }

Further, we could forcibly put the two ifs together by writing (x ^ y) < 0 || (r ^ x) >= 0 or even ((x ^ y) | ~(r ^ x)) < 0. At this point it stops being readable:

    if (((x ^ y) | ~(r ^ x)) < 0) {
        return r;
    }
    return x < 0 ? Long.MIN_VALUE : Long.MAX_VALUE;

We could take a hint from Math.exactAdd() and turn that if into ((r ^ x) & (r ^ y)) < 0. It doesn't improve readability but looks "cool" and is more symmetrical:

    if (((r ^ x) & (r ^ y)) < 0) {
        return x < 0 ? Long.MIN_VALUE : Long.MAX_VALUE;
    }
    return r;

Whew, that's a bit of a leap. Essentially it check if the result has a different sign to both of the inputs, which is only possible if both inputs have the same sign AND resulting sign is different to that.

Moving along, adding 1 to Long.MAX_VALUE results in Long.MIN_VALUE:

    if (((r ^ x) & (r ^ y)) < 0) {
        return Long.MAX_VALUE + (x < 0 ? 1 : 0);
    }
    return r;

Another way to derive one when x < 0 is to use that sign bit as one.

    if (((r ^ x) & (r ^ y)) < 0) {
        return Long.MAX_VALUE + (x >>> (Long.SIZE - 1));
    }

Finally, just for the sake of symmetry, change to use r in that bit-shift instead of x, giving us:

    long r = x + y;
    if (((r ^ x) & (r ^ y)) < 0) {
        return Long.MIN_VALUE - (r >>> (Long.SIZE - 1));
    }
    return r;
antak
  • 15,500
  • 7
  • 59
  • 72