将带符号的单精度浮点数舍入到最接近的整数的有效方法是什么?
What's an efficient way to round a signed single precision float to the nearest integer?
float input = whatever;
long output = (long)(0.5f + input);
这对于我在 MSP430 上使用编译器提供的浮点加法支持库的应用程序来说效率很低。
我在想,这种特殊的 'nearest integer' 舍入可能有一个聪明的 'trick',可能通过 'bit twiddling' 直接浮点表示来避免简单的浮点加法, 但我还没有找到这样的。任何人都可以建议这种舍入 IEEE 754 32 位浮点数的技巧吗?
通过位操作转换很简单,下面的 C 代码演示了这一点。根据有关 MSP430 数据类型的注释,代码假定 int
包含 16 位,而 long
包含 32 位。
我们需要一种尽可能高效地将 float
的位模式传输到 unsigned long
的方法。此实现为此使用 union
,您的平台可能具有更高效的特定于机器的方式,例如一个内在的。在最坏的情况下,使用 memcpy()
来复制字节。
只有少数情况需要区分。我们可以检查 float
输入的指数字段来梳理它们。如果参数太大或为 NaN,则转换失败。在这种情况下,一种约定是 return 最小的负整数操作数。如果输入小于 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;
}
float input = whatever;
long output = (long)(0.5f + input);
这对于我在 MSP430 上使用编译器提供的浮点加法支持库的应用程序来说效率很低。
我在想,这种特殊的 'nearest integer' 舍入可能有一个聪明的 'trick',可能通过 'bit twiddling' 直接浮点表示来避免简单的浮点加法, 但我还没有找到这样的。任何人都可以建议这种舍入 IEEE 754 32 位浮点数的技巧吗?
通过位操作转换很简单,下面的 C 代码演示了这一点。根据有关 MSP430 数据类型的注释,代码假定 int
包含 16 位,而 long
包含 32 位。
我们需要一种尽可能高效地将 float
的位模式传输到 unsigned long
的方法。此实现为此使用 union
,您的平台可能具有更高效的特定于机器的方式,例如一个内在的。在最坏的情况下,使用 memcpy()
来复制字节。
只有少数情况需要区分。我们可以检查 float
输入的指数字段来梳理它们。如果参数太大或为 NaN,则转换失败。在这种情况下,一种约定是 return 最小的负整数操作数。如果输入小于 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;
}