在C中有效地提取double *的小数部分

时间:2011-04-08 01:10:52

标签: c floating-point double micro-optimization bit-manipulation

我希望采用IEEE双精度并以最有效的方式删除它的任何整数部分。

我想要

1035 ->0
1045.23->0.23
253e-23=253e-23

我不关心正确处理非正规,无穷大或NaN。我不介意有点麻烦,因为我知道我正在使用IEEE双打,所以它应该适用于各种机器。

无分支代码将是首选。

我的第一个想法是(伪代码)

char exp=d.exponent;
(set the last bit of the exponent to 1)
d<<=exp*(exp>0);
(& mask the last 52 bits of d)
(shift d left until the last bit of the exponent is zero, decrementing exp each time)
d.exponent=exp;

但问题是我无法想到一种有效的方法将d向左移动直到指数的最后一位为零,而且如果没有设置所有最后一位,它似乎需要输出零。这似乎与基数为2的对数问题有关。

非常感谢您对此算法或任何更好的算法的帮助。

我应该注意到我想要无分支代码的原因是因为我希望它能有效地进行矢量化。

6 个答案:

答案 0 :(得分:34)

简单的事情怎么样?

double fraction = whole - ((long)whole);

这只是从值本身中减去double的整数部分,余数应该是小数部分。当然,这可能会产生一些代表性问题。

答案 1 :(得分:12)

最佳实施完全取决于目标架构。

在最近的英特尔处理器上,这可以通过两条指令实现:roundsdsubsd,但无法用便携式 C代码表示。

在某些处理器上,最快的方法是对浮点表示进行整数运算。想到早期的Atom和许多ARM CPU。

在其他一些处理器上,最快的事情是转换为整数然后返回,然后减去,分支以保护大值。

如果您要处理大量值,可以将舍入模式设置为舍入为零,然后将+/- 2 ^ 52加减并减去截断为整数的数字,然后从原始值中减去获得分数的价值。如果你没有SSE4.1,但确实有一个现代化的英特尔CPU并想要进行矢量化,这通常是你能做到的最好的。但是,如果您要处理许多值,那么这是有道理的,因为更改舍入模式有点贵。

在其他架构上,其他实现是最佳的。一般来说,谈论C程序的“效率”是没有意义的;只有特定架构上特定实现的效率。

答案 2 :(得分:10)

#include <math.h>
double fraction = fmod(d, 1.0);

答案 3 :(得分:7)

提案

函数remainder计算余数,但不计算modf之类的整数部分:

#include <math.h>

double fracpart(double input)
{
    return remainder(input, 1.);
}

这是最有效(也是最便携)的方式,因为它不会计算不必要的值来完成工作(参见modf(long)fmod等。)


基准

正如Mattew在评论中所建议的,我写了一些benchmark code来将此解决方案与本页提供的所有其他解决方案进行比较。

请在下面找到65536次计算的时间测量值(使用关闭优化的Clang编译):

method 1 took 0.002389 seconds (using remainder)
method 2 took 0.000193 seconds (casting to long)
method 3 took 0.000209 seconds (using floor)
method 4 took 0.000257 seconds (using modf)
method 5 took 0.010178 seconds (using fmod)

再次使用Clang,这次使用-O3标志:

method 1 took 0.002222 seconds (using remainder)
method 2 took 0.000000 seconds (casting to long)
method 3 took 0.000000 seconds (using floor)
method 4 took 0.000223 seconds (using modf)
method 5 took 0.010131 seconds (using fmod)

原来最简单的解决方案似乎在大多数平台上都能提供最好的结果,而执行该任务的具体方法(fmodmodfremainder)实际上是超慢的!

答案 4 :(得分:3)

标准库函数modf非常巧妙地解决了这个问题。

#include <math.h>
/*...*/
double somenumber;
double integralPart;
double fractionalPart = modf(somenumber, &integralPart);

这应该做你所要求的,可移植的和合理有效的。

未记录的细节是第二个参数是否可以为NULL,然后避免使用临时的积分部分,这在您所描述的用途中是可取的。

不幸的是,它接缝很多实现不支持第二个参数的NULL,所以无论你是否使用这个值都必须使用临时值。

答案 5 :(得分:3)

在Microsoft Visual Studio 2015中使用C ++进行的一些分析和实验表明,正数的最佳方法是:

double n;
// ...
double fractional_part = n - floor(n);

它比modf快,并且如前所述,余数函数舍入到最接近的整数,因此不能使用。