我觉得我必须无法找到它。有什么理由说c ++ pow函数没有为浮点数和双精度数以外的任何东西实现“幂”函数吗?
我知道实现是微不足道的,我只是觉得我正在做一个应该在标准库中的工作。强大的幂函数(即以某种一致,明确的方式处理溢出)写入并不好玩。
答案 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
位宽。我们可以很容易地对有意义的输入部分进行约束:
因此,在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)
函数失败的方式。
pow_int(0,0)
pow_int(2,-1)
该功能至少有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_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标准的原因。
脚注:在时间性能测试中,我给我的函数提供了相对慷慨的优化标志(-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
个函数,它使用两个浮点数(x
和y
)并使用公式{{计算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),因为:
答案 10 :(得分:-4)
一个非常简单的原因:
5^-2 = 1/25
STL库中的所有内容都基于可以想象的最准确,最强大的东西。当然,int将返回零(从1/25),但这将是一个不准确的答案。
我同意,在某些情况下这很奇怪。