【问题标题】:Mathematically find the value that is closest to 0数学上找到最接近 0 的值
【发布时间】:2021-03-22 21:13:39
【问题描述】:

有没有办法用数学方法确定一个值是否比另一个值更接近 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 ... 

【问题讨论】:

  • 其他语言可能有 argmin 而不是 min。如果该语言的标准库中确实没有这样的功能,那么通过对值列表进行简单的迭代来编写代码是很容易的。但是,您的问题并未询问特定语言。事实上,你的问题究竟是什么真的不清楚。
  • 在数学中你会这样写:a 如果 abs(a)
  • @Henry:在使用 2 补码整数的固定语言中,请确保对 abs 结果进行无符号比较,以便正确处理 abs(INT_MIN)(如果已签名)会溢出到 INT_MIN,即使它最大程度地远离 0。但是,abs(a) < (unsigned) abs(b) ? a : b 应该在 abs 的返回值被愚蠢地签名的语言中工作。除了仍然涉及 C 中的带符号溢出,这是未定义的行为,如果 abs 实际上已完成签名。所以你真的很想将自己的absu(int) 实现为return x<0? 0U - x : x;
  • 您可以通过将正数映射为负数来避免 INT_MIN 问题,反之亦然。
  • ab 整数还是浮点数?

标签: algorithm math micro-optimization branch-prediction absolute-value


【解决方案1】:

显而易见的算法是比较绝对值,然后用它选择原始值。

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

在使用 2 补码整数的固定语言中,请记住 abs(INT_MIN) 会溢出与输入宽度相同的有符号结果。 In C and C++, abs() 设计为返回 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;
}

请注意,&lt;&lt;= 实际上在 minabs 中很重要:如果它们的大小相等,则决定选择哪一个。

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

这对于 x86-64 (Godbolt) 编译得很好,尤其是对于 clang。 (即使使用-march=skylake,GCC 也会避免cmov,最终得到更糟糕的序列。除了在执行两个absu 操作后的最终选择之外,它使用cmovbe,在Intel CPU 上cmovb 是2 微指令而不是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 更便宜且不会溢出:只需清除符号位,然后使用它来选择原始位。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-03-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-21
    • 2012-05-15
    • 2012-08-09
    相关资源
    最近更新 更多