pow(std::complex<double>(randdouble,0), 2)
\(\notin \mathbb{R}\)
August 25, 2017
Yep, that's right, if you use std::pow
to square a real-valued complex number in C++11, you don't get a real number
dear lord, save me from egregious numerical error
So, there I was simplifying my syntax tree in Bertini2. I wrote some code, which eliminates 0's and 1's, and reduces the depth of the tree, by flattening it. And, I discovered that some systems don't have a nice property -- that the function values at random points before and after simplification differ. By a lot. Why?
Well, consider this SO post from /u/Schaki. They ask why, when using C++11, pow(i,2) has non-zero but small imaginary part. Like, 1e-16 or so. The answer is that there is a new implementation, and set of templates, for C++11 complex arithmetic. And it's scary.
Why is this so scary? Because pure-real complex numbers don't stay pure-real. And that causes enormous amounts of drift through a long sequence of complex operations.
\(\mathbb{R}\) must remain real under those operations for which it should. This is one of the greatest lessons I have learned from implementing Bertini_real, my surface and curve decomposer. If a number is real, theoretically, then make it be as real as the metal will let it.
My solution to this problem in Bertini2 was to add an overload for pow(std::complex<double>, int)
which uses successive squaring. It restores sanity, in the sense that the following two tests pass, where they wouldn't with just std::pow
.
BOOST_AUTO_TEST_CASE(complex_pow)
{
auto x = bertini::rand_complex();
using bertini::pow;
BOOST_CHECK_EQUAL(pow(x, 2), x*x);
}
BOOST_AUTO_TEST_CASE(complex_pow_on_real_stays_real)
{
using bertini::pow;
for (int ii=0; ii<100; ++ii)
{
auto x = dbl(bertini::RandReal());
auto result = pow(x, 2);
BOOST_CHECK_EQUAL(result, x*x);
BOOST_CHECK_EQUAL(imag(result), 0);
}
}