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

Mathematically find the value that is closest to 0

有没有办法从数学上确定一个值是否比另一个值更接近 0?

例如 closerToZero(-2, 3) 会 return -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 ... 

显而易见的算法是比较绝对值,并将其用于select原始值。

如果这绝对需要无分支(例如为了加密安全),请小心 ? : 三进制。它通常编译为无分支 asm,但这并不能保证。 (我假设这就是你标记 的原因?如果只是出于性能考虑,编译器通常会做出正确的决定。)

在具有固定的 2 补码整数的语言中,请记住 abs(INT_MIN) 会溢出与输入宽度相同的带符号结果。 In C and C++, abs() 是不方便地设计为 return 和 int 并且在 2 的补码系统上用最负的 2 的补码整数调用它是未定义的行为。在具有明确包装的有符号整数数学(如 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 中很重要:如果它们的大小相等,则决定 select 哪个。

0U - xx 转换为 unsigned 从 0 减去可能溢出之前。将负有符号整数类型转换为无符号整数在 C 和 C++ 中明确定义为模缩减(与浮点数不同,UB IIRC)。在 2 的补码机器上,这意味着使用相同的位模式不变。

这对 x86-64 (Godbolt) 编译得很好,尤其是对 clang。 (即使使用 -march=skylake,GCC 也避免了 cmov,结果序列更糟。除了在执行两个 absu 操作后的最终 select,然后它使用 cmovbe,即 2 微指令而不是 Intel 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 完全无分支,启用优化。可以肯定的是,其他 ISA 也将是相同的。

它可能会很好地自动矢量化,但 x86 在 AVX512 之前没有 SIMD 无符号整数比较。 (您可以通过翻转高位来使用有符号整数来模拟pcmpgtd)。

对于 float / double,abs 更便宜且不会溢出:只需清除符号位,然后将其用于 select 原始值。