【问题标题】:Round to IEEE 754 precision but keep binary format舍入到 IEEE 754 精度但保持二进制格式
【发布时间】:2019-04-21 02:01:23
【问题描述】:

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

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

将公式/算法四舍五入到最接近的 IEEE 754 精度以获得 3120000488281250 是什么? 我还需要一种方法来获得四舍五入的结果 (3120000732421875)。

【问题讨论】:

  • 我想最简单的方法是转换为浮点数,然后再转换回您的格式。
  • 定点数如何存储?
  • @user3386109,比例为 10^12,所以 3120.0005 = 3120000500000000
  • 求两个不大于 x 的最大幂。让 p 除以 2^23。找到最接近 x 的 p 的倍数。结果就是这样。
  • 某些结果无法以您的格式表示。例如,对于可表示的输入 1.0000001,最接近的 IEEE-754 32 位二进制值是 1.00000011920928955078125,但这不能以您的格式表示。那你想要什么结果?

标签: c algorithm formula ieee-754 fixed-point


【解决方案1】:

如果除以十进制比例因子,您会找到离您最近的可表示浮点数。对于另一个方向的舍入,可以使用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

【讨论】:

  • @user3386109:结果相同,代码更改是为了使其与 C 兼容,而不是为相同的函数使用 C++ 名称。
  • @user3386109:抱歉,我在测试 C++ -> C 端口时做了更改(从 roundf 到 round),但忘记了。
  • 此代码不处理输入完全可表示的情况,对于这种情况,“向下舍入”和“向上舍入”情况都应该产生输入值,不变。此外,x / 1.0e12 可能会出现舍入错误。而问题的样本 3120000500000000 接近常见 double 格式的可表示连续整数的限制——当 long long 转换为 double 时,超过 9007199254740992 的值可能会出现舍入错误。
  • @EricPostpischil:这能解决问题吗?
  • 可能scale_to_fixed 适合使用它的地方。但我实际上很关心float x_reduced = x / 1.0e12;。在除法之前,x 被转换为double。这可能涉及舍入误差。然后除法会引入另一个舍入误差。我没有调查是否有任何情况导致最终结果出现错误。
【解决方案2】:

为了将值处理为float 按1e12 缩放并计算下一个较大的二的幂,例如"rounding up (3120000732421875)",关键是要了解您正在从 x / 1.0e12 的 32 位表示中寻找下一个更大的 2 次幂。虽然您可以通过数学方法得出该值,但float 和unsigned(或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));
}

使用/输出示例

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

脚注:

1. 作为替代方案,您可以使用 指向 char 的指针来保存浮点类型的地址并解释存储在该位置为unsigned 而不会与C11 Standard - §6.5 Expressions (p6,7) 发生冲突(“严格别名规则”),但最好使用union。

【讨论】:

  • "increment pwr of 2 by 1" 字面意思是你在改变指数,但实际上你是在增加尾数。
  • @David 这个答案有一个 Ben 的代码试图避免的问题。当计算 x / 1.0e12 向上取整时,prev 和 next 都大于 x。这可以通过从值 3120000700000000 开始来证明。此外,在x / 1.0e12 精确表示为float 的情况下,next 是否应该大于prev 的问题。这可以用值 3120500000000000 来证明。诚然,这对于 OP 来说更像是一个问题。
  • 是的,这很明显,为什么我在问题中包含引用的"rounding up (3120000732421875)",解释为了获得该答案,您没有找到下一个可能的浮点表示,而是在操作位等价的无符号值。我通过 Eric 在问题下的 cmets 中描述的正确数学得到了 Ben 的答案,但是要获得 "rounding up (3120000732421875)" 值需要操纵 32 位值(例如 round(nextafterf(...)) 的组合。
猜你喜欢
  • 2016-01-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-10-04
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多