[libcxx/test] Puzzling floating point behaviour

Hi all,

When running the libcxx I got asserts in complex.transcendentals. After a lot of digging I have the following:

-- pow_complex_test.cpp:
#include <complex>
#include <cmath>
#include <cstdio>

int main()
{
     const std::complex<long double> a = std::complex<long double>(2, 3);
     const std::complex<long double> b = std::complex<long double>(2, 0);
     std::complex<long double> x = std::complex<long double>(-5, 12);

     std::complex<long double> c = pow(a, b);

     printf("c = pow(a,b) = %.20Le + i*%.20Le\n", real(c), imag(c));
     printf("x = %.20Le + i*%.20Le\n\n", real(x), imag(x));
     printf("err = c - x = %.20Le + i*%.20Le\n\n", real(c-x), imag(c-x));

     long double r_a = sqrt(real(a)*real(a) + imag(a)*imag(a));
     long double th_a = atan(imag(a)/real(a));

     /* bw = b*log(a) = 2*log(r_a) + i*2*th_a */

     std::complex<long double> bw = b*log(a);

     /* d = exp(x) = exp(xr) * (cos(xi) + i*sin(xi) */

     std::complex<long double> d = exp(b*log(a));
     std::complex<long double> dx0 = exp(std::complex<long

(bw.real(), bw.imag()));

     std::complex<long double> dx1 =
         std::complex<long double>(exp(bw.real()) * cos(bw.imag()),
                                   exp(bw.real()) * sin(bw.imag()));
     std::complex<long double> dx2 =
         (long double)exp(bw.real()) *
         std::complex<long double>(cos(bw.imag()), sin(bw.imag()));

     printf("d = exp(b*log(a)) = %.20Le + i*%.20Le\n", real(d), imag(d));
     printf("dx0 = %.20Le + i*%.20Le\n", real(dx0), imag(dx0));
     printf("dx1 = %.20Le + i*%.20Le\n", real(dx1), imag(dx1));
     printf("dx2 = %.20Le + i*%.20Le\n\n", real(dx2), imag(dx2));
     printf("err = d - x = %.20Le + i*%.20Le\n", real(d-x), imag(d-x));
     printf("err = dx0 - x = %.20Le + i*%.20Le\n", real(dx0-x), imag(dx0-x));
     printf("err = dx1 - x = %.20Le + i*%.20Le\n", real(dx1-x), imag(dx1-x));
     printf("err = dx2 - x = %.20Le + i*%.20Le\n", real(dx2-x), imag(dx2-x));

}

I'm not getting quite the same results as you, but your testcase isn't
really testing what you want to test: std::exp is not the same as
::exp.

-Eli

Thanks, Eli, that's probably a big clue, but I don't get it. This is probably a stupid question, but can you explain? I would expect all exp's to be in std::. What's the difference?

Thanks, Edward

std::exp is the properly overloaded exp defined by the C++ standard.
::exp isn't overloaded, so it loses precision.

-Eli

I am certainly not convinced that things are working properly. The attached program and test script tests 8 cases, all compiled with fresh clang++ and fresh libc++:

library WITH_NS_STD WITH_EXP_OVERLOADED Result

pow_complex_test.cpp (1.79 KB)

test_pow.sh (1.46 KB)

Eli alluded to the difference. In 7 places of pow_complex_test.cpp you're picking up the wrong math function when WITH_NS_STD is not defined.

In <math.h> you have:

double exp(double);
double sin(double);
double cos(double);

In <cmath> you *additionally* have:

namespace std
{
float exp(float);
double exp(double);
long double exp(long double);

float sin(float);
double sin(double);
long double sin(long double);

float cos(float);
double cos(double);
long double cos(long double);
}

In your WITH_EXP_OVERLOAD you silently convert long double to double and call ::exp, ::cos and ::sin unless WITH_NS_STD is defined.

Ditto in main() when computing dx1. You should qualify all of these calls with std:: to prevent this, e.g.g:

    long double __e = std::exp(__x.real());
    return std::complex<long double>(__e * std::cos(__i), __e * std::sin(__i));

Howard

We do agree that this is illegal, even in C++11, right?

No.

D.5 C standard library headers [depr.c.headers]

1 ...

2 Every C header, each of which has a name of the form name.h, behaves as if each name placed in the standard library namespace by the corresponding cname header is placed within the global namespace scope. It is unspecified whether these names are first declared or defined within namespace scope (3.3.6) of the namespace std and are then injected into the global namespace scope by explicit using-declarations (7.3.3).

3 [ Example: The header <cstdlib> assuredly provides its declarations and definitions within the namespace std. It may also provide these names within the global namespace. The header <stdlib.h> assuredly provides the same declarations and definitions within the global namespace, much as in the C Standard. It may also provide these names within the namespace std. — end example ]

Howard

As far as I can tell, that allows you to declare functions in the global namespace, but not to omit half of the declarations. It thought it was a well-known defect that the change made in C++11 still didn't make the plain C headers valid in C++, so I am a bit surprised at your reaction...

Is your complaint with <math.h> or <cmath>?

Howard

math.h, sorry for not being more precise.
(I have not tried to interpret whether the wording allows cmath to leave the global namespace with only half the overloads declared, but that's a different problem)

I see. Yes, I think you are correct. However the gist of the issue that changed these words was that C++98/03 way over-stepped its bounds in trying to specify the contents of the name.h headers:

http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-defects.html#456

Therefore if this gets brought to the attention of the LWG (and I think it probably should), it's a pretty safe bet that the action taken will be to move more of the name.h specification off to the C standard. Indeed my opinion is that the name.h headers should be removed from the C++ standard. They would still be available to C++ programers. But if you want to find out what they do, you have to read the C standard.

At the very least, anything you read in the C++ standard about the name.h headers, you should take with a grain of salt. The C++ community should be thrilled we got the C prototypes wrapped in extern "C" in the name.h headers. :wink:

Howard

Ah, I think the penny dropped.

In which case: shouldn't the template for std::pow be specialized into double and long double functions, with one calling exp and the latter calling expl?

Anyway, thanks to all who replied,

-- Ed.

Strike that remark. I'm dealing with a linker issue on my system. Here's why:

* Linked with '-nodefaultlibs -lm' gives incorrect results (err~2e-10)
* Linked without those linker options gets me err = 0.

Something gets pulled from libgcc* that makes things work. Don't know why yet.

Leason: stray of the golden path and you learn a lot. For those who like a linker puzzle (and walk into the dire strait of my private investigations), the compile/link logs are attached.

-- Ed.

libm.log (10.5 KB)

nolibm.log (12.5 KB)

pow_complex_test.cpp (1.12 KB)