用数学方法找到最接近0的值



有没有一种方法可以从数学上确定一个值是否比另一个值更接近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 - xx转换为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更便宜,并且不会溢出:只需清除符号位,然后使用它来选择原始符号位。

相关内容

  • 没有找到相关文章

最新更新