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

我希望采用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<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; 

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

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

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

简单的事情怎么样?

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

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

最佳实现完全取决于目标体系结构。

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

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

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

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

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

 #include  double fraction = fmod(d, 1.0); 

提案

函数remainder计算余数,但不像modf那样计算整数部分:

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

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


基准

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

请在下面找到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) 

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

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

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

这应该做你所要求的,便携式和合理有效的。

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

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

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

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

它比modf更快,并且,正如已经提到的,余数函数舍入到最接近的整数,因此是不可用的。