两个有符号 Java“长”的饱和加法价值观
如何在 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;
}
or
import com.google.common.primitives.Ints;
int saturatedAdd(int x, int y) {
long sum = (long) x + (long) y;
return Ints.saturatedCast(sum);
}
但在 long
的情况下没有更大的原始类型可以容纳中间(未钳位)和。
由于这是Java,我无法使用内联汇编(特别是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 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?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
您应该能够根据数字的符号将其分为四种情况:
如果其中一个数字为零,则答案是另一个数字。
如果一个为正,另一个为负,那么就不能溢出或下溢。
如果两者都是正数,则只能溢出。
如果两者都是负数,则只能下溢。
只需对最后两种情况进行额外的计算,看看是否会导致不需要的情况:
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:
这是我对无分支版本的尝试:
Here's my attempt at a branch-free version:
您还可以使用类型转换的内置饱和机制:
将
x
和y
添加为double
,并转换为int
将饱和到[Integer.MIN_VALUE, Integer.MAX_VALUE]
范围。这不太适合
long
,因为double
的精度小于long
,但如果精度不那么重要,则可以就足够了。You can also use the built-in saturation mechanism of type-casting:
x
andy
are added asdouble
, and casting toint
will saturate to the[Integer.MIN_VALUE, Integer.MAX_VALUE]
range.This is not as suitable for
long
s as precision ofdouble
is less than that oflong
, but if the precision is not as important, it will suffice.让我们从一个带有注释的简单形式开始:
虽然使用这个实现没有任何问题,但接下来只是为了论证而尝试对其进行微观“优化”。
(x < 0) != (y < 0)
可以更改为(x ^ y) < 0
本质上是符号位的按位XOR
。此外,我们可以通过编写
(x ^ y)
(x ^ y)
来强制将两个
甚至if
放在一起。 0 || (r ^ x) >= 0((x ^ y) | ~(r ^ x))
0 。此时它不再可读:
我们可以从
Math.exactAdd()
中获取提示,并将if
转换为((r ^ x) & ( r ^ y)) < 0 。它并没有提高可读性,但看起来“很酷”并且更加对称:
唷,这有点飞跃了。本质上,它检查结果是否与两个输入具有不同的符号,只有当两个输入具有相同的符号并且结果符号不同于那个。
接下来,将
1
添加到Long.MAX_VALUE
会得到Long.MIN_VALUE
:当
x
x
时导出 1 的另一种方法。 0
是将符号位用作1。最后,为了对称性,在位移中使用
r
而不是x
,得到:Let's start with a simple form with comments:
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 bitwiseXOR
of the sign bit.Further, we could forcibly put the two
if
s together by writing(x ^ y) < 0 || (r ^ x) >= 0
or even((x ^ y) | ~(r ^ x)) < 0
. At this point it stops being readable:We could take a hint from
Math.exactAdd()
and turn thatif
into((r ^ x) & (r ^ y)) < 0
. It doesn't improve readability but looks "cool" and is more symmetrical: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
toLong.MAX_VALUE
results inLong.MIN_VALUE
:Another way to derive one when
x < 0
is to use that sign bit as one.Finally, just for the sake of symmetry, change to use
r
in that bit-shift instead ofx
, giving us: