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

Jer*_*wen 20 c floating-point double bit-manipulation micro-optimization

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

我想要

1035 ->0
1045.23->0.23
253e-23=253e-23
Run Code Online (Sandbox Code Playgroud)

我不关心正确处理非正规,无穷大或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;
Run Code Online (Sandbox Code Playgroud)

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

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

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

Mar*_*iot 35

简单的事情怎么样?

double fraction = whole - ((long)whole);
Run Code Online (Sandbox Code Playgroud)

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

  • 实际上,任何大的价值都不可能有一小部分.但你应该使用`int64_t`,而不是`long`.`long`可能只有32位,在这种情况下你需要的值不适合. (8认同)
  • 实际上,呃,这就是答案.如果它太长了,我可以检测到它并将小数部分设置为零.公认. (2认同)
  • @R ..:“ floor”比强制转换为整数更好,我认为,至少对于现代CPU(带有SSE4`roundpd`的x86)而言。实现这种方式要求编译器编写代码,如果double超出可表示的long值范围,则该代码将失败。从FP到整数然后返回的往返可能比取整慢。我在[Godbolt编译器浏览器](https://godbolt.org/g/n56CkL)上尝试了这两个版本。不幸的是,`floor`不仅仅与-fno-math-errno内联,所以我使用了-ffast-math。如果您不能做到这一点,并且不能采用SSE4,则投射效果不错。 (2认同)

Ste*_*non 12

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

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

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

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

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

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


Meh*_*dad 10

#include <math.h>
double fraction = fmod(d, 1.0);
Run Code Online (Sandbox Code Playgroud)


Mat*_*dic 7

提案

该函数remainder计算余数,但不计算整数部分modf:

#include <math.h>

double fracpart(double input)
{
    return remainder(input, 1.);
}
Run Code Online (Sandbox Code Playgroud)

这是最有效的(和便携式)的方式,因为它没有计算不必要的值来完成这项工作(参见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)
Run Code Online (Sandbox Code Playgroud)

再次与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)
Run Code Online (Sandbox Code Playgroud)

原来最简单的解决方案似乎给大多数平台上的最好成绩,具体方法来执行该任务(fmod,modf,remainder)实际上是超级慢!

  • 与Clang一起编译,优化功能已关闭。这样您的结果就没有意义了。优化并不能使一切都以相同的速度加快。请参阅我对[关于作业必须针对-O0进行优化的问题的答案]的答案(http://stackoverflow.com/questions/32000917/c-loop-optimization-help-for-final-assignment/32001196# 32001196)。 (2认同)
  • `0.000000 秒`。听起来你的基准测试已经优化掉了。我确实希望“floor”或转换为“long”会非常高效,因为它们只在 x86 上使用几个快速 asm 指令(相对于慢速 FP 除法),但这并不能证明任何事物。 (2认同)