为什么在标准C ++库中不是int pow(int base,int exponent)?

我觉得我必须无法find它。 是否有任何理由的C + +幂函数不执行除了浮动和双打之外的任何东西的“电源”function?

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

既然我在创作的时候(虽然我比较陈旧)既不是C和C ++的创作者,也不是创build这些标准的ANSI / ISO委员会的一部分,这一定是我的观点。 我想认为这是知情的意见,但是,正如我的妻子会告诉你(经常和没有太多的鼓励需要),我以前错了:-)

假设,它的价值如下。

我怀疑原来的(ANSI之前的)C没有这个function的原因是因为这是完全没有必要的。 已经有了一个完美的方式来做整数幂(双打,然后简单地转换回一个整数,让你能够检查整数溢出和下溢前转换)。

还有一点你要记住的是,C的最初意图是一个系统编程语言,无论如何浮点是否是可取的,这是值得怀疑的。 由于最初的用例是对UNIX进行编码,所以浮点数将是无用的。 以C为基础的BCPL也没有权力使用(从内存来看,它根本没有浮点)。

顺便说一下,一个整体的电力运营商可能是一个二元运营商,而不是图书馆的电话。 您不要使用x = add (y, z)添加两个整数,而是使用x = y + z – 这是适当语言的一部分,而不是库。

由于整体力量的实施相对来说是微不足道的,所以语言的开发者可以更好地利用他们的时间来提供更有用的东西(见下面对机会成本的评论)。

这也是最初的C ++相关的。 由于原来的实现只不过是一个产生C代码的译者,它inheritance了C的许多属性,其原意是C类,而不是C类,加上一点点-extra,math的东西。

至于为什么从未添加到标准中,你必须记住,标准制定机构有特定的指导方针要遵循。 例如,ANSI C专门负责编纂现有的实践, 而不是创build一种新的语言。 否则,他们可能会疯了,给我们Ada 🙂

后来的这个标准的迭代也有特定的指导方针,可以在基本原理文件中find(理由为什么委员会做出某些决定,而不是语言本身的理由)。

例如,C99的基本原理文件特别提出了C89的两个指导原则,限制了可以补充的内容:

  • 保持语言小而简单。
  • 只提供一种方法来执行操作。

为各个工作组制定了指导方针(不一定是那些具体的指导方针),因此限制了C ++委员会(以及所有其他的ISO组织)。

另外,标准制定机构认识到,每一个决策都有一个机会成本 (一个经济学术语,意味着你必须放弃做出决定)。 例如,购买这台价值$ 10,000美元的超级博奕机的机会成本与你的另一半在六个月左右是亲切的关系(或者可能是所有的关系)。

Eric Gunnerson用他的-100分的解释说明了这一点,为什么事物并不总是添加到微软的产品中 – 基本上一个function在洞中开始了100个点,所以它必须增加相当多的价值才能被考虑。

换句话说,你宁愿有一个完整的权力运算符(老实说,任何代码猴都可能在十分钟内鞭打)或multithreading添加到标准? 对于我自己而言,我宁愿拥有后者,也不必在UNIX和Windows下使用不同的实现。

我也想看到成千上万的标准库(哈希,树,红黑树,字典,任意地图等等),但是,理由是:

标准是实现者和程序员之间的条约。

而标准组织的实施者数量远远超过程序员(或者至less那些不了解机会成本的程序员)的数量。 如果添加了所有这些东西,下一个标准的C ++将是C ++ 215x,并可能在三百年后由编译器开发人员完全实现。

无论如何,这是我对这个问题(相当多的)的想法。 如果只按照数量而不是质量投票,我很快就会把所有人都排除在外。 感谢收听 :-)

对于任何固定宽度的整数types,无论如何,几乎所有可能的input对都会溢出。 对于绝大多数可能的input,没有给出有用的结果的标准化函数有什么用处?

你几乎需要有一个大的整数types,以使该function有用,大多数大整数库提供的function。


编辑:在对这个问题的评论,static_rtti写道:“大部分input导致溢出?对于exp和double pow也是如此,我没有看到任何人抱怨。 这是不正确的。

让我们把exp放在一边,因为这是旁边的点(虽然它实际上会让我的情况更强),并且关注double pow(double x, double y) 。 对于(x,y)对的哪一部分,这个函数做些有用的事情(即,不是简单的上溢或下溢)?

实际上我只关注pow一小部分,因为这足以certificate我的观点:如果x是正数,| y | 那么pow不会溢出或下溢。 这包括所有浮点对中的近四分之一(非NaN浮点数的正好一半,只有不到一半的非NaN浮点数具有小于1的数量级)。 显然,还有很多其他的input对, pow会产生有用的结果,但是我们已经确定它至less是所有input的四分之一。

现在我们来看一个固定宽度(即非二进制)的整数幂函数。 对于什么部分input,它不是简单地溢出? 为了最大化有意义的input对的数量,基础应该被签名并且指数无符号。 假设基数和指数都是n位宽。 我们可以很容易地得到有意义的input部分的界限:

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

因此,在2 ^(2n)input对中,小于2 ^(n + 1)+ 2 ^(3n / 2)产生有意义的结果。 如果我们看看最常见的用法(32位整数),这意味着input对百分之一千分之一的东西不会简单地溢出。

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

 >>> print 2**-4 0.0625 

这实际上是一个有趣的问题。 我在讨论中没有发现一个论点,那就是论证中没有明显的返回值。 让我们来计算hypthetical int pow_int(int, int)函数可能失败的方式。

  1. 溢出
  2. 结果未定义pow_int(0,0)
  3. 结果不能表示pow_int(2,-1)

该function至less有2个故障模式。 整数不能表示这些值,在这些情况下函数的行为将需要由标准定义 – 程序员需要知道函数如何处理这些情况。

总的来说,这个function似乎是唯一明智的select。 程序员可以使用所有可用的错误报告的浮点版本。

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

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

简短的回答:

pow(x, n)n是自然数的一个专门化对于时间performance通常是有用的。 但是标准库的genericspow()仍然适用于这个目的( 令人惊讶的是 ),因此,尽可能less地包含在标准的C库中是非常重要的,所以它可以做得更简单,更容易实现。 另一方面,这完全不能阻止它在C ++标准库或STL中,我很确定没有人计划在某种embedded式平台上使用STL。

现在,为了长久的答案。

pow(x, n)在许多情况下可以通过将n专门化为一个自然数而变得更快。 几乎我写的每个程序都必须使用自己的函数实现(但是我用C语言编写了大量的math程序)。 专门的操作可以在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)将适合double的pow(double, double)就像pow(double, double)

(是的, pown是recursion的,但是打破堆栈是绝对不可能的,因为最大的堆栈大小将大致等于log_2(n)n是一个整数,如果n是一个64位的整数,那么最大的堆栈大小约为64.除了硬件堆栈只能进行3到8个函数调用的一些狡猾的PIC之外, 没有硬件具有如此极端的内存限制。)

至于表演,你会惊讶于什么花园品种pow(double, double)是能够的。 我testing了我的5年IBM Thinkpad的迭代次数,其中x等于迭代次数, n等于10.在这种情况下, pown_l赢了。 glibc pow()花了12.0个用户秒, pown了7.4个用户秒,而pown_l花了6.5个用户秒。 所以这并不令人感到意外。 我们或多或less地期待这一点。

然后,我让x是恒定的(我把它设置为2.5),并且我从0到19亿次循环。 这一次,意外的是,glibc pow赢了,并且山体滑坡! 它只用了2.0秒的时间。 我的pown花了9.6秒,小pown_l了12.2秒。 这里发生了什么? 我做了另一个testing找出来。

我只用x等于一百万就做了同样的事情。 这一次, pown以9.6 pown获胜。 pown_l花了12.2s,glibc pow花了16.3s。 现在,很明显! 当x低时,glibc powperformance比pow ,但当x高时最差。 当x很高的时候,当n很低的时候, pownperformance最好,而当x很高的时候performance最好。

所以这里有三种不同的algorithm,每种algorithm在正确的情况下都能够比其他algorithm更好。 所以,最终,最有可能取决于你如何使用pow计划,但使用正确的版本值得的,所有的版本都很好。 事实上,你甚至可以用这样的函数自动selectalgorithm:

 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函数调用,并用适当的三种algorithmselect来replace它。 (现在,如果你真的要试图使用这个,你可能需要一点玩,因为我没有完全编译上面写的东西)

另一方面,glibc pow 确实有效 ,glibc已经足够大了。 C标准被认为是可移植的,包括各种embedded式设备 (事实上​​,各地的embedded式开发人员普遍认为glibc对于他们来说已经太大了),并且如果对于每一个简单的math函数来说它都是不可移植的替代algorithm, 可能是有用的。 所以,这就是为什么它不在C标准。

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

奖金回合: pow(x, n)对所有整数的专门化是有帮助的,如果需要一个确切的整数输出,这确实发生了。 考虑为具有p ^ N个元素的N维数组分配内存。 如果将p ^ Nclosures,则会导致可能随机出现的段错误。

也许是因为处理器的ALU没有为整数实现这样的函数,但是有这样一个FPU指令(正如斯蒂芬指出的,它实际上是一对)。 所以实际上,加倍转换,使用double来调用pow,然后testing溢出并退回,比使用整数algorithm来实现更快。

(一方面,对数减less乘法的能力,但整数对数对于大多数input来说失去了很大的精度)

Stephen在现代处理器上的说法是不正确的,但selectmath函数(C ++只是使用C函数)的C标准现在是20年前了吗?

世界正在不断发展,编程语言也在不断发展。 C十进制TR的第四部分增加了一些函数<math.h> 。 这个问题的两个家庭可能会对这个问题感兴趣:

  • pown函数,需要一个浮点数和一个intmax_t指数。
  • powr函数使用两个浮点数( xy ),并用公式exp(y*log(x))计算x的幂y

标准人员似乎最终认为这些function足够有用,可以集成到标准库中。 然而,理性的是这些函数是由ISO / IEC / IEEE 60559:2011标准为二进制和十进制浮点数所推荐的。 我不能确定在C89时期遵循了什么“标准”,但未来<math.h>演变可能会严重受到ISO / IEC / IEEE 60559标准未来演变的影响。

请注意,十进制TR的第四部分将不包含在C2x(下一个主要C版本)中,并且可能稍后将作为可选functionjoin。 我知道在将来的C ++修订版中包含这部分TR的意图是没有的。


¹您可以在这里find一些正在进行的工作文档。

一个非常简单的原因:

 5^-2 = 1/25 

STL库中的所有东西都基于最精确,最可靠的东西。 当然,int会返回到零(从1/25开始),但这将是一个不准确的答案。

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