为什么标准C ++库中没有提供“ int pow(int base,int exponent)”?


116

我觉得我一定无法找到它。是否有任何理由使C ++ pow函数不对floats和doubles 以外的任何东西执行“ power”函数?

我知道实现很简单,我只是觉得我正在做应该在标准库中的工作。健壮的幂函数(即以某种一致的显式方式处理溢出)不好玩。


4
这是一个很好的问题,我认为答案没有多大意义。负指数不起作用?以无符号整数作为指数。大多数输入导致它溢出?exp和double pow同样如此,我看不到有人抱怨。那么为什么这个功能不是标准的呢?
static_rtti 2011年

2
@static_rtti:“ exp和double pow同样如此”完全是错误的。我会详细说明。
斯蒂芬·佳能

11
标准C ++库具有double pow(int base, int exponent)自C ++ 11(§26.8[c.math] / 11项目符号点2)
Cubbi

您需要在“实现琐碎”和“编写不好玩”之间下定决心。
罗恩侯爵,

Answers:


66

截至C++11,特殊情况已添加到幂函数套件(和其他函数)中。C++11 [c.math] /11在列出所有float/double/long double重载(我的重点和措辞)后说:

此外,应该有足够的额外重载来确保,如果与参数相对应的任何double参数具有类型double或整数类型,则与参数相对应的所有double参数都将有效地转换为double

因此,基本上,整数参数将升级为双精度型以执行该操作。


在此之前C++11(当时是问您的问题),不存在整数重载。

由于我既没有与创建者建立紧密的联系,C也没有与创建者建立紧密的联系C++(尽管我还很老),也没有创建标准的ANSI / ISO委员会的一部分,所以这对我来说是必然的看法。我想以为这是知情的意见,但是,正如我妻子会告诉你的(经常并且不需要太多鼓励),我以前错了:-)

假设是值得的,随后是假设。

怀疑原来的pre-ANSI C不具有此功能的原因是因为它完全没有必要。首先,已经有一种完美的方法来进行整数幂运算(加倍,然后简单地转换回整数,在转换之前检查整数上溢和下溢)。

其次,您还必须记住的另一件事是,它的初衷C是作为系统编程语言,并且在该领域是否完全需要浮点数值得怀疑。

由于其最初的用例之一是对UNIX进行编码,因此浮点数几乎是无用的。C所基于的BCPL也没有使用任何功能(它从内存中根本没有浮点数)。

顺便说一句,积分幂运算符可能是二进制运算符,而不是库调用。您不会在- 语言的一部分而不是库中添加x = add (y, z)带有x = y + z-的两个整数。

第三,由于积分能力的实现相对来说是微不足道的,因此几乎可以肯定的是,该语言的开发人员会更好地利用他们的时间提供更多有用的东西(请参阅下面有关机会成本的评论)。

这也与原始内容有关C++。由于原始实现实际上只是产生C代码的翻译器,因此它继承了的许多属性C。它的初衷是C类,而不是C类加上一点点额外的数学知识。

至于为什么以前从未将其添加到标准中C++11,您必须记住,标准制定机构要遵循特定的准则。例如,ANSI的C任务是整理现有实践,而不是创建新语言。否则,他们可能会发疯并给我们Ada :-)

该标准的后续版本也有特定的指导原则,可以在基本原理文件中找到(有关委员会为何做出某些决定的合理说明,而不是语言本身的基本原理)。

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

  • 保持语言小而简单。
  • 仅提供一种操作方法。

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

此外,标准制定机构认识到,他们做出的每个决策都存在机会成本(一个经济术语,意味着您必须放弃做出的决策)。例如,购买这台10,000美元的超级游戏机的机会成本是与您的另一半的亲密关系(或可能是所有关系),持续了大约六个月。

埃里克·甘纳森(Eric Gunnerson)用-100点的解释很好地解释了为什么并非总是将这些东西添加到Microsoft产品中-基本上,一项功能在漏洞中开始100点,因此它必须增加相当多的价值才能被考虑。

换句话说,您是否愿意在标准中添加一个完整的幂运算符(说实话,任何半个体面的编码器都可以在十分钟内完成操作)或多线程?就我自己而言,我宁愿拥有后者,而不必为UNIX和Windows下的不同实现而烦恼。

我还希望看到成千上万个标准库的集合(哈希,btrees,红黑树,字典,任意地图等),但从原理上讲:

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

标准机构的实施者数量远远超过程序员(或至少那些不了解机会成本的程序员)的数量。如果添加了所有这些内容,下一个标准C++将是C++215x之后的三百年内由编译器开发人员完全实现。

无论如何,那是我对此事的想法(相当多)。如果只根据数量而不是质量来分发选票,我很快就会把其他所有人都抛在脑后。谢谢收听 :-)


2
FWIW,我不认为C ++遵循“仅提供一种操作方式”作为约束。没错,因为例如to_stringlambda既方便了您可以做的事情。我想一个人可以很松散地解释“一个操作的唯一方式” 以允许这两种方式,并同时允许人们可以想象的几乎所有功能的重复,只要说“啊哈!不!”,因为方便它与精确等效但更复杂的替代方法有微妙的区别!”。lambdas确实是这样。
史蒂夫·杰索普

@Steve,是的,我这句话措辞不好。准确地说,每个委员会都有指南,而不是所有委员会都遵循相同的指南。调整后的答案以澄清
paxdiablo 2012年

2
一点(仅几分):“任何代码猴子都可以在十分钟内振作起来”。当然,如果每年有100只代码猴子(侮辱性的术语,BTW)这样做(可能是一个低估),那么我们将浪费1000分钟。非常有效,您不觉得吗?
尔根·艾哈德

1
@Jürgen,这并不意味着侮辱(因为我实际上并未将标签归于任何特定的人),这只是一个指示,pow实际上并不需要太多技巧。当然,我宁愿有标准提供一些东西,需要很多技巧,并导致更浪费分钟如果努力不得不被复制。
paxdiablo

2
@ eharo2,只需将当前文本中的“半体面编码器”替换为“ code Monkey”即可。我也不认为这是侮辱性的,但我认为最好还是保持谨慎,老实说,当前的措辞也存在相同的想法。
paxdiablo

41

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

为了使函数有用,您几乎需要具有大整数类型,并且大多数大整数库都提供了该函数。


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

让我们放在一边exp,因为这是重点(尽管这实际上会使我的观点更强),并专注于double pow(double x, double y)。此函数对(x,y)对的哪一部分有用(例如,不仅仅是上溢或下溢)?

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

现在让我们看一个固定宽度(即非大数)整数幂函数。对于哪一部分输入,它不会简单地溢出?为了最大化有意义的输入对的数量,应该对基数进行签名,对指数进行无符号化。假设基数和指数都是n位宽。我们可以轻松地对有意义的输入部分进行限制:

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

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


8
无论如何,这都是没有意义的。仅仅因为一个函数对于某些或许多输入无效,并不意味着它的用处不大。
static_rtti 2011年

2
@static_rtti:pow(x,y)如果| y |,则对于任何x都不会下溢为零。<= 1.输入存在一个非常窄的带(大x,y非常接近-1),发生下溢,但是结果在该范围内仍然有意义。
斯蒂芬·佳能

2
经过深思熟虑,我同意下溢。不过,我仍然认为这与问题无关。
static_rtti 2011年

7
@ybungalobill:为什么选择这个作为理由?从个性上讲,我赞成对大量问题和程序员有用,可以使硬件优化版本的开发速度比大多数程序员可能会天真的实现更快,等等。您的标准似乎完全是武断的,坦率地说,毫无意义。
static_rtti 2011年

5
@StephenCanon:从好的方面来说,您的论点表明,整数的明显正确且最佳的实现pow只是一个很小的查找表。:-)
R .. GitHub STOP HELPING ICE 2013年

11

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

>>> print 2**-4
0.0625

3
对于有限大小的数字类型,由于溢出,无法在该类型内表示该类型的所有幂。但是您关于负权力的观点更有效。
克里斯·卢茨

1
我认为负指数是标准实现可以处理的事情,可以采用无符号int作为指数,或者当提供负指数作为输入且int是预期输出时返回零。
Dan O

3
或有单独int pow(int base, unsigned int exponent)float pow(int base, int exponent)
Ponkadoodle

4
他们可以将其声明为未定义行为以传递负整数。
Johannes Schaub-litb 2010年

2
在所有现代实现中,任何超出范围的内容int pow(int base, unsigned char exponent)都无济于事。底数是0或1,或者指数无关紧要,它是-1,在这种情况下,仅指数的最后一位是重要的,或者base >1 || base< -1在这种情况下,exponent<256将对溢出进行惩罚。
MSalters 2010年

9

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

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

该功能至少具有2种故障模式。整数不能表示这些值,在这种情况下,函数的行为需要由标准定义-程序员将需要知道函数如何精确地处理这些情况。

总体上忽略该功能似乎是唯一明智的选择。程序员可以使用浮点版本,而可以使用所有错误报告。


但是前两种情况也不适用于pow浮点之间吗?拿两个大浮标,将一个浮标提升到另一个浮标,您将产生溢出。并pow(0.0, 0.0)会引起与第二点相同的问题。第三点是实现整数与浮点数的幂函数之间的唯一真正区别。
numbermaniac

7

简短答案:

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。没有硬件具有这种极限内存限制,但有些带有硬件堆栈的狡猾PIC只能进行3到8个函数调用。)

至于性能,您会惊讶于各种花园pow(double, double)的功能。我在5岁的IBM Thinkpad上测试了1亿次迭代,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.6秒。pown_l花了12.2秒,而glibc pow花了16.3秒。现在,很明显!glibc powx低时表现优于三个,但在x高时表现最差。当x为高时,pown_l执行时最好n为低电平,并pown执行时最好x是高的。

因此,这里有三种不同的算法,每种算法在适当的情况下都能比其他算法表现更好。因此,最终,最有可能使用哪种方法取决于您计划如何使用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函数,那么它就不能移植。可能有用的替代算法。因此,这就是为什么它不在C标准中。

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

红利回合:如果需要精确的整数输出,则pow(x, n)对所有整数的专业化将非常有用。考虑为具有p ^ N个元素的N维数组分配内存。使p ^ N减1甚至可能导致随机发生的段错误。


我想如果您摆脱了递归,您将节省堆栈分配所需的时间。是的,我们遇到了这样的情况,战俘正在减慢一切,我们必须实施自己的战俘。
桑巴顿2014年

“没有人有这种极端的记忆力限制”是错误的。PIC通常具有有限的调用堆栈,最多只能进行3次调用(例如PIC10F200)到8次调用(例如16F722A)(PIC使用硬件堆栈进行函数调用)。
12431234123412341234123

哦,这是残酷的笑声。好的,因此在这些PIC上不起作用。
enigmaticPhysicist

对于整数和幂一样的问题,编译器(gcc和clang)将很容易从迭代(而不是递归)实现中产生无分支循环。这样可避免分支的错误预测ngodbolt.org/z/L9Kb98。gcc和clang无法将您的递归定义优化为一个简单的循环,并且实际上在的每一位上进行分支n。(因为pown_iter(double,unsigned)它们仍然是分支,但是应该在x86 asm或C内部函数中实现无分支SSE2或SSE4.1实现。但这甚至比递归还好)
Peter Cordes

废话,现在我必须使用基于循环的版本重新进行基准测试。我会考虑一下。
enigmaticPhysicist


3

世界在不断发展,编程语言也在不断发展。C十进制TR¹第四部分向中添加了更多功能<math.h>。这些功能的两个家族可能对这个问题感兴趣:

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

看来标准人员最终认为这些功能足够有用,可以集成到标准库中。但是,合理的是这些功能是ISO / IEC / IEEE 60559:2011标准针对二进制和十进制浮点数推荐的。我不能肯定地说C89时遵循了什么“标准”,但是ISO / IEC / IEEE 60559标准<math.h>的未来发展可能会极大地影响的未来发展。

请注意,十进制TR的第四部分将不会包含在C2x(下一个主要的C版本)中,以后可能会作为可选功能包含在内。我没有任何意图将TR的这一部分包含在将来的C ++修订版中。


¹您可以在此处找到一些进行中的文档。


是否有任何可能的实现方式,其中使用pown指数大于LONG_MAX应该产生与使用不同的值LONG_MAX,或者小于LONG_MIN应当产生与使用不同的值的值LONG_MIN?我想知道使用intmax_t指数有什么好处?
超级猫

@supercat不知道,对不起。
Morwenn

值得一提的是,看一下标准,它似乎还定义了一个可选的“ crpown”函数,如果定义了该函数,它将是“ pown”的正确舍入的版本。否则,该标准没有规定所需的准确性。实施快速而精确的“ pown”很容易,但是在所有情况下确保正确的舍入都将变得更加昂贵。
超级猫

2

也许是因为处理器的ALU没有为整数实现这种功能,而是有这样的FPU指令(正如Stephen指出的,它实际上是一对)。因此,与使用整数算术实现相比,将其强制转换为double并用double调用pow,然后测试溢出并进行强制回退实际上要快得多。

(一方面,对数降低了乘幂,但对于大多数输入,整数对数却失去了很多精度)

Stephen是对的,在现代处理器上这不再是正确的,但是选择数学函数(C ++仅使用C函数)时的C标准现在已经有20年历史了?


5
我不知道目前使用FPU指令的任何体系结构pow。x86的y log2 x指令(fyl2x)可以用作pow函数的第一部分,但是以pow这种方式编写的函数需要花费数百个周期才能在当前硬件上执行;一个写得好的整数幂运算例程要快几倍。
斯蒂芬·佳能

我不知道“数百”是准确的,在大多数现代CPU上,fyl2x和f2xm1似乎大约有150个周期,并且会与其他指令进行流水线化。但您说对了,因为IMUL比浮点指令的执行速度要快得多,因此,经过良好调整的整数实现应该会更快(这些天)。但是,在编写C标准时,IMUL相当昂贵,并且在循环中使用它可能比使用FPU花费更长的时间。
Ben Voigt 2010年

2
根据更正改变了我的投票;不过,请记住(a)C标准在1999年进行了重大修订(包括对数学库的大扩展),并且(b)C标准未写入任何特定的处理器体系结构-存在x86上是否缺少FPU指令与C委员会选择标准化的功能基本上无关。
斯蒂芬·佳能

确实,它与任何体系结构都没有关系,但是我想,对于所有体系结构,查找表插值(通常用于浮点实现)与整数乘法相比的相对成本已发生了相当大的变化。
Ben Voigt 2010年

1

这是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倍的内存更新

-2

事实上,确实如此。

由于C ++ 11提供了pow(int, int)---甚至更一般情况的模板化实现,请参见http://en.cppreference.com/w/cpp/numeric/math/pow中的(7)


编辑:纯粹主义者可能认为这是不正确的,因为实际上使用的是“提升”类型。一种或另一种方式int,可以在int参数上获得正确的结果或错误。


2
这是不正确的。(7)重载pow ( Arithmetic1 base, Arithmetic2 exp )将强制转换为,double或者long double您已经阅读了以下说明:“ 7)一组重载或函数模板,用于算术类型的自变量的所有组合,但不包含1-3)。如果有任何自变量具有整数类型,则将其强制转换为double。如果任何参数为long double,则返回类型Promoted也是long double,否则返回类型始终为double。”
phuclv

这里有什么不对的地方?我只是说,如今(自C ++ 11起)标准库中包含模板化的pow(),2010
。– Dima Pasechnik,

5
不,不是。庙宇将这些类型提升为双倍或长双倍。因此,它可以在下面的双打中使用。
Trismegistos

1
@Trismegistos它仍然允许int参数。如果此模板不存在,则传递int参数会使它将int中的位解释为浮点数,从而导致任意意外结果。混合输入值也会发生同样的情况。例如pow(1.5f, 3)= 1072693280pow(1.5f, float(3))=3.375
Mark Jeronimus

2
OP要求int pow(int, int),但是C ++ 11仅提供double pow(int, int)。请参阅@phuclv的说明。
xuhdev

-4

一个很简单的原因:

5^-2 = 1/25

STL库中的所有内容均基于可想象的最准确,最可靠的内容。当然,整数将返回零(从1/25开始),但这将是一个不准确的答案。

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


3
显然需要一个无符号的第二个参数。有许多应用程序只需要非负整数幂。
enigmaticPhysicist 2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.