19
$\begingroup$

C does not even have M_PI standardized. C++ only added std::numbers::pi very recently. Yes, the fact that this took so long does hint at some issues. Both languages have an upper bound to their precision of their floating point types (float, double, long double) which are of fixed size. π is just a number and could be offered in float, double and long double form. So, what are the 'issues' with adding π?

$\endgroup$
5
  • 6
    $\begingroup$ Insufficient locale support, which couldn't handle if a government were to redefine pi. $\endgroup$
    – R.M.
    Jul 7, 2023 at 1:21
  • $\begingroup$ @R.M. That doesn't really make sense to me, pi is and will always be the same number, anywhere in the entire universe. $\endgroup$ Jul 7, 2023 at 1:27
  • 3
    $\begingroup$ (I'm pretty sure it's a joke.) $\endgroup$ Jul 7, 2023 at 1:36
  • $\begingroup$ It is indeed a joke (hence comment and not answer). But it's also a winking nod to the dangers of baking into the language assumptions which might be valid for you but not for others. (See the various "Falsehoods programmers believe about ..." articles for potential examples.) $\endgroup$
    – R.M.
    Jul 7, 2023 at 14:36
  • $\begingroup$ Could it just be about namespace pollution? As far as I can tell, M_* identifiers aren't reserved, so a conforming program could have defined its own M_PI. (But I guess the standard could still define _M_PI instead.) $\endgroup$ Jul 23, 2023 at 4:34

4 Answers 4

9
$\begingroup$

Both languages have an upper bound to their precision of their floating point types (float double long double) which are of fixed size.

This does not appear to be true. The floating-point numbers types have lower bounds but not upper bounds for their bit-widths (see Wikipedia or Stack Overflow) and there weren't standardised floating-point types that require exact widths until C++23 (see Stack Overflow).

So, mandating a specific value for M_PI would preclude implementations from using more precise values in implementations where more precision would be supported.

I suppose the spec could have said something like "there must be a constant M_PI of type double whose value is the closest representable value to the mathematical constant π". That would work as a specification, though it would be a bit unusual to specify a constant without giving it a literal value. It would also potentially preclude implementations like this one, which load whatever value of π the processor uses; the processor's value of π might load into a register with more bits of precision than a double would normally have.

$\endgroup$
13
  • 4
    $\begingroup$ x87 is a known target. fldpi loads an 80-bit constant that's the nearest representable long double to Pi, and fst qword or dword will round that to the nearest representable double or float. AFAIK, all CPUs supporting x87 floating point use the same constant for fldpi. If that wasn't the case, and the same machine-code ABI could result in constants or calculations with different precision on different machines, then yeah, that would be incompatible with a specific constant or with there being a definition for std::numeric_limits<long double>::digits and so on $\endgroup$ Jul 6, 2023 at 19:43
  • 3
    $\begingroup$ Personally I see nothing wrong as mandating M_PI as containing the closest representable value to the ratio of a circle's circumference to its diameter. $\endgroup$ Jul 6, 2023 at 21:50
  • 2
    $\begingroup$ Better than having a constant for π would be to have standard functions which compute sin(2πx), cos(2πx), tan(2πx), atn(x,y)/2π, etc. Such functions could in many cases not only be faster than combination of a multiplication and a trig function, but they could in many cases be faster than a trig function without the 2π factor since the cost of argument reduction would be greatly reduced. $\endgroup$
    – supercat
    Jul 6, 2023 at 22:14
  • 1
    $\begingroup$ @supercat C23 adds functions to compute sin(pi*x) etc called sinpi(), cospi(), etc. So that is a step in the right direction. $\endgroup$ Oct 21, 2023 at 20:59
  • 1
    $\begingroup$ @user16217248: I wonder how long it will be before such functions are routinely implemented without a "scale by pi" step? I also wonder if there might be value in a family of "rough" trig functions which would indicate that results related to any angle within e.g. π/65536 of the correct angle would be equally acceptable, minimizing the number of cleanup steps after using e.g. an "approximate sine" function. $\endgroup$
    – supercat
    Oct 23, 2023 at 16:27
25
$\begingroup$

There is no deep reason. The answer is social:

  • The C standard does not specify any constant for π. We can only speculate on why, but ultimately, the set of things the C standard does and does not specify is somewhat arbitrary. We have no reason to believe this was an intentional omission rather than something nobody has bothered to specify.

  • POSIX specifies that math.h must define M_PI, but only if the appropriate feature test macro is set. MSVC requires _USE_MATH_DEFINES be defined before math.h is included. These were often used by C++ programmers, albeit non-portably.

  • A π constant is not difficult for programmers to define, so the absence of a portable way to access it was not a showstopper. Some programmers defined it themselves, others used definitions provided from third-party libraries like Boost.

  • Eventually, someone decided that relying on the non-portable definition of M_PI or user definitions was sufficiently annoying that they took the time to write up a proposal to standardize math constants in the C++ standard library, and that was that.

Nowhere in the proposal is there any indication that there were any technical obstacles to its implementation. Indeed, it makes it sound quite as though it was an arbitrary omission that nobody had bothered to rectify:

C++ inherited from C a rich library of mathematical functions which continues to grow with every release. Amid all this abundance, there is a strange gap: none of the major mathematical constants is defined in the standard. This proposal is aimed to rectify this omission.

Standardizing anything in a language with as many users as C++ is slow and takes a lot of time, even if it’s fairly uncontroversial. Sometimes obvious inconveniences don’t get fixed because nobody cares enough to go through that process when easy workarounds are available. Perhaps they hope someone else will get around to it.

Eventually, someone did.

$\endgroup$
1
  • $\begingroup$ Well, there certainly are technical obstacles to representing irrational numbers in ways that are both exact and generally useful. They're not necessarily relevant, as any DIY approach will have similar problems. $\endgroup$
    – user126527
    Jul 6, 2023 at 12:43
8
$\begingroup$

I can't find anything in the Rationale that specifically addresses mathematical constants, or the lack thereof, so I can only speculate.

  • Other popular programming languages at the time didn't have a standard π constant. Pascal didn't. BASIC didn't. Even FORTRAN, the original language of scientific computing, didn't have a standard π constant yet. A lot of code from the era wrote PI = 4 * atan(1) or equivalent to calculate it.
  • When C was first standardized in 1989, hardware floating-point wasn't as ubiquitous as it is today. Floating-point coprocessors (like the Intel 8087 for IBM PCs) were an expensive extra used only by serious number-crunchers. Compilers at the time provided the option to require a coprocessor, to emulate floating-point instructions in software (slowly), or to just not link the floating-point functions at all (hence GCC separating libm from libc to this day). The perception was that floating point was something to avoid unless you absolutely needed it, so <math.h> would be kept as minimalist as possible.
  • The committee didn't want to waste time on arguments about which constants to define (π? e? φ? τ?) or what type they should have (float? double? long double? All three?), and it was easier to just do nothing and leave this stuff up to the programmers.
$\endgroup$
4
  • 5
    $\begingroup$ I would note that to this day, in the embedded world -- where C still reigns -- there are quite a lot of micro-processors that still don't support floating points. $\endgroup$ Jul 6, 2023 at 13:47
  • 1
    $\begingroup$ @MatthieuM.: I'd assume you couldn't #include <math.h> on such machines. C does define some FP constants like DBL_MIN, so C implementations without FP support already need to "deal with" that. Or not since they're defined as CPP macros, and thus do nothing when not used. $\endgroup$ Jul 6, 2023 at 18:27
  • 1
    $\begingroup$ I would also add a fourth point: π is easy to get at near machine precision if you have working trig functionality and multiplication (4*atan(1)), and even without that you can get a reasonably good approximation suitable for many end-user applications with a single division operation (22/7, 333/106, 355/113, etc). IOW, if a programmer truly needed it, they could easily compute it themselves. $\endgroup$ Jul 6, 2023 at 21:20
  • 2
    $\begingroup$ @AustinHemmelgarn: If x is a value which is close to an integer multiple of the value of 2pi the machine's trig functions use for argument reduction, then the mathematical result one would get from subtracting the result of the machine's sin(x) function from x would be even closer to the value of 2pi the machine uses for argument reduction. however precise that representation happens to be. $\endgroup$
    – supercat
    Oct 24, 2023 at 16:01
4
$\begingroup$

There is no good way to write the exact value of PI in a C program without changing the compiler to add syntax for hex floating point constants; and writing them to lose no bits on conversion is tricky.

Writing const long double pi = 3.141592653589793238462643; doesn't work on most compilers. The last few bits come out wrong.

However you can always get PI by doing atan(1) * 4, and with no loss of precision if your runtime is any good. So there's just insufficient reason to compel the change to the compiler.


In comments on this answer it has been asked why we didn't end up with extern const long double pi;. The primary reason is there are no other such constants. All of the constants are actually macros. The secondary reason is the C standard library is itself meant to be portable, and so it was between Unix systems. The hackery required to make such a declaration of pi work (that is, where just assigning it the decimal value wouldn't work, which is the origin of the problem) would invariably end up being non portable. And no, you can't do const long double pi = atan(1) * 4; in the standard library. The no function calls in global initializers rule isn't arbitrary. There's simply no way to compile that.

$\endgroup$
6
  • $\begingroup$ Presumably it could be made to work on (more than) "most compilers" if it were specified in the Standard? $\endgroup$ Aug 17, 2023 at 18:50
  • $\begingroup$ @TobySpeight: Getting an exact binary result from a decimal floating point wasn't a solved problem in 1980, so no. You'd just have a non-implementable standard and a backwards compatibility headache with people who took an accidental dependency on pi being wrong. $\endgroup$
    – Joshua
    Aug 17, 2023 at 18:57
  • $\begingroup$ It's the Standard Library. So it can use platform-dependent magic to get an exact binary result (perhaps evaluating atanl(1) * 4 or equivalent within the compiler, or perhaps using a built-in (binary) constant). No reason for the implementation to use decimal. $\endgroup$ Aug 17, 2023 at 19:06
  • $\begingroup$ @TobySpeight: Oh. you mean extern const long double pi. That's a different question. $\endgroup$
    – Joshua
    Aug 17, 2023 at 19:07
  • 1
    $\begingroup$ You could still have the library #define PI __builtin_pi if necessary. $\endgroup$ Aug 18, 2023 at 9:55

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .