如何在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的性能。我认为它可以,但我想对有和没有分支的方法进行基准测试。
答案 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);
}
x
和y
被添加为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()]))