Overview
Below is a simple program to estimate pi to the full precision of the provided type. It uses
Machin's formula, but this question is about coding style / choices. It compares the result to M_PI
from <cmath>
- best we have in C++17.
Questions
- Is my use of
enable_if_t
appropriate and idiomatic? - Is the use of
static_cast<T>
forone_over_5
andone_over_239
the "best" solution here? Neccesary, and appropriate? - How about the use of
decltype
,::epsilon
and::digits10
? - Accuracy: How about
sign =* -1
andden += 2.0
and... = sign * ...
? These are integer operations but use the floating point type. - My choice to force the user of
pi<T>()
to specify aT
?
Note: I know the templates should probably be in header file, but all in one here for simplicity on CR.
#include <cmath>
#include <iomanip>
#include <iostream>
#include <type_traits>
template <typename T, std::enable_if_t<std::is_floating_point_v<T>, bool> = true>
T arctan(T tan) {
// fabs(tan) > 1 will not converge
T num = tan;
T den = 1.0;
T sign = 1.0;
T term = num;
T res = num;
while (std::fabs(term) > std::numeric_limits<T>::epsilon()) {
num *= tan * tan;
den += 2.0;
sign *= -1.0;
term = sign * num / den;
res += term;
}
return res;
}
template <typename T, std::enable_if_t<std::is_floating_point_v<T>, bool> = true>
T pi() {
T one_over_5 = static_cast<T>(1.0) / static_cast<T>(5.0);
T one_over_239 = static_cast<T>(1.0) / static_cast<T>(239.0);
return (static_cast<T>(4.0) * arctan<T>(one_over_5) - arctan<T>(one_over_239)) *
static_cast<T>(4.0);
}
int main() {
auto pi_float = pi<float>();
auto pi_double = pi<double>();
auto pi_long_double = pi<long double>();
std::cout << std::setprecision(std::numeric_limits<decltype(pi_float)>::digits10)
<< pi_float << '\n'
<< std::setprecision(std::numeric_limits<decltype(pi_double)>::digits10)
<< pi_double << '\n'
<< std::setprecision(std::numeric_limits<decltype(pi_long_double)>::digits10)
<< pi_long_double << '\n'
<< M_PI << '\n';
return EXIT_SUCCESS;
}
Output - demonstrating that M_PI
from <cmath>
is actually incorrect at long double
precision in the last 2 digits (the correct value is 3.1415926535897932384626433...)
Note (unrelated to question, but for future reference): It turns out that the reason M_PI
is "wrong" is because the M_PI
macro does not specify a literal L
for long double
, and hence the value is rounded to double precision before being displayed. If you put a literal 3.14159265358979323846L
then it is accurate. Usually that is provided as the macro M_PIl
.
3.14159
3.14159265358979
3.14159265358979324
3.14159265358979312