我觉得我一定无法找到它。是否有任何理由使C ++ pow
函数不对float
s和double
s 以外的任何东西执行“ power”函数?
我知道实现很简单,我只是觉得我正在做应该在标准库中的工作。健壮的幂函数(即以某种一致的显式方式处理溢出)不好玩。
double pow(int base, int exponent)
自C ++ 11(§26.8[c.math] / 11项目符号点2)
我觉得我一定无法找到它。是否有任何理由使C ++ pow
函数不对float
s和double
s 以外的任何东西执行“ power”函数?
我知道实现很简单,我只是觉得我正在做应该在标准库中的工作。健壮的幂函数(即以某种一致的显式方式处理溢出)不好玩。
double pow(int base, int exponent)
自C ++ 11(§26.8[c.math] / 11项目符号点2)
Answers:
截至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
之后的三百年内由编译器开发人员完全实现。
无论如何,那是我对此事的想法(相当多)。如果只根据数量而不是质量来分发选票,我很快就会把其他所有人都抛在脑后。谢谢收听 :-)
to_string
lambda既方便了您可以做的事情。我想一个人可以很松散地解释“一个操作的唯一方式” 以允许这两种方式,并同时允许人们可以想象的几乎所有功能的重复,只要说“啊哈!不!”,因为方便它与精确等效但更复杂的替代方法有微妙的区别!”。lambdas确实是这样。
pow
实际上并不需要太多技巧。当然,我宁愿有标准提供一些东西,就需要很多技巧,并导致更浪费分钟如果努力不得不被复制。
对于任何固定宽度的整数类型,无论如何几乎所有可能的输入对都会溢出该类型。标准化对大多数可能的输入都无法提供有用结果的功能的用途是什么?
为了使函数有用,您几乎需要具有大整数类型,并且大多数大整数库都提供了该函数。
编辑:在对问题的评论中,static_rtti写道:“大多数输入导致它溢出?exp和double pow同样如此,我看不到有人抱怨。” 这是不正确的。
让我们放在一边exp
,因为这是重点(尽管这实际上会使我的观点更强),并专注于double pow(double x, double y)
。此函数对(x,y)对的哪一部分有用(例如,不仅仅是上溢或下溢)?
实际上,我只专注于pow
有意义的输入对的一小部分,因为这足以证明我的观点:如果x为正,| y | <= 1,pow
则不会上溢或下溢。它占所有浮点对的近四分之一(非NaN浮点数的正好是一半,而非NaN浮点数的正数只有不到一半的幅度小于1)。显然,还有许多其他输入对可以pow
产生有用的结果,但是我们已经确定它至少占所有输入的四分之一。
现在让我们看一个固定宽度(即非大数)整数幂函数。对于哪一部分输入,它不会简单地溢出?为了最大化有意义的输入对的数量,应该对基数进行签名,对指数进行无符号化。假设基数和指数都是n
位宽。我们可以轻松地对有意义的输入部分进行限制:
因此,在2 ^(2n)个输入对中,少于2 ^(n + 1)+ 2 ^(3n / 2)会产生有意义的结果。如果我们看一下最常见的用法,即32位整数,这意味着大约占输入对百分之一的1/1000的东西不会简单地溢出。
pow(x,y)
如果| y |,则对于任何x都不会下溢为零。<= 1.输入存在一个非常窄的带(大x,y非常接近-1),发生下溢,但是结果在该范围内仍然有意义。
pow
只是一个很小的查找表。:-)
因为无论如何都无法表示int中的所有整数幂:
>>> print 2**-4
0.0625
int pow(int base, unsigned int exponent)
和float pow(int base, int exponent)
int pow(int base, unsigned char exponent)
都无济于事。底数是0或1,或者指数无关紧要,它是-1,在这种情况下,仅指数的最后一位是重要的,或者base >1 || base< -1
在这种情况下,exponent<256
将对溢出进行惩罚。
这实际上是一个有趣的问题。我在讨论中没有找到的一个论点就是简单地缺乏明显的论点返回值。让我们计算一下假设int pow_int(int, int)
函数可能失败的方式。
pow_int(0,0)
pow_int(2,-1)
该功能至少具有2种故障模式。整数不能表示这些值,在这种情况下,函数的行为需要由标准定义-程序员将需要知道函数如何精确地处理这些情况。
总体上忽略该功能似乎是唯一明智的选择。程序员可以使用浮点版本,而可以使用所有错误报告。
pow
浮点之间吗?拿两个大浮标,将一个浮标提升到另一个浮标,您将产生溢出。并pow(0.0, 0.0)
会引起与第二点相同的问题。第三点是实现整数与浮点数的幂函数之间的唯一真正区别。
简短答案:
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 pow
在x
低时表现优于三个,但在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_expected
和n_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甚至可能导致随机发生的段错误。
n
。 godbolt.org/z/L9Kb98。gcc和clang无法将您的递归定义优化为一个简单的循环,并且实际上在的每一位上进行分支n
。(因为pown_iter(double,unsigned)
它们仍然是分支,但是应该在x86 asm或C内部函数中实现无分支SSE2或SSE4.1实现。但这甚至比递归还好)
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
获得稍微准确的结果也意味着获得略有不同的结果。
世界在不断发展,编程语言也在不断发展。C十进制TR¹的第四部分向中添加了更多功能<math.h>
。这些功能的两个家族可能对这个问题感兴趣:
pown
函数需要一个浮点数和一个intmax_t
指数。powr
函数采用两个浮点数(x
和y
),并使用公式计算x
出幂。y
exp(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
指数有什么好处?
也许是因为处理器的ALU没有为整数实现这种功能,而是有这样的FPU指令(正如Stephen指出的,它实际上是一对)。因此,与使用整数算术实现相比,将其强制转换为double并用double调用pow,然后测试溢出并进行强制回退实际上要快得多。
(一方面,对数降低了乘幂,但对于大多数输入,整数对数却失去了很多精度)
Stephen是对的,在现代处理器上这不再是正确的,但是选择数学函数(C ++仅使用C函数)时的C标准现在已经有20年历史了?
pow
。x86的y log2 x
指令(fyl2x
)可以用作pow
函数的第一部分,但是以pow
这种方式编写的函数需要花费数百个周期才能在当前硬件上执行;一个写得好的整数幂运算例程要快几倍。
这是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),因为:
事实上,确实如此。
由于C ++ 11提供了pow(int, int)
---甚至更一般情况的模板化实现,请参见http://en.cppreference.com/w/cpp/numeric/math/pow中的(7)
编辑:纯粹主义者可能认为这是不正确的,因为实际上使用的是“提升”类型。一种或另一种方式int
,可以在int
参数上获得正确的结果或错误。
pow ( Arithmetic1 base, Arithmetic2 exp )
将强制转换为,double
或者long double
您已经阅读了以下说明:“ 7)一组重载或函数模板,用于算术类型的自变量的所有组合,但不包含1-3)。如果有任何自变量具有整数类型,则将其强制转换为double。如果任何参数为long double,则返回类型Promoted也是long double,否则返回类型始终为double。”
pow(1.5f, 3)
= 1072693280
但pow(1.5f, float(3))
=3.375
int pow(int, int)
,但是C ++ 11仅提供double pow(int, int)
。请参阅@phuclv的说明。
一个很简单的原因:
5^-2 = 1/25
STL库中的所有内容均基于可想象的最准确,最可靠的内容。当然,整数将返回零(从1/25开始),但这将是一个不准确的答案。
我同意,在某些情况下这很奇怪。