将带符号的单精度浮点数舍入到最接近的整数的有效方法是什么?

时间:2015-04-28 00:59:25

标签: floating-point rounding msp430

float input = whatever;
long output = (long)(0.5f + input);

使用编译器提供的浮点加法支持库,这对我在MSP430上的应用程序来说是低效的。

我正在思考这种特殊类型的“最接近的整数”舍入可能会有一个聪明的“技巧”,可能会直接“浮动”浮点表示而避免普通浮点加法,但我还没有找到这样的。任何人都可以提出这样的技巧来绕过IEEE 754 32位浮点数吗?

1 个答案:

答案 0 :(得分:4)

按位操作转换很简单,下面的C代码演示了这一点。根据有关MSP430上数据类型的注释,代码假定int包含16位,long 32位。

我们需要一种尽可能有效地将float的位模式转换为unsigned long的方法。此实现为此使用union,您的平台可能具有更高效的机器特定方式,例如内在的。在最坏的情况下,使用memcpy()复制字节。

只有少数情况需要区分。我们可以检查float输入的指数字段以区分它们。如果参数太大或NaN,则转换失败。一种惯例是在这种情况下返回最小的负整数操作数。如果输入小于0.5,则结果为零。在消除了这些特殊情况之后,我们留下那些需要一点点计算才能转换的输入。

对于足够大的参数,float总是一个整数,在这种情况下我们只需要将尾数模式移动到正确的位位置。如果输入太小而不是整数,我们将转换为32.32定点格式。然后舍入基于最重要的分数位,并且在平局的情况下,也基于最低有效整数位,因为关系必须舍入为偶数。

如果连接情况应始终从零开始,则代码中的舍入逻辑简化为

r = r + (t >= 0x80000000UL);

下面是实现上述方法的float_to_long_round_nearest(),以及一个详尽测试此实现的测试框架。

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

long float_to_long_round_nearest (float a)
{
    volatile union {
        float f;
        unsigned long i;
    } cvt;
    unsigned long r, ia, t, expo;

    cvt.f = a;
    ia = cvt.i;
    expo = (ia >> 23) & 0xff;
    if (expo > 157) {        /* magnitude too large (>= 2**31) or NaN */
        r = 0x80000000UL;
    } else if (expo < 126) { /* magnitude too small ( < 0.5) */
        r = 0x00000000UL;
    } else {
        int shift = expo - 150;
        t = (ia & 0x007fffffUL) | 0x00800000UL;
        if (expo >= 150) {   /* argument is an integer, shift left */
            r = t << shift;
        } else {
            r = t >> (-shift);
            t = t << (32 + shift);
            /* round to nearest or even */
            r = r + ((t > 0x80000000UL) | ((t == 0x80000000UL) & (r & 1)));
        }
        if ((long)ia < 0) {  /* negate result if argument negative */
            r = -(long)r;
        }
    }
    return (long)r;
}

long reference (float a) 
{
    return (long)rintf (a);
}

int main (void)
{
     volatile union {
        float f;
        unsigned long i;
    } arg;
     long res, ref;

     arg.i = 0x00000000UL;
     do {
         res = float_to_long_round_nearest (arg.f);
         ref = reference (arg.f);
         if (res != ref) {
             printf ("arg=%08lx % 15.8e  res=%08lx  ref=%08lx\n", 
                     arg.i, arg.f, res, ref);
             return EXIT_FAILURE;
         }
         arg.i++;
     } while (arg.i);
     return EXIT_SUCCESS;
}