C 中的高效浮点模数一(浮点数的小数部分)

ele*_*ena 5 c floating-point x86 fpu modulo

我正在寻找一种非常有效的 CPU 方法来计算 C 中的浮点模数一(包括负值)。我使用它进行归一化相位缩减(包装,即 7.6 -> 0.6、0.2->0.2、-1.1 -> 0.9等等)。

据我了解,fmod() 和 Floor() 通常效率很低。我不需要严格的函数,即考虑 nan 或 inf,因为我负责传递有效值。

我一直在使用

m = x - (float)(int)x;
m += (float)(m<0.f);
// branchless form to add one if m is negative but not zero
Run Code Online (Sandbox Code Playgroud)

从基准来看,它通常比 fmod() 或使用 Floor() 代替 int 转换更有效,但我想知道是否存在更有效的方法,也许基于位操作......

我正在使用 gcc 在 64 位 intel cpu 上进行编码,但出于我的目的,我使用 32 位单精度浮点数。

如果其他地方已经解决了同样的问题,我深表歉意,但从我的搜索中我找不到任何关于这个特定主题的信息。

编辑:抱歉,我意识到最初发布的代码中有一个微妙的错误,所以我必须修复它。如果结果 (m) 为负数则必须加 1,如果 x 为负数则不必加 1

EDIT2:实际上,在 GCC 12 上使用 x-floor(x) 而不是 x-(float)(int)x 以及所有数学优化对相同函数进行基准测试后,我必须说前者更快,因为 GCC 显然足够聪明用非常高效的代码替换 Floor() 内联函数(至少在我的英特尔 i7 上是这样)。然而,对于每个 cpu 和编译器来说,情况可能并不总是如此,因为在其他情况下,根据个人经验,floor() 和 fmod() 的效率都非常低。因此,我对位操作或类似技巧的追求可能会更快,并且对于每个编译器和架构仍然适用

P K*_*mer 2

C++ 中的原型(我不是最新的 C),填充逻辑仍未优化,但如果您的系统上有 AVX512,您可以执行类似的操作来在一个循环中处理 8 个双精度数或 16 个浮点数。我在这里发现很多有用的东西:intrinsic cheatsheet

我使用 Visual Studio 2022 中的 MSVC 编译器

#include <type_traits>
#include <vector>
#include <immintrin.h>


void reduce_phases(std::vector<double>& inputs)
{
    static constexpr std::size_t vector_size = 512ul / sizeof(double);
    auto number_to_pad = vector_size - (inputs.size() % vector_size);
    inputs.insert(inputs.end(), number_to_pad, 0.0);

    auto data_ptr = inputs.data();
    
    for (std::size_t n{ 0ul }; n < inputs.size(); n += vector_size, data_ptr += vector_size)
    {
        auto values = _mm512_load_pd(data_ptr);
        auto floors = _mm512_floor_pd(values);
        auto result = _mm512_sub_pd(values, floors);
        _mm512_store_pd(data_ptr, result);
    }

    inputs.erase(inputs.end() - number_to_pad, inputs.end());
}

void reduce_phases(std::vector<float>& inputs)
{
    static constexpr std::size_t vector_size = 512ul / sizeof(float);

    auto number_to_pad = vector_size - (inputs.size() % vector_size);
    inputs.insert(inputs.end(), number_to_pad, 0.0);

    auto data_ptr = inputs.data();

    for (std::size_t n{ 0ul }; n < inputs.size(); n += vector_size, data_ptr += vector_size)
    {
        auto values = _mm512_load_ps(data_ptr);
        auto floors = _mm512_floor_ps(values);
        auto result = _mm512_sub_ps(values, floors);
        _mm512_store_ps(data_ptr, result);
    }

    inputs.erase(inputs.end() - number_to_pad, inputs.end());
}


int main()
{
    std::vector<double> values{ -1.1, -1.9, -1.5, -0.4, 0.0, 0.4, 1.5, 1.9, 2.1 };
    reduce_phases(values);

    std::vector<float> float_values{ -1.1f, -1.9f, -1.5f, -0.4f, 0.0f, 0.4f, 1.5f, 1.9f, 2.1f };
    reduce_phases(float_values);

    return 0;
}
Run Code Online (Sandbox Code Playgroud)