在C/C++中,有没有更好/更便携的定义圆周率(PI)的方法?

5
目前在我的科学计算代码中,我有以下内容:
//Include M_PI constant
#define _USE_MATH_DEFINES
//Needed for square root function.
#include <math.h>

这个在Linux上可以运行...至少我没有在所有平台上测试过C编译器。然而,最近在调查一些旧的Fortran代码时,我偶然发现了另一个代码中定义π的聪明方式(不是我自己的):
<angle in deg.>*8.0d0 * datan(1.0d0) / 360.0d0

当然,在C语言中这是完全可行的,对吧?所以我可以定义我的转换函数类似于:
inline double DegToRad(const double A)
{
   return A * atan(1.0) / 45.0;
}

哪种方法更具可移植性?是否有任何数字(例如四舍五入)考虑因素,使得使用一种方法比另一种方法更合适?
我确实喜欢M_PI常数使代码更易读。当然,我也可以通过上述方法自己分配一个PI常数。
对于将针对多个平台(Windows、Linux等)的C/C++代码来说,什么被认为是最佳实践?
2个回答

6
不要忽视可读性问题;个人认为,我会做以下处理:
#ifndef M_PI
// Source: http://www.geom.uiuc.edu/~huberty/math5337/groupe/digits.html
#define M_PI 3.141592653589793238462643383279502884197169399375105820974944592307816406 
#endif

#define DEG_TO_RAD (M_PI/180.0)
#define RAD_TO_DEG (180.0/M_PI)

然后声明

inline double DegToRad(const double deg) {
   return deg * DEG_TO_RAD;
}

inline double RadToDeg(const double rad) {
   return rad * RAD_TO_DEG;
}

这可能不会更加可移植(因为在C和C++中,atanM_PI都是标准的)。然而,使用M_PI将比使用atan更易读,并且根据编译器的优化设置,可能会避免昂贵的三角函数调用。
更新:看起来M_PI并没有我想象中那么标准。在上面的#ifndef包含中应该解决了它不可用的情况。

好的,明白了... 我不知道M_PI是独占于GNU或某些平台的... 我见过更奇怪的东西... - Jason R. Mick
2
如果你遇到一个不符合标准的老编译器,你可以简单地定义自己的 M_PI,用 #ifndef M_PI ... #endif 括起来。 - andand
1
"M_PI"是POSIX的特性。它在大多数平台上都可用,但不保证符合C标准。 - Stephen Canon
关于Stephen Canon,具体是SUSv2 / Unix98:http://pubs.opengroup.org/onlinepubs/7990989775/xsh/math.h.html - Conrad Meyer
我认为 #define DEG_TO_RAD (M_PI/180.0) 更好,否则使用 1.0/DEG_TO_RAD 的人会感到失望。 - dmuir

1

通用解决方案

一种可能的方法是使用宏:

#define PI 3.1415926535897932384626433832795028L
#define DEG_TO_RAD(...) ((__VA_ARGS__) * (PI / 180))
// note: write DEG_TO_RAD(x) in C89

这适用于任何版本的 C 和 C++。在这里使用的数字数量恰好足够表示 binary128 浮点格式。 这很重要,因为 long double 可能是一个 128 位的数字。

M_PI 宏不具有可移植性,应该避免使用它。 你可以定义自己的宏,在所有平台上都保证存在。

非宏定义的 pi 值

// C23 / C++11 compatible constants.
// Ideally, make these inline in C++17 to avoid possibly ODR violations.
// In C89/C++98, these could be declared as static const instead.
constexpr long double pil = 3.1415926535897932384626433832795028L;
constexpr double pi = (double) pil;
constexpr float pif = (float) pi;

// C++17 solution using constrained variable template
template <typename T, std::enable_if_t<std::is_floating_point_v<T>, int> = 0>
inline constexpr T pi_v = T(3.1415926535897932384626433832795028L);

// note: this is unnecessary in C++20 thanks to std::numbers::pi in <numbers>

度数转弧度

// C++20 using constraints and the new numeric constants
#include <numbers>

template <std::floating_point T>
T deg_to_rad(T x) {
    return x * std::numbers::pi_v<T>;
}

// C++17, see the variable template above
template <typename T, std::enable_if_t<std::is_floating_point_v<T, int> = 0>
T deg_to_rad(T x) {
    return x * pi_v<T>;
}

// C11 using generic selection
float deg_to_radf(float x) { return x * (pif / 180); }
// ...
#define deg_to_rad(...) _Generic((__VA_ARGS__), \
    float: deg_to_radf,                         \
    double: deg_to_rad,                         \
    long double: deg_to_radl)(__VA_ARGS__)

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