饱和地添加了两个签名的Java“long”值

时间:2010-04-13 19:01:59

标签: java bit-manipulation math signal-processing integer-overflow

如何在Java中添加两个long值,以便在结果溢出时将其限制在范围Long.MIN_VALUE .. Long.MAX_VALUE

对于添加整数,可以用long精度执行算术并将结果转换回int,例如:

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;
}

import com.google.common.primitives.Ints;

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

但是在long的情况下,没有更大的原始类型可以保存中间(非夹紧)总和。

由于这是Java,我不能使用inline assembly(特别是SSE的饱和添加指令。)

可以使用BigInteger来实现,例如

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();
}
然而,性能很重要,因此这种方法并不理想(尽管对测试很有用。)

我不知道避免分支是否会显着影响Java的性能。我认为它可以,但我想对有和没有分支的方法进行基准测试。

相关:How to do saturating addition in C?

4 个答案:

答案 0 :(得分:5)

您应该能够根据数字的符号将其分为四种情况: 如果其中一个数字为零,则答案是另一个数字。 如果一个是积极的而另一个是消极的,那么你不能上下颠倒。 如果两者都是正数,则只能溢出。 如果两者都是负数,则只能下溢。

只需对最后两个案例进行额外计算,看看是否会导致不良情况:

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;
}

答案 1 :(得分:2)

这是我对无分支版本的尝试:

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;
}

答案 2 :(得分:2)

您还可以使用类型转换的内置饱和机制:

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

xy被添加为double,而投放到int将会累移到[Integer.MIN_VALUE, Integer.MAX_VALUE]范围。

这不适合long,因为double的精度小于long的精度,但如果精度不那么重要,那就足够了。

答案 3 :(得分:0)

让我们从一个带注释的简单表单开始:

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;
}

虽然使用此实现没有任何问题,但接下来是为了论证而尝试微观“优化”它。

(x < 0) != (y < 0)可以更改为(x ^ y) < 0,这基本上是符号位的XOR位。

    // 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;
    }

此外,我们可以通过撰写if甚至(x ^ y) < 0 || (r ^ x) >= 0强行将两个((x ^ y) | ~(r ^ x)) < 0放在一起。此时它不再可读:

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

我们可以从Math.exactAdd()获取提示,然后将if转换为((r ^ x) & (r ^ y)) < 0。它不会提高可读性,但看起来“酷”并且更加对称:

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

哇,这是一个小小的飞跃。基本上它会检查结果是否对两个输入都有不同的符号,这只有在两个输入具有相同符号时才会生效结果符号不同于该

继续前进,将1添加到Long.MAX_VALUE会产生Long.MIN_VALUE

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

x < 0使用符号位作为一个时,派生一个的另一种方法。

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

最后,只是为了对称,更改为使用r代替x而不是 long r = x + y; if (((r ^ x) & (r ^ y)) < 0) { return Long.MIN_VALUE - (r >>> (Long.SIZE - 1)); } return r; ,给我们:

dic = {}
with open("C:\\Users\\vWX442280\Desktop\\f1.txt" ,'r') as f:
    for line in f:
        l1 = line.split(" ")
        for w in l1:
            dic[w] = dic.get(w,0)+1
print ('\n'.join(['%s,%s' % (k, v) for k, v in dic.items()]))