在 C++11 中,
std::sqrt
定义为 constexpr
,即可以在其他 constexpr
函数或编译时上下文(如数组大小或模板参数)中合法使用它吗? g++ 似乎允许这样做(使用 -std=c++0x
),但我不确定我是否可以将其视为权威,因为 c++0x/c++11 支持仍然不完整。事实上,我似乎在互联网上找不到任何东西,这让我不确定。
看起来这应该是人们可以使用 Google 轻松找到的东西,但我已经尝试过(现在已经 40 分钟了......)但找不到任何东西。我可以找到几个将 constexpr 添加到标准库各个部分的建议(例如 this one),但没有关于
sqrt
或其他数学函数的内容。
std::sqrt
未定义为 constexpr
(我怀疑他们之后将其添加到最终标准中)。人们可能会编写这样一个版本,但标准库版本不是constexpr
。
这里是
double
浮点数的快速高效的 constexpr 实现。如果需要,您也可以将其改编为float
:
#include <limits>
namespace Detail
{
double constexpr sqrtNewtonRaphson(double x, double curr, double prev)
{
return curr == prev
? curr
: sqrtNewtonRaphson(x, 0.5 * (curr + x / curr), curr);
}
}
/*
* Constexpr version of the square root
* Return value:
* - For a finite and non-negative value of "x", returns an approximation for the square root of "x"
* - Otherwise, returns NaN
*/
double constexpr sqrt(double x)
{
return x >= 0 && x < std::numeric_limits<double>::infinity()
? Detail::sqrtNewtonRaphson(x, x, 0)
: std::numeric_limits<double>::quiet_NaN();
}
以防万一有人对元整数平方根函数感兴趣,这是我之前写的:
constexpr std::size_t isqrt_impl
(std::size_t sq, std::size_t dlt, std::size_t value){
return sq <= value ?
isqrt_impl(sq+dlt, dlt+2, value) : (dlt >> 1) - 1;
}
constexpr std::size_t isqrt(std::size_t value){
return isqrt_impl(1, 3, value);
}
下面是一个使用二分搜索的 constexpr 平方根实现。它可以在 gcc 和 clang 的 2^64 范围内正常工作,其他更简单的版本通常在数字 > 2^32 时失败,因为编译器将递归深度限制为例如 2^64。 200.
// C++11 compile time square root using binary search
#define MID ((lo + hi + 1) / 2)
constexpr uint64_t sqrt_helper(uint64_t x, uint64_t lo, uint64_t hi)
{
return lo == hi ? lo : ((x / MID < MID)
? sqrt_helper(x, lo, MID - 1) : sqrt_helper(x, MID, hi));
}
constexpr uint64_t ct_sqrt(uint64_t x)
{
return sqrt_helper(x, 0, x / 2 + 1);
}
下面是一个更好的版本(针对整数常量),需要 C++14,它类似于 Baptiste Wicht 的博客文章中介绍的版本。 C++14 constexpr 函数允许使用局部变量和 if 语句。
// C++14 compile time square root using binary search
template <typename T>
constexpr T sqrt_helper(T x, T lo, T hi)
{
if (lo == hi)
return lo;
const T mid = (lo + hi + 1) / 2;
if (x / mid < mid)
return sqrt_helper<T>(x, lo, mid - 1);
else
return sqrt_helper(x, mid, hi);
}
template <typename T>
constexpr T ct_sqrt(T x)
{
return sqrt_helper<T>(x, 0, x / 2 + 1);
}
现在有一个提案 P1383R0 More constexpr for
<cmath>
和 <complex>
(Edward J. Rosten, Oliver J. Rosten) 不幸的是还没有进入 C++20 并且从最新的评论来看可能不在C++23 也可以。 C++26?
如果我们查看最接近 C++11 N3337 的标准草案,我们可以看到
sqrt
未标记为 constexpr,来自 26.8
c.math 部分:
这些头文件的内容与标准C库相同 标题 和 分别具有以下内容 变化:
所有更改均不包括将 constexpr 添加到
sqrt
。
我们可以从问题gcc是否将非常量表达式函数的内置函数视为常量表达式看到,
gcc
将许多数学函数标记为constexpr作为扩展。这个扩展是一个不合格的扩展,正如我在回答链接问题时注意到的那样,当gcc
实现这个时,它看起来像是一个合格的扩展,但是这个改变了,gcc
很可能修复这个这个扩展保持一致。
consteval
版本,其灵感来自 Alex 的递归答案,但取消了递归,以获得更紧凑且编译速度稍快的代码:
#include <cmath>
#include <limits>
consteval double sqrt_consteval(double val) {
if(std::signbit(val)) return std::numeric_limits<double>::quiet_NaN();
if(val == std::numeric_limits<double>::infinity()) return std::numeric_limits<double>::quiet_NaN();
double result{val};
for(double last{0.0}; result != last; result = 0.5 * (result + val / result)) last = result;
return result;
}
在 Godbolt 上尝试一下:https://godbolt.org/z/WbjGWcYMr