Constexpr 数学函数

69

我从这个页面注意到,C++11中的数学函数似乎都没有使用constexpr,而我认为它们所有的函数都可以使用constexpr。那么这就让我有两个问题,一个是他们为什么选择不将函数定义为constexpr。第二个对于像sqrt这样的函数,我可能可以编写自己的constexpr版本,但对于sin或cos之类的函数会更加困难,所以是否有其他方法解决。


3
他们没有选择不将函数设为constexpr。他们只是没有做出任何选择(我相信未来可能会有提议将它们改为constexpr)。 - R. Martinho Fernandes
@PeteBecker 并不是所有的都很复杂,但我想说的是为什么不自己做呢? - aaronman
3
注意,gcc已将大多数数学函数实现为constexpr,尽管这个扩展不符合标准,但这应该会改变。所以,这是可行的。 - Shafik Yaghmour
1
我认为这里必须要区分:在纯C++中,似乎几乎不可能将数学函数作为constexpr函数高效地实现。至少在C++11中对constexpr函数的限制下是如此。另一方面,数学函数是一个非常著名的有限函数集,编译器可以很容易地提供它们作为内置函数并在编译时进行求值。这种核心语言特性和库函数的混合可能被认为是丑陋的,但也值得一试。另请参见Alisdair Meredith在CppCon 2014上的演讲 - 5gon12eder
请参阅链接,了解向C++标准中添加更多constexpr <cmath>的提案。 - Holger Strauss
显示剩余3条评论
3个回答

74

实际上,由于旧有且烦人的遗留问题,几乎所有数学函数都不能被constexpr化,因为它们都会在各种错误条件下产生副作用,通常是域错误,并设置errno


1
不,我认为目前还没有可用的constexpr数学函数库,所以即使有也不会起作用。(当然,C++14将使实现它们更加方便和高效。) - Sebastian Redl
4
一个函数可以是constexpr但有时仍然可能抛出异常,等等。双精度浮点数之和的值可能取决于舍入模式,但在constexpr情况下会做出相应的调整。errno确实不是一个好的理由。 - Marc Glisse
5
抛出异常和errno是两种非常不同的东西。C标准数学函数的规定是修改errno,而不是抛出异常。因此,我不明白你的观点。 - Sebastian Redl
2
@SebastianRedl指定加法在结果不精确时设置FE_INEXACT。然而,加法仍然允许是constexpr的。你觉得这更接近于“errno”情况吗? - Marc Glisse
2
它很接近,但在C99中我找不到这样的规范,更不用说在C++11中了。最接近的是C99的附录F中对ISO 60559操作的引用。然而,C++只涉及C的库条款,因此该附录在C++中没有规范作用。我认为C++没有任何要求加法具有副作用。特别注意C++11第5节介绍中缺乏任何“收缩”浮点运算,而这些运算在C99的等效节中存在。 - Sebastian Redl
显示剩余2条评论

8

来自B. Stroustrup的《C++编程语言(第4版)》描述C++11:

"要在编译时进行评估,函数必须足够简单:constexpr函数必须由单个返回语句组成;不允许循环和局部变量。此外,constexpr函数可能没有副作用。"

这意味着它必须是内联的,不能有for、while和if语句以及局部变量。副作用也是被禁止的(例如更改errno)。另一个问题是大多数数学函数都是FPU指令,而这些指令在纯c/c++中没有表示(它们是用汇编代码编写的)。这就是为什么没有任何cmath函数被声明为constexpr的原因。


47
在C++14中,这不再是真的了。 - Marc Glisse
1
是的,说得好。我回答了为什么这些函数不能在C++11中成为constexpr的问题,因为问题是关于C++11的。 - Adam Szaj
另一个回答已经说明了errno是主要原因,不确定你的回答是否添加了其他重要信息。 - aaronman
1
@aaronman,正如Marc Glisse所说,errno不是一个好的理由,因为C++标准可以提供这些函数的并行实现,而不会修改errno。但即使如此,constexpr实现也需要一些内在的编译器特性才能实现。我不想争论,但我认为我指出了几个很好的理由。当然我可能是错的。我目前正在编写一个库,它将从constexpr数学函数中获益很多-这就是我来这里的原因-但我认为还有比'errno'更多的问题。 - Adam Szaj
3
constexpr函数(即使是C++11的)也是图灵完备的,所以我不明白你提出的问题,这里有一个我之前写的contexpr sqrt的例子。 - aaronman
2
很不错,但是试试 awsome::sqrt(3e13)。你尝试过实现sin、cos、tanh或类似的函数吗?我知道有算法可以实现这些函数,但你认为 constexpr_sin(x) == sin(x) 吗? - Adam Szaj

6
我注意到这个页面中没有一个c++11的数学函数使用constexpr,而我相信它们都可以使用。所以我有两个问题,第一个是为什么他们选择不使用constexpr来定义这些函数。
这部分已经由Sebastian Redl和Adam Szaj回答得很好了,所以我不会再添加任何内容。
对于像sqrt这样的函数,我可能可以编写自己的constexpr版本,但像sin或cos这样的函数会更棘手,所以有没有办法绕过它呢?
是的,您可以使用这些函数的泰勒级数展开式编写自己的constexpr sin、cos版本。看一下这个超酷的GitHub repo,它将几个数学函数作为constexpr函数实现:Morwenn/static_math

3
不那么酷:尝试计算 smath::cos(20.)。余弦值大于3……实现一个像样的非constexpr三角函数已经很难了,更不用说constexpr了。 - Ruslan
1
@Ruslan 谢谢您指出这个问题。我自己从来没有尝试过这个,但是我为了完成一些编译时的工作制作了自己的库 https://github.com/lakshayg/compile_time/ , 我相信它可以很好地处理这种情况。请尝试一下并让我知道您是否有建议(这还在开发中)。 - lakshayg
1
没有查看代码,但你的版本 1)未包含 <stdexcept>,因此需要进行调整才能编译;2)似乎只做了微不足道的范围缩减,因此例如 compile_time::cos(200.) 在 x86_64 gcc 上与 std::cos(200.) 差别很大,而对于参数为 2. 的情况结果则更或多或少相同。对于 long double 来说更糟糕。但是,它确实至少尝试进行范围缩减,不像 static_math - Ruslan
@Ruslan 感谢您的反馈,请问您能否提供有关您所使用平台的详细信息。在我的机器上(linux,x86_64,g++5.4)上编译代码没有任何警告/错误,错误率少于0.00001%。此外,我们应该在Github上继续这个讨论。 - lakshayg
是的,已经提交了一个问题报告。无论如何,精度问题不在于它是占百分比的哪一部分-而在于它有多少个ULP(最小单位)。在我的测试中,我得到了2 ** 17 ULP的误差,这对于IEEE754 64位double 不太好。 - Ruslan
谢谢!我会尝试修复这个问题。 - lakshayg

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接