Curious case of missing π

Posted by Michał ‘mina86’ Nazarewicz on 28th of June 2022

π is one of those a constant which pops up when least expected. At the same time it’s sometimes missing when most needed. For example, consider the following application calculating area of a disk (not to be confused with area of a circle which is zero):

#include <math.h>
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char **argv) {
	for (int i = 1; i < argc; ++i) {
		const double r = atof(argv[i]);
		printf("%f\n", M_PI * r * r);
	}
}

It uses features introduced in the 1999 edition of the C standard (often referred to as C99) so it might be good to inform the compiler of that fact with a -std=c99 flag. Unfortunately, doing so leads to an error:

$ gcc -std=c99 -o area area.c
area.c: In function ‘main’:
area.c:8:18: error: ‘M_PI’ undeclared (first use in this function)
    8 |   printf("%f\n", M_PI * r * r);
      |                  ^~~~

What’s going on? Shouldn’t math.h provide the definition of M_PI symbol? It’s what the specification claims after all. ‘glibc is broken’ some may even proclaim. In this article I’ll explain why the compiler conspire with the standard library to behave this way and why it’s the only valid thing it can do.

The problem

First of all, it needs to be observed that the aforecited specification is not the C standard. Instead, it’s POSIX and it tags M_PI with an [XSI] tag. That marking means that M_PI ‘is part of the X/Open Systems Interfaces option’ and ‘is an extension to the ISO C standard.’ Indeed, The C99 standard doesn’t define this constant.

Trying to support multiple standards, gcc and glibc behave differently depending on arguments. With -std=c99 switch the compiler conforms to C standard; without the switch, it includes all the POSIX extensions.

A naïve approach would be to make life easier and unconditionally provide the constant. Alas, the M_PI identifier is neither defined nor reserved by the C standard. This means it can be used freely by the programmer and a conforming compiler cannot define. For example, the following is a well-formed C program and C compiler has no choice but to accept it:

#include <math.h>
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char **argv) {
	const double M_PI = 22.0 / 7.0;
	for (int i = 1; i < argc; ++i) {
		const double r = atof(argv[i]);
		printf("%f\n", M_PI * r * r);
	}
}

If compiler always defined M_PI in math.h the above code wouldn’t work.

The solution

The developer who needs π constant has a few ways to solve the problem. For maximum portability it has to be defined in the program itself. To make things work even when building on a Unix-like system, an ifdef guard can be used. For example:

⋮
#ifndef M_PI
#  define M_PI  3.141592653589793238462643383279502884
#endif
⋮

Another solution is to limit compatibility to Unix-like systems. In this case, M_PI constant can be freely used and -std switch shouldn’t be passed when building the program.

Feature test macros

glibc provides one more approach. The C standard has a notion of reserved identifiers which cannot be freely used by programmers. They are used for future language development and to allow implementations to provide their own extensions to the language.

For example, when C99 added boolean type to the language, it did it by defining a _Bool type. A bool, true and false symbols became available only through a stdbool.h file. Such approach means that C89 code continues to work when built with C99 compiler even if it used bool, true or false identifiers in its sown way..

Similarly, glibc introduced feature test macros. They all start with an underscore followed by a capital letter. Identifiers in this form are reserved by the C standard thus using them in a program invokes undefined behaviour. Technically speaking, the following is not a well-formed C program:

#define _XOPEN_SOURCE

#include <math.h>
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char **argv) {
	for (int i = 1; i < argc; ++i) {
		const double r = atof(argv[i]);
		printf("%f\n", M_PI * r * r);
	}
}

However, glibc documents the behaviour making the program well-defined.

It’s worth noting that uClibc and musl libraries handle the cases in much the same way. Microsoft uses the same technique though different macros. To get access to M_PI in particular, a _USE_MATH_DEFINES symbol needs to be defined. Newlib will define symbols conflicting with C standard unless code is compiled in strict mode (e.g. with -std=c99 flag). Lastly, Bionic and Diet Libc define the constant unconditionally which strictly speaking means that they don’t conform to the C standard.

Yes, I’m aware this link is to a draft. The actual standard is 60 USD from ANSI webstore. Meanwhile, for most practical uses the draft is entirely sufficient. It’s certainly enough for the discussion in this article.