在C语言中的数学常量PI的值

92

计算圆周率是一个复杂的问题,维基百科讨论了对其进行的近似并表示计算准确的圆周率很困难。

C语言如何计算圆周率?它每次都计算还是使用不太准确的固定值?


30
仅仅使用计算到小数点后39位的圆周率,就足以计算整个宇宙的周长,其精度甚至比一个氢原子直径还要小。使用16位小数(大约是双精度double可以得到的精度)即可计算出太阳系的直径,误差不超过一根头发宽度。 - pmg
2
我在网上发现了这个链接,觉得非常有趣:https://crypto.stanford.edu/pbc/notes/pi/code.html - Francis Cugler
7个回答

117
在C语言中,Pi的定义在math.h头文件中:#define M_PI 3.14159265358979323846

2
而且该值是双精度值可用的最准确表示。 - Captain Giraffe
43
事实上,并非所有符合 C 标准的实现都会在 <math.h> 中定义 PI 常量,甚至 POSIX 规定的 M_PI 常量也不一定被定义。这是因为 POSIX 的一些要求与 C 标准有所冲突。但你可以在自己的程序中定义这样一个常量。 - Keith Thompson
2
所以它是固定值,没有更高的精度可能吗? - user1298016
8
如需使用 M_PI 时出现未定义错误,可通过 #define _USE_MATH_DEFINES 解决。 - spin_eight
4
@Danijel M代表“数学”。早些时候,所有的“数学常数”都以“M_”为前缀。还有一些类似于“M_E”,“M_LN10”等的内容。它们从来没有成为标准。 - Lundin
显示剩余10条评论

31

C语言中最接近“计算π”的方式可能就是直接使用acos(-1)或类似函数。这通常会通过多项式/有理逼近来计算被计算的函数(无论是在C中还是由FPU微码实现)。

然而,一个有趣的问题是计算三角函数(sin, cos, 和 tan)需要将它们的参数对2π取模。由于2π不是二进制有理数(甚至不是有理数),所以它不能用任何浮点类型表示,因此对于大参数使用任何精度逼近的值都会导致灾难性的误差积累(例如,如果x1e12,并且2*M_PI和2π之间相差ε,那么fmod(x,2*M_PI)与2π的正确值相差多达1e12*ε/π倍x mod 2π的正确值。也就是说,这是完全没有意义的。

C标准数学库的正确实现只需在其源代码中硬编码一个巨大的高精度π表示来解决正确的参数约减问题(并使用一些花哨的技巧使其不那么巨大)。这就是大多数/全部C版本的sin/cos/tan函数的工作原理。然而,某些实现(如glibc)在某些cpu上(如x86)上使用汇编实现,并且不执行正确的参数约减,导致完全无意义的输出。(顺便说一句,不正确的汇编通常对于小参数的速度与正确的C代码差不多。)


2
我正在寻找ISO C附录F中三角函数的参考资料,但显然它们不一定是IEEE三角函数(这将要求它们对所有输入都正确且正确舍入)。因此,这可能更多是一个QoI(实现质量)问题而不是正确性问题。但基于fdlibm的实现具有适用于可表示值域的整个域的参数缩减。 - R.. GitHub STOP HELPING ICE
1
好的,我明白你的意思了,感谢指向fdlibm。我的想法更多是计算这样一个大的x的正弦几乎没有意义,并且很可能是使用正弦函数的代码中的错误。如果x太大以至于成为整数,那么每个正弦周期只能表示大约6个x的值。当然可以找到满足条件的y,这很好,但我怀疑计算整数的正弦是否有任何有用的应用。 - Fritz
1
比整数x的极端情况更有趣的是分析这个“epsilon误差”随着x增加的速度。如果在1000*M_PI处存在显著误差,那么我理解这个问题,并且会完全同意您的观点。 - Fritz
2
@Fritz:根据我的答案中的粗略分析,fmod(x,2*M_PI)的误差与正确的参数缩减值在x的数量级上呈线性增长。因此,假设x在单位圆外部仅有1ulp的误差,那么在1000*M_PI处就会出现大约1000ulp的误差。在零点附近,sin接近于斜率为1的线性函数,因此参数中的ulp数量直接转化为结果中的ulp数量。 - R.. GitHub STOP HELPING ICE
1
顺便说一句,对于大的n值,sin(2^n)有一些有趣的应用;虽然我现在不记得它们是什么了。 - R.. GitHub STOP HELPING ICE
显示剩余3条评论

25

定义如下:

#define M_PI acos(-1.0)

它应该给出您正在使用的数学函数精确的PI值。 因此,如果他们在正切、余弦或正弦中更改了他们正在使用的PI值,则您的程序应始终保持最新状态 ;)


14
每次使用常量时,都会计算代价相当昂贵的函数(acos)。这是一种效率低下且不合理的方法。 - Jaromír Adamec
4
@JaromírAdamec 实际上,任何好的编译器都应该将该表达式优化为常量,因为acos是从标准库调用常量参数的纯函数;除非你关闭了优化。 - AquilaIrreale
2
相反,*C标准明确允许 - 甚至支持 - 编译器了解所有标准库函数的复杂细节! - Antti Haapala -- Слава Україні
1
@JaromírAdamec 我并没有对单个实现的质量发表任何意见。我只是陈述了一个符合ISO 9899中描述的C规范的C编译器明确地允许替换/内联/替代所有标准库的引用,前提是同名函数的行为等同于标准中定义的行为。各种编译器中存在的开关仅存在于这些现代实现中使得非严格符合的程序能够正确编译。 - Antti Haapala -- Слава Україні
1
acos函数是C语言的一部分。 - Kaz
显示剩余3条评论

10

无论如何,您都没有无限的准确性,因此C以这种方式定义常量:

#define PI 3.14159265358979323846

导入math.h即可使用此功能


这意味着我可以在程序中使用 double x = 1345*PI; 来获得比 C 中定义的更高精度的 PI 值吗?但是这仍然受程序中使用的 double 变量精度的限制,对吗?这是否意味着新定义的精确值是无用的? - user1298016
通过这个操作,你会失去一些精度,因为PI已经是尽可能准确的了。如果你想要更高的精度,你应该实现自己的结构并将结果存储在数组中(就像Java中的BigInteger)。好吗? - Maziar Aboualizadehbehbahani
1
实际上,M_PI 不是 C 语言的一部分。它是 POSIX 中 XSI 扩展选项的一部分。 - R.. GitHub STOP HELPING ICE
@R..,你确定这是仅限于XSI吗?文档上没有普通的[XSI]标记,就像[XSI]专用的东西一样(例如who)。 - Matthew Flaschen
是的,请看这里:http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/math.h.html 所有的 M_* 常量都带有 [XSI> 标签。 - R.. GitHub STOP HELPING ICE
2
啊,你正在查看SUSv2,这不是POSIX。这是在UNIX/POSIX规范合并之前,在此期间整个SUS都是XSI。从SUSv3开始,Unix和POSIX标准合并,其中XSI选项是未被认为足够有用或通用以强制要求所有POSIX系统支持的Unix部分。后来在SUSv4(POSIX 2008)中,一些更有用的XSI选项被移动到基本(POSIX)标准中,而不那么有用的选项逐渐被标记为过时,所以有些幸运地,XSI选项将停止存在...... - R.. GitHub STOP HELPING ICE

0

根据您使用的库,标准GNU C预定义数学常量在此处... https://www.gnu.org/software/libc/manual/html_node/Mathematical-Constants.html

您已经拥有它们,为什么要重新定义呢? 您的系统桌面计算器可能已经拥有它们,并且更加精确,因此您可以使用它们,但请确保不会与现有定义的常量冲突,以避免编译警告,因为它们往往会对此类事情进行默认设置。 享受吧!


0
你可以编写一个函数来计算PI,通过进行多个可能的无限和计算之一(我不建议使用像atan()这样的函数,因为这些函数可能自己以某种形式使用pi)。话虽如此,我不建议手动计算pi。没有人需要将pi精确到比math.h提供的M_PI更高的精度,事实上,最好只使用前几位数字,因为这将使您的代码稍微快一些。除非你想计算宇宙的大小到厘米为止,否则为什么要费心呢?
如果你仍然想计算它,以下是方法:
(来源于3blue1brown 用几何证明的Wallis乘积求π
double caculate_pi(int accuracy){
     double result = 1;
     int a = 2;
     int b = 1;

     for(i = 0;i < accuracy; i ++){
          result = a/b * result;
          if(a < b){
               a = a + 2;
          }
          else if(b < a){
               b = b + 2;
          }
     }

     return result * 2;
}

-3

我不确定 C 如何直接计算 PI,因为我比较熟悉 C++ 而不是 C。但是,你可以使用预定义的 C 宏或常量,例如:

#define PI 3.14159265359.....
const float PI = 3.14159265359.....
const double PI = 3.14159265359.....
/* If your machine,os & compiler supports the long double */
const long double PI = 3.14159265359..... 

或者你可以使用以下两个公式之一来计算:

#define M_PI acos(-1.0);
#define M_PI (4.0 * atan(1.0)); // tan(pi/4) = 1 or acos(-1)

个人认为,我不是100%确定,但我认为atan()acos()更便宜。


@RyanHaining 这是我犯的一个笔误,感谢您指出。我已经做了相应的更正。 - Francis Cugler
1
仍然不是有效的C代码。 - Ryan Haining
@RyanHaining 嗯,好的;我知道你可以在C++中做到这一点,但我不确定C是否允许。自从我工作在C上已经过去太多年了。有没有一个等效的方法,而不使用#define CONSTANT numberHere - Francis Cugler
一个具有静态或线程存储期限的对象初始化器中的所有表达式都应该是常量表达式或字符串字面值。这是[C2011, 6.7.9/4]规定的。我不清楚您希望在不依赖宏的替代方案中具有哪些特征,但这是您所面临的主要问题之一。 - John Bollinger
@JohnBollinger 有道理;我的 C 已经非常生疏了,因为我已经有近20年没有使用它了,而且我主要使用的是 C++。我最初回答这个问题时是从 C++ 的角度出发的... 我已经更新了答案以适应这一点... - Francis Cugler
显示剩余2条评论

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