为什么标准C ++库中没有int power(int base,int exponent)?

时间:2010-03-07 23:25:39

标签: c++ math standard-library

我觉得我必须无法找到它。有什么理由说c ++ pow函数没有为浮点数和双精度数以外的任何东西实现“幂”函数吗?

我知道实现是微不足道的,我只是觉得我正在做一个应该在标准库中的工作。强大的幂函数(即以某种一致,明确的方式处理溢出)写入并不好玩。

11 个答案:

答案 0 :(得分:56)

答案 1 :(得分:39)

对于任何固定宽度的整数类型,无论如何,几乎所有可能的输入对都会溢出该类型。标准化一个函数的用途是什么,它不能为绝大多数可能的输入提供有用的结果?

你几乎需要一个大整数类型才能使函数变得有用,并且大多数大整数库都提供了这个函数。


编辑:在对该问题的评论中,static_rtti写道“大多数输入导致它溢出?对于exp和double pow也是如此,我没有看到有人在抱怨。”这是不正确的。

让我们暂时搁置exp,因为这不是重点(虽然它实际上会使我的情况变得更强),并专注于double pow(double x, double y)。对于(x,y)对的哪一部分,这个函数做了一些有用的事情(即,不仅仅是上溢或下溢)?

我实际上只关注pow有意义的输入对的一小部分,因为这足以证明我的观点:如果x为正且| y | < = 1,然后pow不会溢出或下溢。这几乎占所有浮点对的四分之一(正好一半的非NaN浮点数是正数,只有不到一半的非NaN浮点数的数量小于1)。显然,很多其他输入对 pow会产生有用的结果,但我们已经确定它至少是所有输入的四分之一。

现在让我们看一下固定宽度(即非bignum)整数幂函数。它输入的部分不仅仅是溢出?为了最大化有意义的输入对的数量,应该对基数进行签名,并且指数是无符号的。假设基数和指数都是n位宽。我们可以很容易地对有意义的输入部分进行约束:

  • 如果指数为0或1,那么任何基数都是有意义的。
  • 如果指数为2或更大,那么大于2 ^(n / 2)的基数不会产生有意义的结果。

因此,在2 ^(2n)个输入对中,小于2 ^(n + 1)+ 2 ^(3n / 2)产生有意义的结果。如果我们看看最常见的用法是什么,32位整数,这意味着大约百分之一的输入对的1/1000的数量不会简单地溢出。

答案 2 :(得分:10)

因为无论如何都无法表示int中的所有整数幂:

>>> print 2**-4
0.0625

答案 3 :(得分:9)

这实际上是一个有趣的问题。我在讨论中没有找到的一个论点是参数的简单缺乏明显的返回值。让我们算一下hypthetical int pow_int(int, int)函数失败的方式。

  1. 溢出
  2. 结果未定义pow_int(0,0)
  3. 无法表示结果pow_int(2,-1)
  4. 该功能至少有2种故障模式。整数不能代表这些值,在这些情况下函数的行为需要由标准定义 - 程序员需要知道函数处理这些情况的确切程度。

    总体而言,将功能退出似乎是唯一明智的选择。程序员可以使用浮点版本来代替所有错误报告。

答案 4 :(得分:6)

C ++没有额外重载的一个原因是与C兼容。

C ++ 98具有double pow(double, int)之类的功能,但这些功能已在C ++ 11中删除,其中C99不包含它们。

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2011/n3286.html#550

获得稍微更精确的结果也意味着获得稍微不同的结果。

答案 5 :(得分:6)

简短回答:

pow(x, n)n是自然数的专门化对于时间效果通常很有用。但是标准库的通用pow()仍然可以很好地工作(令人惊讶!),并且在标准C库中尽可能少地包含它是绝对关键的,因此它可以作为便携且易于实施。另一方面,这并不能阻止它进入C ++标准库或STL,我很确定没有人计划在某种嵌入式平台上使用它。

现在,答案很长。

在许多情况下,通过将pow(x, n)专门化为自然数,可以使

n更快。对于我编写的几乎每个程序,我都必须使用自己的函数实现(但我在C中编写了很多数学程序)。专门的操作可以在O(log(n))时间内完成,但是当n较小时,更简单的线性版本可以更快。以下是两者的实现:


    // Computes x^n, where n is a natural number.
    double pown(double x, unsigned n)
    {
        double y = 1;
        // n = 2*d + r. x^n = (x^2)^d * x^r.
        unsigned d = n >> 1;
        unsigned r = n & 1;
        double x_2_d = d == 0? 1 : pown(x*x, d);
        double x_r = r == 0? 1 : x;
        return x_2_d*x_r;
    }
    // The linear implementation.
    double pown_l(double x, unsigned n)
    {
        double y = 1;
        for (unsigned i = 0; i < n; i++)
            y *= x;
        return y;
    }

(我离开x并且返回值为双倍,因为pow(double x, unsigned n)的结果将与pow(double, double)的频率相匹配。)

(是的,pown是递归的,但是打破堆栈是绝对不可能的,因为最大堆栈大小大约等于log_2(n)n是一个整数。如果n是一个64位整数,它给你一个大约64的最大堆栈大小。没有硬件具有如此极端的内存限制,除了一些硬件堆栈只有3到8个函数调用深度的狡猾的PIC 。)

至于表现,你会对花园种类pow(double, double)的能力感到惊讶。我在我5岁的IBM Thinkpad上测试了一亿次迭代,x等于迭代次数,n等于10.在这种情况下,pown_l获胜。 glibc pow()占用12.0用户秒,pown占用7.4秒,而pown_l仅占用6.5秒。所以这并不太令人惊讶。我们或多或少地期待着这一点。

然后,我让x保持不变(我将其设置为2.5),然后我将n从0到19循环一次。这一次,出乎意料的是,glibc pow以压倒性优势获胜!用户只用了2.0秒。我pown花了9.6秒,pown_l花了12.2秒。这里发生了什么?我做了另一个测试来找出答案。

我做了同样的事情,只有x等于一百万。这一次,pown赢了9.6s。 pown_l获得了12.2秒,而glibc pow获得了16.3秒。现在,很清楚!当pow为低时,glibc x的效果优于三者,但x为高时,表现最差。当x为高时,pown_ln较低时效果最佳,pownx为高时表现最佳。

所以这里有三种不同的算法,每种算法在适当的环境下都能比其他算法表现得更好。因此,最终,哪些使用最有可能取决于您计划如何使用pow,但使用正确的版本 值得,并且拥有所有版本都很好。实际上,您甚至可以使用以下函数自动选择算法:

double pown_auto(double x, unsigned n, double x_expected, unsigned n_expected) {
    if (x_expected < x_threshold)
        return pow(x, n);
    if (n_expected < n_threshold)
        return pown_l(x, n);
    return pown(x, n);
}

只要x_expectedn_expected是在编译时决定的常量,以及可能的其他一些注意事项,优化编译器就会自动删除整个pown_auto函数调用和用适当选择的三种算法替换它。 (现在,如果你真的要尝试使用这个,你可能不得不玩一点,因为我没有完全尝试编译我是什么上面写的。;))

另一方面,glibc pow 可以正常工作并且glibc已经足够大了。 C标准应该是可移植的,包括各种嵌入式设备(事实上,各地的嵌入式开发人员普遍认为glibc对他们来说已经太大了),如果每个简单的话都不能移植。数学函数需要包含可能使用的每个替代算法。所以,这就是它不符合C标准的原因。

脚注:在时间性能测试中,我给我的函数提供了相对慷慨的优化标志(-s -O2),这些标志可能与我系统上编译glibc的可能性相当,甚至更差。 archlinux),所以结果可能是公平的。对于更严格的测试,我必须自己编译glibc,而我 reeeally 不喜欢这样做。我曾经使用Gentoo,所以我记得它需要多长时间,即使任务是自动化。结果对我来说足够结论(或相当不确定)。你当然欢迎自己这样做。

奖励回合:如果需要精确的整数输出,那么pow(x, n)对所有整数的特化是工具,这确实发生了。考虑为具有p ^ N个元素的N维数组分配内存。即使用1来关闭p ^ N也会导致可能随机发生的段错误。

答案 6 :(得分:3)

世界在不断发展,编程语言也在不断发展。 fourth part of the C decimal TR¹为<math.h>添加了更多功能。这个问题的两个系列可能对这个问题感兴趣:

  • pown函数,它采用浮点数和intmax_t指数。
  • powr个函数,它使用两个浮点数(xy)并使用公式{{计算x到幂y 1}}。

似乎标准人员最终认为这些功能足够有用,可以集成到标准库中。但是,理性的是ISO/IEC/IEEE 60559:2011标准推荐这些函数用于二进制和十进制浮点数。我不能肯定地说在C89时遵循了什么“标准”,但exp(y*log(x))的未来演变很可能会受到 ISO / IEC / IEEE 60559 <未来演变的影响。 / strong>标准。

请注意,十进制TR的第四部分不会包含在C2x(下一个主要C版本)中,稍后可能会作为可选功能包含在内。我不知道在未来的C ++版本中包含TR的这一部分。


¹您可以找到一些正在进行的文档here

答案 7 :(得分:2)

也许是因为处理器的ALU没有为整数实现这样的函数,但是有这样的FPU指令(正如Stephen指出的那样,它实际上是一对)。所以实际上更快地转换为double,使用double来调用pow,然后测试溢出和强制转换,而不是使用整数运算来实现它。

(一方面,对数会降低乘法的次数,但对于大多数输入,整数的对数会失去很多准确度)

斯蒂芬是正确的,在现代处理器上这不再是真的,但选择数学函数时的C标准(C ++刚刚使用C函数)现在是什么,20岁?

答案 8 :(得分:2)

事实上,确实如此。

由于C ++ 11存在pow(int, int)的模板化实现---更常见的情况,请参阅(7)in http://en.cppreference.com/w/cpp/numeric/math/pow

答案 9 :(得分:0)

这是pow()的非常简单的O(log(n))实现,适用于任何数字类型,包括整数

template<typename T>
static constexpr inline T pown(T x, unsigned p) {
    T result = 1;

    while (p) {
        if (p & 0x1) {
            result *= x;
        }
        x *= x;
        p >>= 1;
    }

    return result;
}

它比enigmaticPhysicist的O(log(n))实现要好,因为它不使用递归。

它几乎总是比线性实现快(只要p>〜3),因为:

  • 它不需要任何额外的内存
  • 每个循环只能执行约1.5倍的操作
  • 每个循环最多只能进行约1.25倍的内存更新

答案 10 :(得分:-4)

一个非常简单的原因:

5^-2 = 1/25

STL库中的所有内容都基于可以想象的最准确,最强大的东西。当然,int将返回零(从1/25),但这将是一个不准确的答案。

我同意,在某些情况下这很奇怪。