高效浮动到int而不溢出

时间:2016-04-05 16:35:52

标签: c++ optimization floating-point

using int_type = int;
int_type min = std::numeric_limits<Depth>::min();
int_type max = std::numeric_limits<Depth>::max();

int_type convert(float f) {
    if(f < static_cast<float>(min)) return min; // overflow
    else if(f > static_cast<float>(max)) return max; // overflow
    else return static_cast<int_type>(f);
}

是否有更有效的方法将float f转换为int_type,同时将其限制为整数类型的最小值和最大值? 例如,没有将minmax投射到float进行比较。

3 个答案:

答案 0 :(得分:1)

有时几乎总是如此,信任编译器是最好的事情。

此代码:

template<class Integral>
__attribute__((noinline))
int convert(float f)
{
    using int_type = Integral;
    constexpr int_type min = std::numeric_limits<int_type>::min();
    constexpr int_type max = std::numeric_limits<int_type>::max();

    constexpr float fmin = static_cast<float>(min);
    constexpr float fmax = static_cast<float>(max);

    if(f < fmin) return min; // overflow
    if(f > fmax) return max; // overflow
    return static_cast<int_type>(f);
}

使用-O2和-fomit-frame-pointer编译,产生:

__Z7convertIiEif:                       ## @_Z7convertIiEif
    .cfi_startproc
    movl    $-2147483648, %eax      ## imm = 0xFFFFFFFF80000000
    movss   LCPI1_0(%rip), %xmm1    ## xmm1 = mem[0],zero,zero,zero
    ucomiss %xmm0, %xmm1
    ja  LBB1_3
    movl    $2147483647, %eax       ## imm = 0x7FFFFFFF
    ucomiss LCPI1_1(%rip), %xmm0
    ja  LBB1_3
    cvttss2si   %xmm0, %eax
LBB1_3:
    retq

我不确定它会更有效率。

注意这里定义的LCPI_x:

    .section    __TEXT,__literal4,4byte_literals
    .align  2
LCPI1_0:
    .long   3472883712              ## float -2.14748365E+9
LCPI1_1:
    .long   1325400064              ## float 2.14748365E+9
  

如何使用fmin(),fmax()...进行钳位[感谢njuffa提问]

代码确实变得更有效,因为删除了条件跳转。但是,它在钳位限制时开始表现不正确。

考虑:

template<class Integral>
__attribute__((noinline))
int convert2(float f)
{
    using int_type = Integral;
    constexpr int_type min = std::numeric_limits<int_type>::min();
    constexpr int_type max = std::numeric_limits<int_type>::max();

    constexpr float fmin = static_cast<float>(min);
    constexpr float fmax = static_cast<float>(max);

    f = std::min(f, fmax);
    f = std::max(f, fmin);
    return static_cast<int_type>(f);
}

打电话
auto i = convert2<int>(float(std::numeric_limits<int>::max()));

结果:

-2147483648

显然我们需要通过epsilon来减少限制,因为浮点数无法准确表示int的整个范围,所以......

template<class Integral>
__attribute__((noinline))
int convert2(float f)
{
    using int_type = Integral;
    constexpr int_type min = std::numeric_limits<int_type>::min();
    constexpr int_type max = std::numeric_limits<int_type>::max();

    constexpr float fmin = static_cast<float>(min) - (std::numeric_limits<float>::epsilon() * static_cast<float>(min));
    constexpr float fmax = static_cast<float>(max) - (std::numeric_limits<float>::epsilon() * static_cast<float>(max));

    f = std::min(f, fmax);
    f = std::max(f, fmin);
    return static_cast<int_type>(f);
}

应该更好......

除了现在相同的函数调用产生:

2147483392

顺便说一句,处理这个问题实际上导致了我原始代码中的错误。由于相同的舍入错误问题,><运算符需要替换为>=<=

像这样:

template<class Integral>
__attribute__((noinline))
int convert(float f)
{
    using int_type = Integral;
    constexpr int_type min = std::numeric_limits<int_type>::min();
    constexpr int_type max = std::numeric_limits<int_type>::max();

    constexpr float fmin = static_cast<float>(min);
    constexpr float fmax = static_cast<float>(max);

    if(f <= fmin) return min; // overflow
    if(f >= fmax) return max; // overflow
    return static_cast<int_type>(f);
}

答案 1 :(得分:1)

对于32位整数,您可以让CPU为您完成一些钳位工作。

在超出范围的浮点数的情况下,cvtss2si指令实际上将返回0x80000000。这使您可以在大多数情况下消除一个测试:

int convert(float value)
{
    int result = _mm_cvtss_si32(_mm_load_ss(&value));
    if (result == 0x80000000 && value > 0.0f)
        result = 0x7fffffff;
    return result;
}

如果你有很多要转换的内容,那么_mm_cvtps_epi32允许你一次处理四个(溢出时具有相同的行为)。这应该比一次处理一个快得多,但是你需要以不同的方式构造代码才能使用它。

答案 2 :(得分:0)

如果要截断,可以利用avx2和avx指令512:

#include <float.h>

int main() {
    __m256 a = {5.423423, -4.243423, 423.4234234, FLT_MAX, 79.4234876, 19.7, 8.5454, 7675675.6};
    __m256i b = _mm256_cvttps_epi32(a);
    void p256_hex_u32(__m256i in) {
    alignas(32) uint32_t v[8];
    _mm256_store_si256((__m256i*)v, in);
    printf("v4_u32: %d %d %d %d %d %d %d %d\n", v[0], v[1], v[2], v[3], v[4], v[5], v[6], v[7]);
}

编译为:

g++ -std=c++17 -mavx2  a.cpp && ./a.out

并且对于mavx512(我的cpu不支持,因此我将不提供有效的测试,请随时进行编辑):

_mm512_maskz_cvtt_roundpd_epi64(k, value, _MM_FROUND_NO_EXC);