如何强制 pow(float, int) 到 return float
How to force pow(float, int) to return float
在 C++11 中删除了重载函数 float pow(float base, int iexp )
,现在 pow
returns double
。在我的程序中,我正在计算很多这些(单精度),我对最有效的方法很感兴趣。
是否有一些具有上述签名的特殊函数(在标准库或任何其他函数中)?
如果不是,在任何其他操作(这会将其他所有内容都转换为 double
) 或将 iexp
转换为 float
并使用重载函数 float pow(float base, float exp)
?
编辑:为什么我需要 float
而不使用 double
?
主要原因是 RAM -- 我需要数十或数百 GB,因此这种减少是巨大的优势。所以我需要从 float
得到 float
。现在我需要最有效的方法来实现这一点(更少的转换,使用已经优化的算法等)。
尝试改用 powf()。这是 C99 函数,应该也可以在 C++11 中使用。
您可以使用 exponentiation by squaring 轻松编写自己的 fpow
。
float my_fpow(float base, unsigned exp)
{
float result = 1.f;
while (exp)
{
if (exp & 1)
result *= base;
exp >>= 1;
base *= base;
}
return result;
}
无聊的部分:
这个算法给出了最好的精度,当|base|时可以用float
类型存档> 1
证明:
让我们计算 pow(a, n)
,其中 a
是底数,n
是指数。
让我们定义 b1=a1, b2 =a2,b3=a4、b4=a8,等等。
那么an是所有这样的乘积bi 其中第 ith 位设置在 n.
所以我们有令集B={bk1,bk1,...,bkn} 并且对于任何 j 位 kj 设置在 n.
以下显而易见的算法A可用于舍入误差最小化:
- 如果B包含单个元素,则结果为
- 从B中选取两个元素p和q,取最小模
- 将它们从 B
中删除
- 计算乘积s = p*q并放入B
- 进入第一步
现在,让我们证明 B 中的元素可以从左到右相乘而不会失去准确性。这是事实,即:
bj > b1*b2*。 ..*bj-1
因为bj=bj-1*bj-1=bj-1*bj-2*bj-2=。 ..=bj-1*bj-2*...*b1* b1
因为,b1 = a1 = a 及其模大于一然后:
bj > b1*b2*。 ..*bj-1
因此我们可以得出结论,在从左到右的乘法过程中,累加器变量小于 B.
中的任何元素
然后,表达式 result *= base;
(当然除了第一次迭代)对 B 中的两个最小数进行乘法运算,因此舍入误差最小。因此,代码采用算法 A.
又是一个只有"wrong question"才能诚实回答的问题。或者至少:"Are you really willing to go there?"。 float
理论上需要大约。减少 80% 的裸片 space(对于相同的循环次数),因此批量处理的成本要低得多。由于这个原因,GPU 喜欢 float
。
但是,让我们看看 x86(诚然,您没有说您使用的是什么架构,所以我选择了最常见的)。 die space 中的价格已经支付。使用 float
进行计算实际上什么也得不到。实际上,您甚至可能 损失 吞吐量,因为需要从 float
到 double
的额外扩展,并且需要额外舍入到中间 float
精度。换句话说,您支付额外费用以获得不太准确的结果。这通常是要避免的事情,除非您需要与其他程序的最大兼容性。
另请参阅 Jens 的评论。这些选项允许编译器忽略某些语言规则以获得更高的性能。不用说,这有时会适得其反。
在 x86 上,有两种情况 float
可能 更高效:
- GPU(包括 GPGPU),事实上许多 GPU 甚至不支持
double
,如果支持,通常会慢很多。然而,您只有在进行大量此类计算时才会注意到。
- CPU SIMD 又名矢量化
你用过GPGPU就知道了。使用编译器内在函数的显式矢量化也是一种选择——您当然可以做出这种选择,但这需要进行相当多的成本效益分析。可能您的编译器能够自动矢量化某些循环,但这通常仅限于 "obvious" 应用程序,例如您将 vector<float>
中的每个数字乘以另一个 float
,而这种情况海事组织不是那么明显。即使你用相同的 int
pow
这样一个向量中的每个数字,编译器也可能不够聪明,无法有效地对其进行矢量化,特别是如果 pow
驻留在另一个翻译单元中,并且没有有效 link 时间码生成。
如果您还没有准备好考虑更改程序的整个结构以允许有效使用 SIMD(包括 GPGPU),并且您所处的架构默认 float
确实便宜得多,我建议你坚持使用 double
,并考虑 float
最好 一种可能有助于节省 RAM 或改善缓存局部性的存储格式(当你有 很多 时)。即使那样,测量也是一个好主意。
也就是说,您可以尝试 ivaigult 的算法(只有 double
用于中间体和结果),它与称为 Egyptian multiplication 的经典算法(以及各种其他名称)相关), 只有操作数相乘而不相加。我不知道 pow(double, double)
是如何工作的,但可以想象这个算法在某些情况下会更快。同样,您应该对基准测试有强迫症。
Is there some special function (in standard libraries or any other) with the above signature?
不幸的是,我不知道。
但是,正如许多人已经提到的那样 基准测试是必要的 以了解是否存在任何问题。
我组装了一个快速基准测试 online。基准代码:
#include <iostream>
#include <boost/timer/timer.hpp>
#include <boost/random/mersenne_twister.hpp>
#include <boost/random/uniform_real_distribution.hpp>
#include <cmath>
int main ()
{
boost::random::mt19937 gen;
boost::random::uniform_real_distribution<> dist(0, 10000000);
const size_t size = 10000000;
std::vector<float> bases(size);
std::vector<float> fexp(size);
std::vector<int> iexp(size);
std::vector<float> res(size);
for(size_t i=0; i<size; i++)
{
bases[i] = dist(gen);
iexp[i] = std::floor(dist(gen));
fexp[i] = iexp[i];
}
std::cout << "float pow(float, int):" << std::endl;
{
boost::timer::auto_cpu_timer timer;
for(size_t i=0; i<size; i++)
res[i] = std::pow(bases[i], iexp[i]);
}
std::cout << "float pow(float, float):" << std::endl;
{
boost::timer::auto_cpu_timer timer;
for(size_t i=0; i<size; i++)
res[i] = std::pow(bases[i], fexp[i]);
}
return 0;
}
基准测试结果(快速结论):
- gcc: c++11 始终比 c++03 快。
- clang: 确实
int
-c++03 版本似乎快了一点。我不确定它是否在误差范围内,因为我只 运行 在线基准测试。
- 两者: 即使使用 c++11 调用
pow
和 int
似乎性能更高一些。
如果其他人可以验证这是否也适用于他们的配置,那就太好了。
如果您的目标是 GCC,您可以尝试
float __builtin_powif(float, int)
我不知道它的性能如何。
在 C++11 中删除了重载函数 float pow(float base, int iexp )
,现在 pow
returns double
。在我的程序中,我正在计算很多这些(单精度),我对最有效的方法很感兴趣。
是否有一些具有上述签名的特殊函数(在标准库或任何其他函数中)?
如果不是,在任何其他操作(这会将其他所有内容都转换为 double
) 或将 iexp
转换为 float
并使用重载函数 float pow(float base, float exp)
?
编辑:为什么我需要 float
而不使用 double
?
主要原因是 RAM -- 我需要数十或数百 GB,因此这种减少是巨大的优势。所以我需要从 float
得到 float
。现在我需要最有效的方法来实现这一点(更少的转换,使用已经优化的算法等)。
尝试改用 powf()。这是 C99 函数,应该也可以在 C++11 中使用。
您可以使用 exponentiation by squaring 轻松编写自己的 fpow
。
float my_fpow(float base, unsigned exp)
{
float result = 1.f;
while (exp)
{
if (exp & 1)
result *= base;
exp >>= 1;
base *= base;
}
return result;
}
无聊的部分:
这个算法给出了最好的精度,当|base|时可以用float
类型存档> 1
证明:
让我们计算 pow(a, n)
,其中 a
是底数,n
是指数。
让我们定义 b1=a1, b2 =a2,b3=a4、b4=a8,等等。
那么an是所有这样的乘积bi 其中第 ith 位设置在 n.
所以我们有令集B={bk1,bk1,...,bkn} 并且对于任何 j 位 kj 设置在 n.
以下显而易见的算法A可用于舍入误差最小化:
- 如果B包含单个元素,则结果为
- 从B中选取两个元素p和q,取最小模
- 将它们从 B 中删除
- 计算乘积s = p*q并放入B
- 进入第一步
现在,让我们证明 B 中的元素可以从左到右相乘而不会失去准确性。这是事实,即:
bj > b1*b2*。 ..*bj-1
因为bj=bj-1*bj-1=bj-1*bj-2*bj-2=。 ..=bj-1*bj-2*...*b1* b1
因为,b1 = a1 = a 及其模大于一然后:
bj > b1*b2*。 ..*bj-1
因此我们可以得出结论,在从左到右的乘法过程中,累加器变量小于 B.
中的任何元素然后,表达式 result *= base;
(当然除了第一次迭代)对 B 中的两个最小数进行乘法运算,因此舍入误差最小。因此,代码采用算法 A.
又是一个只有"wrong question"才能诚实回答的问题。或者至少:"Are you really willing to go there?"。 float
理论上需要大约。减少 80% 的裸片 space(对于相同的循环次数),因此批量处理的成本要低得多。由于这个原因,GPU 喜欢 float
。
但是,让我们看看 x86(诚然,您没有说您使用的是什么架构,所以我选择了最常见的)。 die space 中的价格已经支付。使用 float
进行计算实际上什么也得不到。实际上,您甚至可能 损失 吞吐量,因为需要从 float
到 double
的额外扩展,并且需要额外舍入到中间 float
精度。换句话说,您支付额外费用以获得不太准确的结果。这通常是要避免的事情,除非您需要与其他程序的最大兼容性。
另请参阅 Jens 的评论。这些选项允许编译器忽略某些语言规则以获得更高的性能。不用说,这有时会适得其反。
在 x86 上,有两种情况 float
可能 更高效:
- GPU(包括 GPGPU),事实上许多 GPU 甚至不支持
double
,如果支持,通常会慢很多。然而,您只有在进行大量此类计算时才会注意到。 - CPU SIMD 又名矢量化
你用过GPGPU就知道了。使用编译器内在函数的显式矢量化也是一种选择——您当然可以做出这种选择,但这需要进行相当多的成本效益分析。可能您的编译器能够自动矢量化某些循环,但这通常仅限于 "obvious" 应用程序,例如您将 vector<float>
中的每个数字乘以另一个 float
,而这种情况海事组织不是那么明显。即使你用相同的 int
pow
这样一个向量中的每个数字,编译器也可能不够聪明,无法有效地对其进行矢量化,特别是如果 pow
驻留在另一个翻译单元中,并且没有有效 link 时间码生成。
如果您还没有准备好考虑更改程序的整个结构以允许有效使用 SIMD(包括 GPGPU),并且您所处的架构默认 float
确实便宜得多,我建议你坚持使用 double
,并考虑 float
最好 一种可能有助于节省 RAM 或改善缓存局部性的存储格式(当你有 很多 时)。即使那样,测量也是一个好主意。
也就是说,您可以尝试 ivaigult 的算法(只有 double
用于中间体和结果),它与称为 Egyptian multiplication 的经典算法(以及各种其他名称)相关), 只有操作数相乘而不相加。我不知道 pow(double, double)
是如何工作的,但可以想象这个算法在某些情况下会更快。同样,您应该对基准测试有强迫症。
Is there some special function (in standard libraries or any other) with the above signature?
不幸的是,我不知道。
但是,正如许多人已经提到的那样 基准测试是必要的 以了解是否存在任何问题。
我组装了一个快速基准测试 online。基准代码:
#include <iostream>
#include <boost/timer/timer.hpp>
#include <boost/random/mersenne_twister.hpp>
#include <boost/random/uniform_real_distribution.hpp>
#include <cmath>
int main ()
{
boost::random::mt19937 gen;
boost::random::uniform_real_distribution<> dist(0, 10000000);
const size_t size = 10000000;
std::vector<float> bases(size);
std::vector<float> fexp(size);
std::vector<int> iexp(size);
std::vector<float> res(size);
for(size_t i=0; i<size; i++)
{
bases[i] = dist(gen);
iexp[i] = std::floor(dist(gen));
fexp[i] = iexp[i];
}
std::cout << "float pow(float, int):" << std::endl;
{
boost::timer::auto_cpu_timer timer;
for(size_t i=0; i<size; i++)
res[i] = std::pow(bases[i], iexp[i]);
}
std::cout << "float pow(float, float):" << std::endl;
{
boost::timer::auto_cpu_timer timer;
for(size_t i=0; i<size; i++)
res[i] = std::pow(bases[i], fexp[i]);
}
return 0;
}
基准测试结果(快速结论):
- gcc: c++11 始终比 c++03 快。
- clang: 确实
int
-c++03 版本似乎快了一点。我不确定它是否在误差范围内,因为我只 运行 在线基准测试。 - 两者: 即使使用 c++11 调用
pow
和int
似乎性能更高一些。
如果其他人可以验证这是否也适用于他们的配置,那就太好了。
如果您的目标是 GCC,您可以尝试
float __builtin_powif(float, int)
我不知道它的性能如何。