在C++中将浮点数截断为最接近2的幂 - 性能

Chr*_*pka 1 c++ floating-point performance

在C++中(假定至少是C++ 11),给定浮点值a,我需要找到满足以下约束的浮点值b:

  • b必须与a具有相同的符号.
  • b的大小必须小于或等于a的大小.
  • b的大小必须是2的幂.
  • 在这些约束条件下,b的大小必须尽可能大.

换句话说,我需要将a的幅度"截断"到最接近的2的幂,同时保持符号不变.

[*在我的情况下,"较少或相等"的约束是松散的,而"较少"也会起作用.]

给定一些IEEE 754二进制表示,实现此目的的一种方法是通过比特抨击简单地清除所有尾数位,同时保持符号和指数位不变.

一种更便携的方法是:

  1. 获得幅度的基数2对数,向下舍入,使用例如logb,ilogb甚至更便携,log2frexp.
  2. 使用例如整数比特移位(小心负功率和值范围的问题)提高2到n次幂,pow(2.0,n),exp2(n),或ldexp(1.0,n).
  3. 通过复制标志copysign.

这允许许多可能的组合来解决任务,当考虑单精度替代方案时更是如此.有没有人对现代硬件和使用现代编译器的性能有这些方法的经验?

chu*_*ica 5

使用frexp()1,ldexp()2来呈现a,形成了答案.

这两个功能几乎完全是所需要的.

这些frexp函数将浮点数分解为归一化分数和2的整数幂.... frexp函数返回值x,使其x具有区间[1/2,1]或0的大小.

这些ldexp函数将浮点数乘以2的整数幂.

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

double round_pow2(double a) {
  int exp;
  double frac = frexp(a, &exp);
  if (frac > 0.0) frac = 0.5;
  else if (frac < 0.0) frac = -0.5;
  double b = ldexp(frac, exp);

  printf("% 20g % 25a % 25a", a, a, b);
  printf(" %d", !!signbit(a) == !!signbit(b)); // b must have the same sign as a.
  printf(" %d\n", !(fabs(b) > fabs(a)));       // magnitude `b` must be <= magnitude `a`.

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

测试代码

void round_pow2_test(double x) {
  round_pow2(nextafter(-x, -INFINITY));
  round_pow2(-x);
  round_pow2(nextafter(-x, INFINITY));
  round_pow2(nextafter(x, -INFINITY));
  round_pow2(x);
  round_pow2(nextafter(x, INFINITY));
}

int main(void) {
  round_pow2_test(0);
  round_pow2_test(DBL_MIN);
  round_pow2_test(1.0);
  round_pow2_test(42.0);
  round_pow2_test(DBL_MAX);
  round_pow2(NAN);
  return 0;
}
Run Code Online (Sandbox Code Playgroud)

产量

   -4.94066e-324                -0x1p-1074                -0x1p-1074 1 1
              -0                   -0x0p+0                   -0x0p+0 1 1
    4.94066e-324                 0x1p-1074                 0x1p-1074 1 1
   -4.94066e-324                -0x1p-1074                -0x1p-1074 1 1
               0                    0x0p+0                    0x0p+0 1 1
    4.94066e-324                 0x1p-1074                 0x1p-1074 1 1
   -2.22507e-308  -0x1.0000000000001p-1022                -0x1p-1022 1 1
   -2.22507e-308                -0x1p-1022                -0x1p-1022 1 1
   -2.22507e-308  -0x1.ffffffffffffep-1023                -0x1p-1023 1 1
    2.22507e-308   0x1.ffffffffffffep-1023                 0x1p-1023 1 1
    2.22507e-308                 0x1p-1022                 0x1p-1022 1 1
    2.22507e-308   0x1.0000000000001p-1022                 0x1p-1022 1 1
              -1     -0x1.0000000000001p+0                   -0x1p+0 1 1
              -1                   -0x1p+0                   -0x1p+0 1 1
              -1     -0x1.fffffffffffffp-1                   -0x1p-1 1 1
               1      0x1.fffffffffffffp-1                    0x1p-1 1 1
               1                    0x1p+0                    0x1p+0 1 1
               1      0x1.0000000000001p+0                    0x1p+0 1 1
             -42     -0x1.5000000000001p+5                   -0x1p+5 1 1
             -42                 -0x1.5p+5                   -0x1p+5 1 1
             -42     -0x1.4ffffffffffffp+5                   -0x1p+5 1 1
              42      0x1.4ffffffffffffp+5                    0x1p+5 1 1
              42                  0x1.5p+5                    0x1p+5 1 1
              42      0x1.5000000000001p+5                    0x1p+5 1 1
            -inf                      -inf                   -0x1p-1 1 1
   -1.79769e+308  -0x1.fffffffffffffp+1023                -0x1p+1023 1 1
   -1.79769e+308  -0x1.ffffffffffffep+1023                -0x1p+1023 1 1
    1.79769e+308   0x1.ffffffffffffep+1023                 0x1p+1023 1 1
    1.79769e+308   0x1.fffffffffffffp+1023                 0x1p+1023 1 1
             inf                       inf                    0x1p-1 1 1
             nan                       nan                       nan 1 1
Run Code Online (Sandbox Code Playgroud)

1来自OP的"获得幅度的基数2对数,向下舍入,使用例如...... frexp".

2从OP的"使用例如...... ldexp(1.0,n)将2提升到n次幂".