我正在寻找一种以快速且可移植(IEEE 754)的方式将
float
截断为 int
的方法。原因是因为在这个函数中 50% 的时间都花在了强制转换上:
float fm_sinf(float x) {
const float a = 0.00735246819687011731341356165096815f;
const float b = -0.16528911397014738207016302002888890f;
const float c = 0.99969198629596757779830113868360584f;
float r, x2;
int k;
/* bring x in range */
k = (int) (F_1_PI * x + copysignf(0.5f, x)); /* <-- 50% of time is spent in cast */
x -= k * F_PI;
/* if x is in an odd pi count we must flip */
r = 1 - 2 * (k & 1); /* trick for r = (k % 2) == 0 ? 1 : -1; */
x2 = x * x;
return r * x*(c + x2*(b + a*x2));
}
float->int 转换的缓慢主要发生在 x86 上使用 x87 FPU 指令时。为了进行截断,需要将 FPU 控制字中的舍入模式更改为舍入到零并返回,这往往非常慢。
当使用 SSE 而不是 x87 指令时,可以在不更改控制字的情况下进行截断。您可以使用编译器选项(如 GCC 中的
-mfpmath=sse -msse -msse2
)或将代码编译为 64 位来完成此操作。
SSE3指令集有
FISTTP
指令可以在不改变控制字的情况下转换为带截断的整数。如果指示采用 SSE3,编译器可能会生成此指令。
或者,C99
lrint()
函数将使用当前舍入模式转换为整数(舍入到最接近的值,除非您更改它)。如果删除 copysignf
术语,则可以使用此选项。不幸的是,十几年过去了,这个功能仍然没有普及。
我发现了 Sree Kotay 的快速截断方法,它提供了我所需要的优化。
为了可移植,您必须添加一些指令并学习几种汇编语言,但理论上您可以使用一些内联汇编将浮点寄存器的部分移动到 eax/rax ebx/rbx 中并手动转换您需要的内容,浮点规范虽然是一个痛苦的屁股,但我非常确定,如果你用汇编来完成它,你会更快,因为你的需求非常具体,并且系统方法可能更通用并且对于你的目的来说效率较低