C++ 有效地将双精度除以2的幂

C++ 有效地将双精度除以2的幂,c++,c,x86,C++,C,X86,我正在实现一个相干噪声函数,并惊讶地发现使用梯度噪声(即柏林噪声)实际上比值噪声稍快。分析表明,出现这种情况的原因是将随机int值转换为-1.0到1.0范围内的双精度所需的除法: static double noiseValueDouble(int seed, int x, int y, int z) { return 1.0 - ((double)noiseValueInt(seed, x, y, z) / 1073741824.0); } 渐变噪波需要更多的倍数,但由于预计算的渐变

我正在实现一个相干噪声函数,并惊讶地发现使用梯度噪声(即柏林噪声)实际上比值噪声稍快。分析表明,出现这种情况的原因是将随机int值转换为-1.0到1.0范围内的双精度所需的除法:

static double noiseValueDouble(int seed, int x, int y, int z) {
    return 1.0 - ((double)noiseValueInt(seed, x, y, z) / 1073741824.0);
}
渐变噪波需要更多的倍数,但由于预计算的渐变表直接使用
noiseValueInt
计算表中的索引,因此不需要任何除法。所以我的问题是,考虑到除法是2(2^30)的幂,我如何使上述除法更有效


理论上,所有需要做的就是从双精度的指数中减去30,但通过蛮力(即位操纵)这样做会导致各种各样的角情况(INF、NAN、指数溢出等)。x86汇编解决方案就可以了。

我不确定您是否可以信任这里的评测。对于更小、更快的函数,分析代码本身的效果开始扭曲结果

在循环中运行
noiseValueDouble
和相应的备选方案以获得更好的数字

x86汇编程序解决方案是一种位篡改解决方案,您也可以在C语言中进行位篡改。双除法指令(位移位)的快速功能仅适用于整数

如果你真的想使用特殊指令,可以用MMX将其加起来。

声明一个具有反数值的变量(或常数)并乘以它,有效地将除法改为乘法:

static const double div_2_pow_30 = 1.0 / 1073741824.0;

另一种方法(利用数字是2的幂的特性)是使用位操作修改指数。这样做会使代码依赖于使用IEEE标准存储的双倍体,而IEEE标准的可移植性可能较低。

我尝试使用gcc编译此代码:

double divide(int i) {
    return 1.0 - (double)i / 1073741824.0;
}

使用
-O3
将其编码为
FMULS
-指令,使用
-O3-mfpmath=sse-march=core2
使用sse指令集并将其编码为
MULSD
。我不知道什么是最快的,但函数调用本身可能比实际除法慢几个数量级。

您可以直接使用函数
frexp
ldexp
修改指数。我不确定这是否会更快。

你确定这有那么重要吗?使用特定于机器的位操作是不可移植的,甚至可能因为缓存或调度问题而影响性能…@BasileStarynkevitch这就是我上一段的意思。我不想在假设IEEE754的情况下进行位操作,但如果使用x86特定的汇编解决方案,则可以。我不会为此而烦恼。你赢不了多少,也可能会失去一些性能。如果你启用了优化,编译器应该已经将除法替换为倒数的乘法。请发布替代函数。是的,我也这么做了。尽管影响较小,但仍然存在。对于渐变噪声,调用是
grad(noiseValueInt(seed,x0,y0),x,y)
,对于值噪声,
noiseValueDouble(seed,x0,y0)
。除此之外,这两种算法是相同的。我想说的是,编译器可以算出这一点,但实际上不行(除非你对gcc使用-ffast math)。@spraff:如果除数是2的精确幂,并且其倒数没有上溢或下溢,那么结果总是一样的。大多数编译器都知道这一点,并进行优化。如中所示,如果它是常数,则将其设为常数!我现在觉得不尝试这个很傻,但是是的,就是这样。GCC4.4.3和-O2显然并没有自动优化。当然,当你说“用位运算修改尾数”时,你指的是指数,对吗?