快速的浮点数转整数(截断)转换

9

我正在寻找一种快速且可移植(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));
}

1
你尝试过使用-ffast-math编译吗?或者省略copysign函数,改用lrint()代替(int)强制转换? - Gunther Piez
1
你的常量过于精确(或者说过于乐观)。单精度IEEE754只能保留6位有效数字,双精度可以保留15位数字,而长双精度则因编译器和架构而异,但即使在x86 FPU的本机80位格式下,也只能保留20位数字。如果你需要那种级别的精度,代码在任何情况下都无法工作,并且任意精度库会慢得多。 - Clifford
1
@Clifford:我知道这些数字过于精确,但我总是喜欢计算35位数字,这样我就可以通过复制/粘贴来支持高达128位的任何内容。 - orlp
您是指50%的时间用于将浮点数转换为整数,还是50%的时间与该行代码相关?您想要截断并保留浮点格式吗?这不太困难,指数告诉您小数点在尾数中的位置,在该位置右侧的所有内容都应设置为零。如果小数点在左侧(数字是分数,小于一),则只需编码零即可。 - old_timer
@dwelch:我验证了一下,50%到66%的时间都花在了转换上。 - orlp
4个回答

4
float->int类型转换的缓慢主要发生在使用x86上的x87 FPU指令时。为了进行截断,需要更改FPU控制字中的舍入模式到零舍入模式,然后再改回来,这往往非常缓慢。
当使用SSE而不是x87指令时,可以进行无控制字更改的截断。您可以使用编译器选项(例如,在GCC中使用-mfpmath=sse -msse -msse2)或通过将代码编译为64位来实现此目的。
SSE3指令集具有FISTTP指令,可在不更改控制字的情况下将浮点数转换为整数。如果指示假定使用SSE3,则编译器可以生成此指令。
或者,C99 lrint()函数将按照当前舍入模式(除非您更改它)将其转换为整数。如果您删除copysignf一项,则可以使用此方法。不幸的是,十多年过去了,该函数仍然不是普遍存在的。

3
我找到了Sree Kotay提供的快速截断方法,它恰好提供了我所需的优化。

2

要实现可移植性,您需要添加一些指令并学习一些汇编语言,但理论上您可以使用一些内联汇编将浮点寄存器的部分移动到eax/rax ebx/rbx中,并手动转换所需内容。不过,浮点规范很麻烦,但我非常确定如果您使用汇编语言,速度会更快,因为您的需求非常具体,系统方法可能更通用,但对于您的目的来说效率较低。


3
您认为在汇编语言中进行位操作比使用本机浮点指令(假设是x86)将浮点数转换为整数更快,是基于什么样的理由? - Oliver Charlesworth
@OliCharlesworth 如果你愿意对输入施加一些限制,确实可以使用SSE指令集来非常高效地完成这个技巧。强制转换缓慢的原因是因为语言要求输出对于所有输入都是正确的。 - Mysticial
@Mysticial 如果你们中的任何一个人能分享这个技巧和限制,那就太好了 - 听起来足够有趣。 - Voo
@Voo 我以为我之前看到过一个关于这个问题的SO问题。但事实证明是相反的。(int -> float)第二个答案才是需要关注的。虽然它是int -> float,但很容易就能倒过来。我用类似的技巧进行double -> __int64转换。 - Mysticial
@Mysticial 谢谢,非常有趣。虽然我完全同意Paul R在下面的回答中的评论“聪明,但有点晦涩”。真的很聪明。 - Voo

0

您可以通过使用frexpf获取尾数和指数,跳过转换为int的步骤,并在适当的位位置(使用指数计算)检查原始尾数(使用union),以确定(依赖于象限的)r


Dough Currie:非常抱歉,因为匆忙,我忘记复制函数中的一行代码。我还使用“int”的值来获取免费的“fmod”。 “fmod”本身速度太慢了。 - orlp
@nightcracker:你尝试过使用 nearbyint() 吗?将其转换为整数再转回浮点数会很慢。 - caf
@caf:实际上,后者非常快。只有转换为整数才会很慢。 - orlp

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接