Cách sử dụng hằng số PI trong C ++


476

Tôi muốn sử dụng các hàm PI và lượng giác trong một số chương trình C ++. Tôi nhận được các hàm lượng giác với include <math.h>. Tuy nhiên, dường như không có định nghĩa cho PI trong tệp tiêu đề này.

Làm thế nào tôi có thể nhận PI mà không cần xác định thủ công?


3
@tiwo, bạn đang hỏi sự khác biệt giữa 3.14, 3.141592atan(1) * 4?
Nikola Malešević

21
Là một lưu ý phụ, cmath nên được sử dụng trong C ++ thay vì math.h, dành cho C.
juzzlin

4
Liên quan một cách lỏng lẻo: xem cise.ufl.edu/~manuel/obfuscate/pi.c về cách tính giá trị PI trực tiếp từ định nghĩa.
lorro

Câu trả lời:


537

Trên một số nền tảng (đặc biệt cũ hơn) (xem các bình luận bên dưới), bạn có thể cần phải

#define _USE_MATH_DEFINES

và sau đó bao gồm tệp tiêu đề cần thiết:

#include <math.h>

và giá trị của pi có thể được truy cập thông qua:

M_PI

Trong math.h(2014) của tôi, nó được định nghĩa là:

# define M_PI           3.14159265358979323846  /* pi */

nhưng kiểm tra của bạn math.hđể biết thêm. Một trích xuất từ ​​"cũ" math.h(năm 2009):

/* Define _USE_MATH_DEFINES before including math.h to expose these macro
 * definitions for common math constants.  These are placed under an #ifdef
 * since these commonly-defined names are not part of the C/C++ standards.
 */

Tuy nhiên:

  1. trên các nền tảng mới hơn (ít nhất là trên Ubuntu 14.04 64 bit của tôi) Tôi không cần xác định _USE_MATH_DEFINES

  2. Trên các nền tảng Linux (gần đây) cũng có long doublecác giá trị được cung cấp dưới dạng Phần mở rộng GNU:

    # define M_PIl          3.141592653589793238462643383279502884L /* pi */

51
#define _USE_MATH_DEFINEStheo sau là #include <math.h>định nghĩa M_PItrong trực quan c ++. Cảm ơn.
Etan

3
Làm việc với các tiêu đề cygwin là tốt.
Cướp

24
Bạn luôn có thể bao gồm cmaththay vì math.h.
Richard J. Ross III

10
Ngay cả sau khi xác định _USE_MATH_DEFINESnếu GCC phàn nàn rằng đó là vì __STRICT_ANSI__được xác định (có lẽ bạn đã thông qua -pedantichoặc -std=c++11) không M_PItuân theo định nghĩa, do đó sẽ xác định nó với -D__STRICT_ANSI__. Khi tự xác định nó, vì đó là C ++, thay vì macro bạn nên constexpr auto M_PI = 3.14159265358979323846;.
huyền thoại2k

1
Kể từ năm 2018, câu trả lời chắc chắn sẽ được cập nhật để sử dụng <cmath> thay vì <math.h>
jaskmar

170

Pi có thể được tính là atan(1)*4. Bạn có thể tính giá trị theo cách này và lưu trữ nó.


78
Đối với người dùng c ++ 11:constexpr double pi() { return std::atan(1)*4; }
matiu

41
-1: Chỉ hoạt động nếu atan(1)*4 == 3.141592653589793238462643383279502884(nói đại khái). Tôi sẽ không đặt cược vào nó. Hãy bình thường và sử dụng một chữ thô để xác định hằng số. Tại sao mất độ chính xác khi bạn không cần?
Thomas Eding

29
Người ta có thể tránh các hoạt động nhân với atan2(0, -1);.
huyền thoại2k

44
@matiu atanthì không constexpr.
R. Martinho Fernandes

45
acos(-1)Thay vào đó hãy thử , không cần atan2.
dùng541686

113

Bạn cũng có thể sử dụng boost, định nghĩa các hằng số toán học quan trọng với độ chính xác tối đa cho loại được yêu cầu (tức là float so với double).

const double pi = boost::math::constants::pi<double>();

Kiểm tra tài liệu tăng cường để biết thêm ví dụ.


184
Boost: Tăng cường độ phức tạp không cần thiết của C ++ kể từ năm 1999!
Dan Mould

47
Hấp dẫn và một phần đúng. Mặt khác, việc tăng tốc có thể rất hữu ích vào những lúc ...
BuschnicK

59
@DanMoulding: Uhm. Có phải C là ngôn ngữ khác mà bạn biết? Bởi vì tất cả các ngôn ngữ khác mà tôi biết, ngoại trừ C, đều có thư viện chuẩn có cường độ lớn hơn C ++ '(ví dụ: Python, Haskell, C #, PHP, Delphi, Erlang, Java, ......). Từ kinh nghiệm cá nhân, tinh hoa not gonna use libs-opinion đó là một dịch hại và có lẽ là lý do số một cho phần mềm xấu được viết bằng C ++.
Sebastian Mach

11
@Gracchus: Yup. C ++ không có thư viện (hoặc không có thư viện C ++ 11 mới), tôi thích ngôn ngữ đó và tôi muốn tự mình viết mã mọi thứ, không hiệu quả lắm.
Sebastian Mach

14
Tôi tin rằng ông nói phức tạp không kích thước . Có lẽ đề cập đến a) 3 không gian tên lồng nhau và b) định nghĩa pi là một hàm templated chứ không chỉ là một hằng số bình thường.
Timmmm

83

Thay vào đó, hãy lấy nó từ đơn vị FPU trên chip:

double get_PI()
{
    double pi;
    __asm
    {
        fldpi
        fstp pi
    }
    return pi;
}

double PI = get_PI();

40
:-) có lẽ không phải là nền tảng độc lập, mà là một giải pháp kỳ lạ bổ sung tốt đẹp!
Etan

3
tôi yêu cách bạn mặc dù ra khỏi hộp ở đây;)
VivienLeger

1
Tôi thích câu trả lời này. Điều này đặc biệt hữu ích khi nhắm mục tiêu các nền tảng x86 cũ hơn, là một mốt nhỏ vào cuối thời gian mà trình biên dịch tối ưu hóa không liên quan khủng khiếp như các nền tảng hiện đại. Cảm ơn vì Henrik này!
Matt

49

Tôi khuyên bạn chỉ nên gõ pi để độ chính xác bạn cần. Điều này sẽ không thêm thời gian tính toán cho việc thực hiện của bạn và nó sẽ có thể di động mà không cần sử dụng bất kỳ tiêu đề hoặc #defines nào. Tính toán acos hoặc atan luôn đắt hơn so với sử dụng giá trị được tính toán trước.

const double PI  =3.141592653589793238463;
const float  PI_F=3.14159265358979f;

28
Đây là một ví dụ tuyệt vời tại sao chúng ta không nên thực hiện phương pháp này, mọi người chúng ta mắc lỗi, làm tròn, sao chép và dán, v.v ... Tôi nghĩ rằng sử dụng M_PI là cách tiếp cận phù hợp.
nacho4d

10
Nếu một người đang làm điều này trong C ++ 11, hãy tạo consta constexpr.
huyền thoại2k

3
@ nacho4d Tôi cũng thích M_PI hơn nếu có, nhưng không phải tất cả các hệ thống đều tuân thủ POSIX. Tôi nghĩ rằng phương pháp này tốt hơn phương pháp 4 * atan (1) cho các trường hợp không có sẵn M_PI.
m24p

2
"Tính toán acos hoặc atan luôn đắt hơn" là không đúng. Bất kỳ trình biên dịch tối ưu hóa hiện đại nào cũng biết tất cả về các hàm toán học tiêu chuẩn và có thể truyền liên tục thông qua chúng. Xem ví dụ: goo.gl/BvdJyr
Nemo

2
@Nemo, ví dụ về Counter: godbolt.org/g/DsAern Như đã nói ở nơi khác, hiện tại chỉ có GCC thực hiện điều này và đó có thể là do nó đã khai báo các hàm toán học cơ bản như constexpr.
Parker Coates

47

Thay vì viết

#define _USE_MATH_DEFINES

Tôi muốn giới thiệu sử dụng -D_USE_MATH_DEFINEShoặc/D_USE_MATH_DEFINES tùy thuộc vào trình biên dịch của bạn.

Bằng cách này, bạn chắc chắn rằng ngay cả trong trường hợp có ai đó bao gồm tiêu đề trước khi bạn làm (và không có #define), bạn vẫn sẽ có các hằng số thay vì lỗi trình biên dịch tối nghĩa mà bạn sẽ mất nhiều thời gian để theo dõi.


Mẹo tốt. Nếu "bạn" là một đơn vị biên dịch thì tất nhiên bạn có thể đảm bảo macro được xác định trước khi bao gồm mọi thứ. Nhưng nếu "bạn" là một tệp tiêu đề, nó nằm ngoài tầm kiểm soát của bạn.
Steve Jessop

3
Trên thực tế, ngay cả khi "bạn" là một đơn vị biên dịch ... tùy theo thứ tự của các tiêu đề là con đường ngắn nhất dẫn đến cơn ác mộng bảo trì ...
Matthieu M.

1
Tuy nhiên, bạn không phải phụ thuộc vào thứ tự của các tiêu đề. Việc các tiêu đề bao gồm lẫn nhau không quan trọng, miễn là bạn thực hiện #define trước khi bạn hoàn thành mọi thứ (ít nhất, giả sử rằng không có gì #undefs). Áp dụng tương tự cho NDEBUG.
Steve Jessop

1
Vấn đề rất phổ biến trong một dự án là nếu bạn biên dịch với Visual Studio chẳng hạn, bạn không biết trình biên dịch sẽ đi qua các tệp của bạn theo cách nào, vì vậy nếu bạn sử dụng <cmath>ở những nơi khác nhau thì sẽ trở thành một nỗi đau lớn (đặc biệt là nếu nó được bao gồm bởi một thư viện khác mà bạn đang bao gồm). Sẽ tốt hơn nhiều nếu họ đặt phần đó bên ngoài những người bảo vệ tiêu đề nhưng bây giờ không thể làm gì nhiều về điều đó. Lệnh biên dịch hoạt động khá tốt thực sự.
meneldal

40

Vì thư viện tiêu chuẩn chính thức không xác định PI không đổi, bạn sẽ phải tự xác định nó. Vì vậy, câu trả lời cho câu hỏi của bạn "Làm thế nào tôi có thể nhận PI mà không cần xác định thủ công?" là "Bạn không - hoặc bạn dựa vào một số tiện ích mở rộng dành riêng cho trình biên dịch." Nếu bạn không quan tâm đến tính di động, bạn có thể kiểm tra hướng dẫn sử dụng máy tính của bạn để biết điều này.

C ++ cho phép bạn viết

const double PI = std::atan(1.0)*4;

nhưng việc khởi tạo hằng số này không được đảm bảo là tĩnh. Tuy nhiên, trình biên dịch G ++ xử lý các hàm toán học này như là nội tại và có thể tính toán biểu thức hằng này tại thời gian biên dịch.


6
Tôi thường sử dụng acos (-1), như bạn nói, chúng được đánh giá theo thời gian biên dịch. Khi tôi kiểm tra M_PI, acos (-1) và atan (1) * 4, tôi đã nhận được các giá trị giống hệt nhau.
Mi-chê

2
Cách truyền thống là sử dụng 4*atan(1.): atandễ thực hiện và nhân với 4 là một thao tác chính xác. Tất nhiên, các trình biên dịch hiện đại có thể gập lại (nhằm mục đích gấp lại) tất cả các hằng số với độ chính xác cần thiết và hoàn toàn hợp lý khi sử dụng acos(-1)hoặc thậm chí std::abs(std::arg(std::complex<double>(-1.,0.)))là nghịch đảo của công thức Euler và do đó có tính thẩm mỹ cao hơn so với vẻ ngoài của tôi (Tôi đã thêm vào absvì tôi không ' t nhớ cách cắt mặt phẳng phức hoặc nếu nó được xác định ở tất cả).
tobi_s

Chỉ để không ai vô tình nghĩ rằng bạn nghiêm túc (một lần nữa -_- '). Đây là một giải pháp khủng khiếp. Việc thực hiện atan không được xác định bởi tiêu chuẩn có nghĩa là việc thực hiện và có thể phụ thuộc vào hw. Điều này có nghĩa là các con số có thể rất tệ, có nghĩa là bạn có thể tốt hơn khi sử dụng 3.14 nói chung. Hơn nữa nó khá có thể chậm, ngay cả đối với các trường hợp đặc biệt.
midjji

32

Từ trang người đàn ông Posix của math.h :

   The  <math.h>  header  shall  provide for the following constants.  The
   values are of type double and are accurate within the precision of  the
   double type.

   M_PI   Value of pi

   M_PI_2 Value of pi/2

   M_PI_4 Value of pi/4

   M_1_PI Value of 1/pi

   M_2_PI Value of 2/pi

   M_2_SQRTPI
          Value of 2/ sqrt pi

3
Câu trả lời tốt nhưng liên kết đã chết. Tôi đề nghị cái này thay thế.
Abderrahim Kitouni

30

C ++ 20 std::numbers::pi

Cuối cùng, nó đã đến: http://eel.is/c++draft/numbers

Tôi hy vọng việc sử dụng sẽ như thế nào:

#include <numbers>
#include <iostream>

int main() {
    std::cout << std::numbers::pi << std::endl;
}

Tôi sẽ dùng thử khi hỗ trợ đến GCC, GCC 9.1.0 mà g++-9 -std=c++2avẫn không hỗ trợ.

Đề xuất được chấp nhận mô tả:

5.0. Tiêu đề của bộ phận [tiêu đề] Trong bảng [tab: cpp.l Library.headers], một <math>tiêu đề mới cần được thêm vào.

[...]

namespace std {
namespace math { 
  template<typename T > inline constexpr T pi_v = unspecified;
    inline constexpr double pi = pi_v<double>;

Tất nhiên cũng có một std::numbers::ekhóa học :-) Làm thế nào để tính hằng số Euler hoặc Euler được cung cấp trong C ++?

Các hằng số này sử dụng tính năng mẫu biến C ++ 14: Mẫu biến C ++ 14: mục đích của chúng là gì? Bất kỳ ví dụ sử dụng?

Trong các phiên bản trước của dự thảo, hằng số nằm dưới std::math::pi: http://www.open-std.org/jtc1/sc22/wg21/docs/ con / 2019 / p0631r7.pdf


27

C ++ tiêu chuẩn không có hằng số cho PI.

Nhiều trình biên dịch C ++ định nghĩa M_PItrong cmath(hoặc trong math.hC) là một phần mở rộng không chuẩn. Bạn có thể phải #define _USE_MATH_DEFINEStrước khi bạn có thể nhìn thấy nó.


18

tôi sẽ làm

template<typename T>
T const pi = std::acos(-T(1));

hoặc là

template<typename T>
T const pi = std::arg(-std::log(T(2)));

Tôi sẽ không gõ vào số chính xác mà bạn cần . Điều đó thậm chí có nghĩa là gì? Độ chính xác bạn cần là độ chính xác T, nhưng chúng tôi không biết gì vềT .

Bạn có thể nói: Bạn đang nói về cái gì? Tsẽ float, doublehoặc long double. Vì vậy, chỉ cần gõ vào độ chính xác của long double, tức là

template<typename T>
T const pi = static_cast<T>(/* long double precision π */);

Nhưng bạn có thực sự biết rằng sẽ không có loại điểm nổi mới trong tiêu chuẩn trong tương lai với độ chính xác thậm chí còn cao hơn long doublekhông? Bạn không.

Và đó là lý do tại sao giải pháp đầu tiên là đẹp. Bạn có thể khá chắc chắn rằng tiêu chuẩn sẽ quá tải các hàm lượng giác cho một loại mới.

Và xin vui lòng, đừng nói rằng việc đánh giá hàm lượng giác khi khởi tạo là một hình phạt hiệu suất.


1
Lưu ý rằng arg(log(x)) == πcho tất cả 0 < x < 1.
0xbadf00d

Đây là một ý tưởng khủng khiếp. sử dụng một constexpr mẫu quá tải cho mỗi loại, theo cách đó bạn sẽ gặp lỗi biên dịch để buộc bạn phải xác định nó nếu một loại mới xuất hiện. Nó cũng nói chung là khủng khiếp vì các loại trig không giới hạn ở các loại dấu phẩy động. Vì vậy, hãy tận hưởng sai lầm atan (1) ... Tiêu chuẩn không đảm bảo rằng các hàm lượng giác tính toán các giá trị lượng giác thực tế của chúng với độ chính xác của loại. Họ thường không làm như vậy, và nó trở nên tồi tệ hơn với ví dụ fastmath và luôn đặc biệt tệ cho các giá trị đặc biệt.
midjji

10

Tôi sử dụng theo sau trong một trong những tiêu đề chung của tôi trong dự án bao gồm tất cả các cơ sở:

#define _USE_MATH_DEFINES
#include <cmath>

#ifndef M_PI
#define M_PI (3.14159265358979323846)
#endif

#ifndef M_PIl
#define M_PIl (3.14159265358979323846264338327950288)
#endif

Mặt khác, tất cả các trình biên dịch dưới đây xác định các hằng số M_PI và M_PIl nếu bạn bao gồm <cmath>. Không cần thêm `#define _USE_MATH_DEFINES vốn chỉ cần cho VC ++.

x86 GCC 4.4+
ARM GCC 4.5+
x86 Clang 3.0+

Có thể nhận xét downvoter về những gì sai với câu trả lời này. Điều này được nghiên cứu và thử nghiệm tốt và đang được sử dụng trong hệ thống thực. Tôi chắc chắn muốn cải thiện nó nếu có gì đó không ổn.
Shital Shah

1
Trình biên dịch FYI, Borland C ++ cũng xác định M_PImà không cần_USE_MATH_DEFINES
Remy Lebeau

8

Tôi thường thích xác định riêng của mình: const double PI = 2*acos(0.0);bởi vì không phải tất cả các triển khai đều cung cấp cho bạn.

Câu hỏi liệu hàm này có được gọi trong thời gian chạy hay không hoạt động trong thời gian biên dịch thường không phải là một vấn đề, bởi vì dù sao nó cũng chỉ xảy ra một lần.


8
acos (-1) cũng là pi.
Roderick Taylor

3
Nó thường ít hướng dẫn CPU hơn và / hoặc độ trễ thấp hơn để tải một toán hạng ngay lập tức hơn là đọc toán hạng từ một vị trí bộ nhớ. Ngoài ra, chỉ các biểu thức được biết tại thời gian biên dịch mới có thể được tính toán trước (ý tôi là double x = pi * 1.5;và tương tự). Nếu bạn từng có ý định sử dụng PI trong toán học giòn trong các vòng lặp chặt chẽ, tốt hơn bạn nên đảm bảo giá trị được trình biên dịch biết.
Eugene Ryabtsev

7

Tôi vừa xem qua bài viết này của Daniel Kalev , người có một mẹo rất hay cho C ++ 14 trở lên.

template<typename T>
constexpr T pi = T(3.1415926535897932385);

Tôi nghĩ điều này khá tuyệt (mặc dù tôi sẽ sử dụng PI có độ chính xác cao nhất có thể), đặc biệt là vì các mẫu có thể sử dụng nó dựa trên loại.

template<typename T>
T circular_area(T r) {
  return pi<T> * r * r;
}
double darea= circular_area(5.5);//uses pi<double>
float farea= circular_area(5.5f);//uses pi<float>

4

Các giá trị như M_PI, M_PI_2, M_PI_4, v.v. không phải là C ++ tiêu chuẩn nên một constexpr có vẻ là một giải pháp tốt hơn. Các biểu thức const khác nhau có thể được xây dựng để tính toán cùng một số pi và nó liên quan đến tôi liệu chúng (tất cả) có cung cấp cho tôi độ chính xác đầy đủ hay không. Tiêu chuẩn C ++ không đề cập rõ ràng cách tính pi. Vì vậy, tôi có xu hướng quay trở lại để xác định pi bằng tay. Tôi muốn chia sẻ giải pháp dưới đây hỗ trợ tất cả các loại phân số của pi một cách chính xác.

#include <ratio>
#include <iostream>

template<typename RATIO>
constexpr double dpipart()
{
    long double const pi = 3.14159265358979323846264338327950288419716939937510582097494459230781640628620899863;
    return static_cast<double>(pi * RATIO::num / RATIO::den);
}

int main()
{
    std::cout << dpipart<std::ratio<-1, 6>>() << std::endl;
}

2
Rất đẹp. Có thể cần phải có "l" hoặc "L" ở cuối số đó. Tôi nhận được một cảnh báo thu hẹp từ trình biên dịch gcc của tôi trên linux.
Cấp cho Rostig

2

Trên các cửa sổ (cygwin + g ++), tôi thấy cần phải thêm cờ -D_XOPEN_SOURCE=500cho bộ xử lý trước để xử lý định nghĩa M_PItrong math.h.


2
Đây không phải là một câu trả lời, mà là một nhận xét cho câu trả lời của fritzone.
0xbadf00d

2
@ 0xbadf00d: Đây là một câu trả lời hoàn toàn độc lập cung cấp các bước cần thiết để M_PIlàm việc trên một nền tảng cụ thể. Đó không phải là nhận xét về câu trả lời cho một số nền tảng khác nữa mà câu trả lời cho một số nền tảng khác là nhận xét về nền tảng này.
Ben Voigt

2

C ++ 14 cho phép bạn làm static constexpr auto pi = acos(-1);


9
std::acoskhông phải là một constexpr. Vì vậy, mã của bạn sẽ không được biên dịch.
0xbadf00d

@ 0xbadf00d Tôi đã biên dịch nó với g ++
Willy Goat

12
@WillyGoat: Sau đó, g ++ đã sai, vì acoskhông có constexprtrong C ++ 14 và không được đề xuất để trở thành constexprngay cả trong C ++ 17
Ben Voigt

@BenVoigt có bất kỳ hàm toán học constexprnào không? Rõ ràng là không: stackoverflow.com/questions/17347935/constexpr-math-fifts
wcochran

1
@wcochran: Có rất nhiều hàm toán học MỚI constexpr, ví dụ, xem ( github.com/kthohr/gcem ). Nhưng chúng không tương thích ngược với các hàm C cùng tên, vì vậy chúng không thể chiếm lấy các tên cũ.
Ben Voigt

2

Một số giải pháp thanh lịch. Tôi nghi ngờ rằng độ chính xác của các hàm lượng giác bằng với độ chính xác của các loại mặc dù. Đối với những người thích viết một giá trị không đổi, điều này hoạt động cho g ++: -

template<class T>
class X {
public:
            static constexpr T PI = (T) 3.14159265358979323846264338327950288419\
71693993751058209749445923078164062862089986280348253421170679821480865132823066\
47093844609550582231725359408128481117450284102701938521105559644622948954930381\
964428810975665933446128475648233786783165271201909145648566923460;
...
}

Độ chính xác 256 chữ số thập phân phải đủ cho bất kỳ loại kép dài dài nào trong tương lai. Nếu cần nhiều hơn, hãy truy cập https://www.piday.org/million/ .



1

Bạn có thể làm được việc này:

#include <cmath>
#ifndef M_PI
#define M_PI (3.14159265358979323846)
#endif

Nếu M_PIđã được xác định trong cmath, điều này sẽ không làm gì khác hơn là bao gồm cmath. Nếu M_PIkhông được xác định (ví dụ như trong Visual Studio), nó sẽ xác định nó. Trong cả hai trường hợp, bạn có thể sử dụngM_PI để lấy giá trị của pi.

Giá trị này của pi đến từ qmath.h của Qt Creator.


1

Bạn có thể sử dụng:

#define _USE_MATH_DEFINES // for C++
#include <cmath>

#define _USE_MATH_DEFINES // for C
#include <math.h>

Các hằng số toán học không được định nghĩa trong C / C ++ tiêu chuẩn. Để sử dụng chúng, trước tiên bạn phải xác định _USE_MATH_DEFINESvà sau đó bao gồm cmathhoặc math.h.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.