C++ 如何强制pow(float,int)返回float

C++ 如何强制pow(float,int)返回float,c++,c++11,pow,C++,C++11,Pow,重载函数float-pow(float-base,int-iexp)在C++11中被删除,现在pow返回一个double。在我的程序中,我计算了很多(单精度),我对最有效的方法很感兴趣 是否有具有上述签名的特殊功能(在标准库或任何其他库中) 如果不是,那么(就单精度性能而言)在任何其他操作之前将pow的结果显式转换为float(这会将所有其他操作转换为double)或将iexp转换为float并使用重载函数float-pow(float-base,float-exp) 编辑:为什么我需要floa

重载函数
float-pow(float-base,int-iexp)
在C++11中被删除,现在
pow
返回一个
double
。在我的程序中,我计算了很多(单精度),我对最有效的方法很感兴趣

是否有具有上述签名的特殊功能(在标准库或任何其他库中)

如果不是,那么(就单精度性能而言)在任何其他操作之前将
pow
的结果显式转换为
float
(这会将所有其他操作转换为
double
)或将
iexp
转换为
float
并使用重载函数
float-pow(float-base,float-exp)

编辑:为什么我需要
float
而不使用
double


主要原因是RAM——我需要数十或数百GB的内存,所以这种减少是巨大的优势。所以我需要从
float
获取
float
。现在我需要最有效的方法来实现这一点(减少强制转换,使用已经优化过的算法等等)。

尝试使用powf()。这是C99函数,在C++11中也应该可用。

您可以使用它轻松编写自己的
fpow


钻孔部分: 当| base |>1时,此算法提供了最佳精度,可以使用
float
类型存档

证明: 让我们来计算
pow(a,n)
其中
a
是基,
n
是指数。
让我们定义b1=a1、b2=a2、b3=a4、b4=a8等等。

然后an是所有bi的乘积,其中第i位设置在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,且其模数大于1,那么:

bj>b1*b2*…*bj-1

因此,我们可以得出结论,在从左到右的乘法过程中,累加器变量小于B中的任何元素


然后,表达式
result*=base
(当然,除了第一次迭代之外)将B中的两个最小数相乘,因此舍入误差最小。因此,代码使用了算法A

另一个问题,只能用“错误的问题”诚实地回答。或者至少:“你真的愿意去那里吗?”
float
理论上需要减少约80%的芯片空间(对于相同的周期数),因此批量处理成本更低。GPU之所以喜欢浮动就是因为这个原因

但是,让我们看看x86(无可否认,您没有说明您使用的是什么体系结构,所以我选择了最常见的体系结构)。模具领域的价格已经支付。通过使用
float
进行计算,您实际上一无所获。实际上,您甚至可能会失去吞吐量,因为需要从
float
double
的额外扩展,以及到中间
float
精度的额外舍入。换言之,你需要付出额外的代价才能得到不太准确的结果。这通常是要避免的,除非您需要与其他程序最大限度地兼容

参见Jens的评论。这些选项允许编译器忽略某些语言规则以获得更高的性能。不用说,这有时会适得其反

在x86上,有两种情况下,
float
可能更有效:

  • GPU(包括GPGPU),事实上,许多GPU甚至不支持双精度,如果支持双精度,通常速度要慢得多。然而,只有在进行大量此类计算时,您才会注意到
  • CPU SIMD aka矢量化
如果你有GPGPU,你就会知道。使用编译器内部函数进行显式矢量化也是一种选择——当然,您可以这样做,但这需要进行相当多的成本效益分析。可能您的编译器能够自动向量化某些循环,但这通常仅限于“明显的”应用程序,例如,将
向量中的每个数字乘以另一个
浮点数,这种情况在IMO中并不明显。即使将向量中的每个数乘以相同的
int
,编译器可能不够聪明,无法有效地将其矢量化,特别是当
pow
驻留在另一个翻译单元中,并且没有有效的链接时代码生成时

如果你还没有准备好改变你的程序的整个结构,以允许有效地使用SIMD(包括GPGPU),而你不是在一个架构中,<代码>浮点< /代码>在默认情况下确实便宜得多,我建议你一定要遵守<代码>双< /代码>,并考虑<代码>浮点最适合保存RAM的存储格式,或者提高缓存局部性(当您有很多)时。即使如此,测量也是一个很好的主意

也就是说,您可以尝试ivaigult的算法(仅对中间值和结果使用
double
),该算法与一个名为(以及各种其他名称)的经典算法相关,只是操作数是相乘的,而不是相加的。我不知道pow(double,double)
是如何精确工作的,但可以想象,在某些情况下,该算法可能会更快。同样,你也应该对基准测试有所了解

t是否有一些特殊功能(在标准库或任何其他库中)
float my_fpow(float base, unsigned exp)
{
    float result = 1.f;
    while (exp)
    {
        if (exp & 1)
            result *= base;
        exp >>= 1;
        base *= base;
    }

    return result;
}
#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;
}
float __builtin_powif(float, int)