我需要将一个浮点数截断为10的最接近的幂.例如,1.1将截断为1.0,4.7e3将截断为1e3.我目前正在用看似复杂的powf(10,floorf(log10f(x))).我想知道是否有一种性能更好(比如执行速度更快)的解决方案?我的目标CPU架构是x86-64和arm64.

#include <stdio.h>
#include <math.h>

int main()
{
  float x = 1.1e5f;
  while (x > 1e-6f)
  {
    float y = powf(10,floorf(log10f(x)));
    printf("%e ==> %g\n", x, y); 
    x /= 5.0f;
  }
}

运行时,会产生

1.100000e+05 ==> 100000
2.200000e+04 ==> 10000
4.400000e+03 ==> 1000
8.800000e+02 ==> 100
1.760000e+02 ==> 100
3.520000e+01 ==> 10
7.040000e+00 ==> 1
1.408000e+00 ==> 1
2.816000e-01 ==> 0.1
5.632000e-02 ==> 0.01
1.126400e-02 ==> 0.01
2.252800e-03 ==> 0.001
4.505600e-04 ==> 0.0001
9.011199e-05 ==> 1e-05
1.802240e-05 ==> 1e-05
3.604480e-06 ==> 1e-06

推荐答案

可以使用查找表来加速计算.这种技术应该适用于所有normal个浮点数.如果没有一些专门的逻辑,次正常数和NaN将无法工作,0和无穷大可以通过表中的极值来处理.

尽管我预计这种技术实际上比最初的实现更快,但还是需要进行测量.

代码使用C++20 std::bit_castfloat值中提取指数.如果不可用,还有其他较老的技术,如frexpf.

#include <bit>
#include <cstdint>
#include <cstdio>
#include <limits>

constexpr float magnitudeLUT[] = { 
    0.f,    1e-38f, 1e-38f, 1e-38f, 1e-38f, 1e-37f, 1e-37f, 1e-37f, 1e-36f, 1e-36f, 1e-36f, 1e-35f, 
    1e-35f, 1e-35f, 1e-35f, 1e-34f, 1e-34f, 1e-34f, 1e-33f, 1e-33f, 1e-33f, 1e-32f, 1e-32f, 1e-32f, 
    1e-32f, 1e-31f, 1e-31f, 1e-31f, 1e-30f, 1e-30f, 1e-30f, 1e-29f, 1e-29f, 1e-29f, 1e-28f, 1e-28f, 
    1e-28f, 1e-28f, 1e-27f, 1e-27f, 1e-27f, 1e-26f, 1e-26f, 1e-26f, 1e-25f, 1e-25f, 1e-25f, 1e-25f, 
    1e-24f, 1e-24f, 1e-24f, 1e-23f, 1e-23f, 1e-23f, 1e-22f, 1e-22f, 1e-22f, 1e-22f, 1e-21f, 1e-21f, 
    1e-21f, 1e-20f, 1e-20f, 1e-20f, 1e-19f, 1e-19f, 1e-19f, 1e-19f, 1e-18f, 1e-18f, 1e-18f, 1e-17f, 
    1e-17f, 1e-17f, 1e-16f, 1e-16f, 1e-16f, 1e-16f, 1e-15f, 1e-15f, 1e-15f, 1e-14f, 1e-14f, 1e-14f, 
    1e-13f, 1e-13f, 1e-13f, 1e-13f, 1e-12f, 1e-12f, 1e-12f, 1e-11f, 1e-11f, 1e-11f, 1e-10f, 1e-10f, 
    1e-10f, 1e-10f, 1e-09f, 1e-09f, 1e-09f, 1e-08f, 1e-08f, 1e-08f, 1e-07f, 1e-07f, 1e-07f, 1e-07f, 
    1e-06f, 1e-06f, 1e-06f, 1e-05f, 1e-05f, 1e-05f, 1e-04f, 1e-04f, 1e-04f, 1e-04f, 1e-03f, 1e-03f, 
    1e-03f, 1e-02f, 1e-02f, 1e-02f, 1e-01f, 1e-01f, 1e-01f, 1e+00f, 1e+00f, 1e+00f, 1e+00f, 1e+01f, 
    1e+01f, 1e+01f, 1e+02f, 1e+02f, 1e+02f, 1e+03f, 1e+03f, 1e+03f, 1e+03f, 1e+04f, 1e+04f, 1e+04f, 
    1e+05f, 1e+05f, 1e+05f, 1e+06f, 1e+06f, 1e+06f, 1e+06f, 1e+07f, 1e+07f, 1e+07f, 1e+08f, 1e+08f, 
    1e+08f, 1e+09f, 1e+09f, 1e+09f, 1e+09f, 1e+10f, 1e+10f, 1e+10f, 1e+11f, 1e+11f, 1e+11f, 1e+12f, 
    1e+12f, 1e+12f, 1e+12f, 1e+13f, 1e+13f, 1e+13f, 1e+14f, 1e+14f, 1e+14f, 1e+15f, 1e+15f, 1e+15f, 
    1e+15f, 1e+16f, 1e+16f, 1e+16f, 1e+17f, 1e+17f, 1e+17f, 1e+18f, 1e+18f, 1e+18f, 1e+18f, 1e+19f, 
    1e+19f, 1e+19f, 1e+20f, 1e+20f, 1e+20f, 1e+21f, 1e+21f, 1e+21f, 1e+21f, 1e+22f, 1e+22f, 1e+22f, 
    1e+23f, 1e+23f, 1e+23f, 1e+24f, 1e+24f, 1e+24f, 1e+24f, 1e+25f, 1e+25f, 1e+25f, 1e+26f, 1e+26f, 
    1e+26f, 1e+27f, 1e+27f, 1e+27f, 1e+27f, 1e+28f, 1e+28f, 1e+28f, 1e+29f, 1e+29f, 1e+29f, 1e+30f, 
    1e+30f, 1e+30f, 1e+31f, 1e+31f, 1e+31f, 1e+31f, 1e+32f, 1e+32f, 1e+32f, 1e+33f, 1e+33f, 1e+33f, 
    1e+34f, 1e+34f, 1e+34f, 1e+34f, 1e+35f, 1e+35f, 1e+35f, 1e+36f, 1e+36f, 1e+36f, 1e+37f, 1e+37f, 
    1e+37f, 1e+37f, 1e+38f, 1e+38f, std::numeric_limits<float>::infinity() };

float decimalMagnitude(float val)
{
    uint32_t intVal = std::bit_cast<uint32_t>(val);
    uint8_t exponent = intVal >> 23;

    if (val >= magnitudeLUT[exponent + 1])
        return magnitudeLUT[exponent + 1];
    else
        return magnitudeLUT[exponent];
}

int main()
{
    for (float v = 1e-38f; v < 1e38f; v *= 1.78)
        printf("%e => %e\n", v, decimalMagnitude(v));
}

C++相关问答推荐

在32位处理器上优化53—32位模计算>

如何将字符串argv[]赋给C中的整型数组?

为什么在函数内部分配内存空间时需要添加符号?

#If指令中未定义宏?

为什么我一直收到分段错误?

在Apple Silicon上编译x86的Fortran/C程序

为什么Fread()函数会读取内容,然后光标会跳到随机位置?

在进程之间重定向输出和输入流的问题

是否定义了此函数的行为?

合并对 struct 数组进行排序

将 struct 数组写入二进制文件时发生Valgrind错误

向左移位3如何得到以字节为单位的位数?

从Raku nativecall调用时精度不同

RISC-V GCC编译器错误编译ASM代码

C 和 C++ 标准如何告诉您如何处理它们未涵盖的情况?

macos/arm64 上地址空间不使用第一位吗?

是什么阻止编译器优化手写的 memcmp()?

如何使用 raylib 显示数组中的图像

返回指向函数内声明的复合文字的指针是否安全,还是应该使用 malloc?

M1 Mac 上的 jmp_buf 如何解码?