我觉得我一定是找不到它。是否有任何理由,导致C++的 pow
函数除了 float
和 double
以外,不实现“幂”函数?
我知道实现很简单,但我觉得我正在做应该在标准库中的工作。一个强大的幂函数(即以某种一致、明确的方式处理溢出)不是很好写。
我觉得我一定是找不到它。是否有任何理由,导致C++的 pow
函数除了 float
和 double
以外,不实现“幂”函数?
我知道实现很简单,但我觉得我正在做应该在标准库中的工作。一个强大的幂函数(即以某种一致、明确的方式处理溢出)不是很好写。
从 C++11
开始,一些特殊情况被添加到了幂函数(和其他函数)的套件中。 根据 C ++ 11 [c.math] / 11
(其中列出了所有的 float/double/long double
重载),如下所述:
此外,还应有足够的其他重载以确保,如果与
double
参数相对应的任何参数具有double
或整数类型,则与double
参数相对应的所有参数都被有效地强制转换为double
。
因此,基本上将整数参数升级为 double 类型以执行操作。
在 C++11
之前(也是提出问题时的情况),不存在整数重载。
由于我既不是 C
或 C++
的创建者,也不是创建标准的 ANSI/ISO 委员会的成员(尽管我很老),因此这必然是我的观点。我认为这是一个有根据的观点,但是,正如我的妻子经常并且不需要太多的鼓励告诉你的那样,我曾经犯过错误 :-)
以下是我推测的情况。
我认为最初的 ANSI 前的 C
没有这个功能的原因是完全没有必要。首先,已经有一种非常好的方法来进行整数幂运算(使用双精度浮点数然后简单地转换回整数,在转换之前检查整数溢出和下溢)。
其次,您还必须记住,C
的最初意图是作为一个系统编程语言,而在这个领域是否需要浮点数是值得怀疑的。
由于其最初的使用案例之一是编写UNIX,浮点数将几乎没有用处。基于BCPL语言的C语言也没有使用幂运算(从记忆中它根本没有浮点数)。
顺便提一下,整数幂操作符可能本来就应该是一个二元运算符而不是库函数调用。你不是用
x = add(y, z)
来相加两个整数,而是用x = y + z
- 这是语言本身的一部分而不是库。
第三,由于实现整数幂相对简单,开发者们肯定会更好地利用他们的时间提供更有用的东西(查看下面关于机会成本的评论)。
这对原始的C++
也是相关的。由于原始的实现实际上只是一个翻译器,产生了C
代码,因此它继承了许多C
的属性。它最初的意图是带类的C,而不是带类加一点额外数学东西的C。
至于为什么在C++11
之前从未添加到标准中,您必须记住标准制定机构有特定的指导方针要遵循。例如,ANSIC
专门负责编码现有实践,而不是创建新语言。否则,他们可能会疯狂并给我们提供Ada :-)
该标准的后续版本也有具体的指南,并且可以在理论文档中找到(关于委员会做出某些决定的理由,而不是有关语言本身的理由)。
例如,C99
理论文档明确延续了C89
的两个指导原则,限制了可以添加的内容:
各个工作组为个人设置了指导方针,因此也限制了C++
委员会(以及所有其他ISO组)。
to_string
和lambda表达式都是方便的工具,可用于已经存在的操作。我想人们可能可以非常宽松地解释“只有一种方法来执行操作”,以允许这两者,同时几乎可以想象任何功能的重复,通过说“啊哈!不行!因为这些方便之处使其与精确等效但更冗长的替代品略有不同!”。对于lambda表达式这一点肯定是正确的。 - Steve Jessoppow
并不需要太多技巧。当然,我宁愿标准提供一些需要很多技能的东西,并且如果必须重复努力,会导致更多浪费的时间。 - paxdiablo对于任何固定宽度的整数类型,几乎所有可能的输入对都会溢出该类型。那么标准化一个在其大多数可能的输入下都不能给出有用结果的函数有什么用呢?
你需要一个大整数类型才能使该函数变得有用,并且大多数大整数库都提供了该函数。
编辑: 在问题的评论中,static_rtti写道“大多数输入会导致溢出?exp和double pow也是如此,我没有看到任何抱怨。” 这是不正确的。
让我们暂且不管exp
,因为这与重点无关(尽管实际上这会更加支持我的观点),并聚焦于double pow(double x, double y)
。这个函数在哪些(x,y)对的部分可以产生有用的结果(即不会简单地溢出或下溢)?
我只会关注一小部分的输入对,证明我的观点就足够了:如果x是正数且|y|≤1,则pow
不会溢出或下溢。这包括近四分之一的浮点数对(非NaN浮点数的一半为正,略小于一半的非NaN浮点数的大小小于1)。显然,还有许多其他输入对可以产生有用的结果,但是我们已经确定它至少占所有输入的四分之一。
现在让我们来看看一个固定宽度(即非大整数)的整数幂函数。在哪些输入中它不会简单地溢出?为了最大限度地提高有意义的输入对的数量,基数应为有符号的,指数应为无符号的。假设基数和指数都是n
位宽。我们可以轻松地获得有意义的输入部分的下界:
因此,在2^(2n)个输入对中,少于2^(n+1) + 2^(3n/2)个对将产生有意义的结果。如果我们看一下最常见的用法,即32位整数,这意味着大约千分之一的输入对不仅仅是溢出。
pow(x,y)
不会向零下溢。对于一些输入(大x,y接近-1),会出现非常狭窄的带状区域发生下溢,但在该范围内结果仍然是有意义的。 - Stephen Canonpow
的显然正确和最优实现只需要一个小小的查找表。 :-) - R.. GitHub STOP HELPING ICE因为在int类型中无法表示所有的整数次幂:
>>> print 2**-4
0.0625
int pow(int base, unsigned int exponent)
和 float pow(int base, int exponent)
函数。 - Ponkadoodleint pow(int base, unsigned char exponent)
的任何内容都有些无用。要么基数为0或1,指数不重要,为-1,在这种情况下,只有指数的最后一位是有意义的,要么是base >1 || base< -1
,在这种情况下,exponent<256
,否则会溢出。 - MSalters这实际上是一个有趣的问题。在讨论中,我没有找到一个简单的论点来解释参数的明显返回值不足。让我们来看看假设的 int pow_int(int, int)
函数可能失败的方式。
pow_int(0,0)
pow_int(2,-1)
该函数至少有2种故障模式。整数无法表示这些值,函数在这些情况下的行为需要由标准定义 - 程序员需要知道函数如何处理这些情况。
总的来说,省略该函数似乎是唯一明智的选择。程序员可以使用带有所有错误报告的浮点版本。
pow
吗?取两个大浮点数,将一个数的幂次方提高到另一个数,就会出现溢出。而且pow(0.0, 0.0)
也会导致你第二点所述的问题。你第三点才是实现整数和浮点数幂函数的唯一真正区别。 - numbermaniac简短回答:
对于自然数n
,将pow(x, 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;
}
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
函数调用并用三种算法中适当的选择替换它。(现在,如果你实际上要尝试使用这个,你可能需要稍微玩弄一下,因为我并没有确切地尝试编译我上面写的内容。 ;))pow
是有效的,而且 glibc 已经足够大了。C 标准应该是可移植的,包括对各种嵌入式设备的支持(事实上,到处都有嵌入式开发人员普遍认为 glibc 对他们来说已经太大了),如果对于每个简单的数学函数它需要包含每个可能有用的替代算法,那么它就无法移植。所以,这就是为什么它不在 C 标准中的原因。-s -O2
),这些标志可能与在我的系统(archlinux)上编译 glibc 时使用的标志相当或更差,所以结果可能是公正的。要进行更严格的测试,我必须自己编译 glibc,但我真的不想这样做。我曾经使用 Gentoo,所以我记得它需要多长时间,即使任务是自动化的。对我来说,结果已经足够(或者说是不确定的)。当然,你也可以自己尝试。pow(x, n)
的特殊化是非常重要的,如果需要精确的整数输出,这确实会发生。考虑为一个有 p^N 个元素的 N 维数组分配内存。如果 p^N 偏差超过一个,那么可能会随机出现 segfault。n
的每个位的分支预测错误。 https://godbolt.org/z/L9Kb98. gcc和clang未能将您的递归定义优化为简单的循环,并实际上在n
的每个位上进行分支。(对于pown_iter(double,unsigned)
,它们仍然分支,但可以使用x86 asm或C intrinsics实现无分支SSE2或SSE4.1的实现,但即使是这样也比递归好)。 - Peter Cordesdouble pow(double, int)
,但在C++11中已被删除,因为C99没有包含它们。参考http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2011/n3286.html#550。获得稍微更准确的结果也意味着获得稍微不同的结果。下面是一个非常简单的O(log(n)) pow() 实现,适用于任何数字类型,包括整数:
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十进制 TR 的第四部分¹在<math.h>
中添加了一些函数。其中两个函数族可能对此问题有所帮助:
pown
函数,它接受一个浮点数和一个 intmax_t
指数。powr
函数,它接受两个浮点数(x
和 y
),并使用公式exp(y*log(x))
计算x
的y
次幂。<math.h>
的未来发展可能会受到ISO/IEC/IEEE 60559标准未来发展的重大影响。¹ 你可以在这里找到一些正在进行中的文档 链接。
LONG_MAX
的指数使用pown
应该产生与使用LONG_MAX
不同的值,或者小于LONG_MIN
的值应该产生与LONG_MIN
不同的值?我想知道使用intmax_t
作为指数所获得的好处是什么? - supercatpow
的 FPU 指令。x86 有一个 y log2 x
指令 (fyl2x
),可以作为 pow
函数的第一部分使用,但是以这种方式编写的 pow
函数在当前硬件上需要数百个周期才能执行;一个良好编写的整数指数运算程序则要快几倍。 - Stephen Canon事实上,它确实如此。
自从C++11以来,pow(int, int)
有模板化的实现方式---甚至还有更加普通的情况,请参见(7)在http://en.cppreference.com/w/cpp/numeric/math/pow中
编辑:纯粹主义者可能会认为这不正确,因为实际上使用了“推广”输入法。无论如何,在 int
参数上,一个可以得到正确的结果或错误。
pow(Arithmetic1 base, Arithmetic2 exp)
,如果您已经阅读了描述,则会转换为double
或long double
。描述如下:“7)一组重载或函数模板,用于所有未被1-3)覆盖的算术类型参数的组合。如果任何参数具有整数类型,则将其转换为double。如果任何参数是long double,则返回类型Promoted也是long double,否则返回类型始终为double。” - phuclvpow(1.5f, 3)
= 1072693280
但是 pow(1.5f, float(3))
= 3.375
- Mark Jeronimusint pow(int, int)
,但 C++ 11 只提供了 double pow(int, int)
。请参考 @phuclv 的解释。 - xuhdev
double pow(int base, int exponent)
函数。该函数用于计算底数为整型的次方指数的幂值。 - Cubbi