舍入到 IEEE 754 精度但保留二进制格式

Round to IEEE 754 precision but keep binary format

如果我将十进制数 3120.0005 转换为浮点数(32 位)表示形式,该数字将向下舍入为 3120.00048828125。

假设我们使用比例为 10^12 的定点数,则 1000000000000 = 1.0 和 3120000500000000 = 3120.0005。

formula/algorithm 是多少才能向下舍入到最接近的 IEEE 754 精度以获得 3120000488281250? 我还需要一种方法来获得四舍五入的结果 (3120000732421875)。

如果除以小数比例因子,您会找到最接近的可表示浮点数。对于另一个方向的舍入,可以使用 std::nextafter

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

long long scale_to_fixed(float f)
{
    float intf = truncf(f);
    long long result = 1000000000000LL;
    result *= (long long)intf;
    result += round((f - intf) * 1.0e12);
    return result;
}

/* not needed, always good enough to use (float)(n / 1.0e12) */
float scale_from_fixed(long long n)
{
    float result = (n % 1000000000000LL) / 1.0e12;
    result += n / 1000000000000LL;
    return result;
}

int main()
{
    long long x = 3120000500000000;
    float x_reduced = scale_from_fixed(x);
    long long y1 = scale_to_fixed(x_reduced);
    long long yfloor = y1, yceil = y1;
    if (y1 < x) {
        yceil = scale_to_fixed(nextafterf(x_reduced, FLT_MAX));
    }
    else if (y1 > x) {
        yfloor = scale_to_fixed(nextafterf(x_reduced, -FLT_MAX));
    }

    printf("%lld\n%lld\n%lld\n", yfloor, x, yceil);
}

结果:

3120000488281250

3120000500000000

3120000732421875

为了处理 float1e12 缩放的值,并计算下一个更大的 2 的幂,例如"rounding up (3120000732421875)",关键是理解您正在从 x / 1.0e12 的 32 位表示中寻找下一个更大的 2 的幂。虽然您可以从数学上得出该值,但 floatunsigned(或 uint32_t)之间的 union 提供了一种直接的方法来解释存储的 32 位浮点值- 作为无符号值的点数。1

一个简单的例子,利用联合 prev 来保存 x 的减少值和一个单独的实例 next 保存无符号值 (+1) 可以是:

#include <stdio.h>
#include <inttypes.h>

int main (void) {

    uint64_t x = 3120000500000000;
    union {                         /* union between float and uint32_t */
        float f;
        uint32_t u;
    } prev = { .f = x / 1.0e12 },   /* x reduced to float, pwr of 2 as .u */
      next = { .u = prev.u + 1u };  /* 2nd union, increment pwr of 2 by 1 */

    printf ("prev : %" PRIu64 "\n   x : %" PRIu64 "\nnext : %" PRIu64 "\n", 
            (uint64_t)(prev.f * 1e12), x, (uint64_t)(next.f * 1e12));
}

示例Use/Output

$ ./bin/pwr2_prev_next
prev : 3120000488281250
   x : 3120000500000000
next : 3120000732421875

脚注:

1. 作为替代方案,您可以使用指向 char 指针来保存浮点数的地址键入并将存储在该位置的 4 字节值解释为 unsigned,而 运行 与 C11 Standard - §6.5 Expressions (p6,7)"Strict Aliasing Rule")不冲突,但首选 union