有没有一种方法可以从数学上确定一个值是否比另一个值更接近0?
例如,closerToZero(-2, 3)
将返回-2
。
我试着去掉符号,然后比较最小值,但我会分配初始数字的无符号版本。
a和b是符合IEEE-754的浮点双精度(js数(
(64位=>1位符号11位指数52位分数(
min (a,b) => b-((a-b)&((a-b)>>52));
result = min(abs(a), abs(b));
// result has the wrong sign ...
显而易见的算法是比较绝对值,并使用它来选择原始值。
如果这绝对需要无分支(例如,为了加密安全(,请小心使用? :
三元。它经常编译为无分支的asm,但这并不能保证。(我想这就是你标记分支预测的原因吧?如果只是出于性能考虑,编译器通常会做出好的决定。(
在具有固定的2补码整数的语言中,请记住abs(INT_MIN)
溢出与输入宽度相同的有符号结果在C和C++中,abs()
被设计成返回一个int
是不方便的,并且在2的补码系统上用最负的2补码整数来调用它是未定义的行为。在具有定义良好的包装有符号int数学的系统(如gcc -fwrapv
,或者可能是Java(上,有符号abs(INT_MIN)
会溢出回int_MIN,如果进行有符号比较,则会给出错误的结果,因为int_MIN离0的距离最大。
确保对abs
结果进行无符号比较,以便正确处理INT_MIN
(或者,正如@kaya3所建议的,将正整数映射为负,而不是将负映射为正。(
避免未定义行为的安全C实现:
unsigned absu(int x) {
return x<0? 0U - x : x;
}
int minabs(int a, int b) {
return absu(a) < absu(b) ? a : b;
}
注意,<
与<=
在minabs
中实际上很重要:如果它们的幅度相等,则决定选择哪一个。
0U - x
将x
转换为unsigned
,然后从0中减去可能溢出的值。在C和C++中,将负有符号整数类型转换为无符号整数类型是作为模缩减定义的(与浮点、UB IIRC不同(。在2的补码机上,这意味着使用相同的位模式而不改变。
这可以很好地编译x86-64(Godbolt(,尤其是使用clang。(即使使用-march=skylake
,GCC也会避免使用cmov
,最终会得到更差的序列。除了在执行两个absu操作后的最终选择外,它会使用cmovbe
,这是2个uops,而不是英特尔CPU上cmovb
的1,因为它需要读取ZF和CF标志。如果它在EAX中已经得到相反的值,它本可以使用cmovb
。(
# clang -O3
absu:
mov eax, edi
neg eax # sets flags like sub-from-0
cmovl eax, edi # select on signed less-than condition
ret
minabs:
mov ecx, edi
neg ecx
cmovl ecx, edi # inlined absu(a)
mov eax, esi
mov edx, esi
neg edx
cmovl edx, esi # inlined absu(b)
cmp ecx, edx # compare absu results
cmovb eax, edi # select on unsigned Below condition.
ret
具有GCC和clang的完全无分支,并启用了优化。可以肯定的是,其他ISAs也会如此。
它可能会很好地自动向量化,但x86直到AVX512才有SIMD无符号整数比较。(您可以通过翻转高位以使用带符号整数pcmpgtd
进行模拟(。
对于浮点/双精度,abs
更便宜,并且不会溢出:只需清除符号位,然后使用它来选择原始符号位。