ctypes' c_longdouble: underflow error (bug?)

K

kj

I have a C library function hg that returns a long double, so when
I import it using C types I specify this return type like this:

MYLIB.hg.restype = ctypes.c_longdouble

But certain non-zero values returned by hg appear as zero Python-side.
If I modify hg so that it prints out its value right before returning
it, I get stuff like the following:
from hg: 2.96517e-161
Falsefrom hg: 5.28791e-380
True

So, although the value returned by hg in the second invocation
above is 5.28791e-380, Python sees it as 0.

What am I doing wrong?

TIA!

~K
 
T

Thomas Jollans

I have a C library function hg that returns a long double, so when
I import it using C types I specify this return type like this:

MYLIB.hg.restype = ctypes.c_longdouble

But certain non-zero values returned by hg appear as zero Python-side.
If I modify hg so that it prints out its value right before returning
it, I get stuff like the following:

from hg: 2.96517e-161
False
from hg: 5.28791e-380
True

So, although the value returned by hg in the second invocation
above is 5.28791e-380, Python sees it as 0.

What am I doing wrong?

Nothing.

http://docs.python.org/library/ctypes.html#fundamental-data-types

c_longdouble maps to float

http://docs.python.org/library/stdtypes.html#numeric-types-int-float-long-complex

"floating point numbers are implemented using double in C"

ergo, the extra precision a long double gives you versus a normal double
is lost if you use Python (or, at least, ctypes)

If you really want to keep the precision, you need a new/different type.
ctypes won't help you. Cython and NumPy may or may not be useful here.

- Thomas
 
T

Thomas Jollans

Thanks for pointing this out!
~K
(Does it make *any difference at all* to use c_longdouble instead
of c_double? If not, I wonder what's the point of having c_longdouble
at all.)

they're still different types (in C).

on my machine, a double is 64 bits wide, while a long double is 128 bits
wide. This means that a function that expects a long double argument
will expect 16 bytes, but ctypes will only pass 8 bytes if you tell it
to pass double. The same applies to return values.
 
K

kj

they're still different types (in C).

i understand that doubles and long doubles are different in C.
on my machine, a double is 64 bits wide, while a long double is 129 bits
wide. This means that a function that expects a long double argument
will expect 16 bytes, but ctypes will only pass 8 bytes if you tell it
to pass double. The same applies to return values.

This is extremely confusing. From my naive reading of the
documentation, I would have expected that the following two blocks
would produce identical results (expl is one of the standard C math
library exponential functions, with signature "long double expl(long
double)"):

MATH.expl.argtypes = [c_longdouble]
MATH.expl.restype = c_longdouble
print MATH.expl(0)

MATH.expl.argtypes = [c_double]
MATH.expl.restype = c_double
print MATH.expl(0)

....but no, they don't: the first one prints out the correct result,
1.0, while the second one prints out 0.0, of all things. (In fact,
with the second (mis)configuration, the value returned by MATH.expl
is always equal to its argument, go figure.)

I find these results perplexing because, based on the docs, I
expected that they *both* would be analogous to doing the following
in C:

printf("%f\n", (double) expl((double) 0.0)); /* prints out 1.000000 */

i.e., in *both* cases, expl would get passed a double (which gets
automatically cast into a long double), and in both cases its
returned value would be cast into a double. Clearly, this is not
what's happening, but I can't figure out the correct interpreation
of what's going on based on the documentation...
 
M

Mark Dickinson

This is extremely confusing.  From my naive reading of the
documentation, I would have expected that the following two blocks
would produce identical results (expl is one of the standard C math
library exponential functions, with signature "long double expl(long
double)"):

MATH.expl.argtypes = [c_longdouble]
MATH.expl.restype = c_longdouble
print MATH.expl(0)

MATH.expl.argtypes = [c_double]
MATH.expl.restype = c_double
print MATH.expl(0)

...but no, they don't: the first one prints out the correct result,
1.0, while the second one prints out 0.0, of all things.  (In fact,
with the second (mis)configuration, the value returned by MATH.expl
is always equal to its argument, go figure.)

This is just a case of garbage in, garbage out. In the second case
you're telling ctypes that the signature of expl is

double expl(double)

which just isn't true. ctypes has no way of telling that in fact expl
takes a long double rather than a double.
I find these results perplexing because, based on the docs, I
expected that they *both* would be analogous to doing the following
in C:

printf("%f\n", (double) expl((double) 0.0)); /* prints out 1.000000 */

No, not really. Assuming that expl has been declared somewhere (e.g.
you've included math.h), the C compiler knows that expl takes a long
double, so it'll convert the "(double) 0.0" argument to a long double
before passing it to expl.

Your second Python+ctypes example would be equivalent to doing
something like this in C:

#include <stdio.h>
double expl(double x); /* deliberate misdeclaration of expl */

int main(void) {
printf("%f\n", expl(0.0));
return 0;
}

which indeed produces 0.0 on my machine (gcc 4.2 / OS X 10.6). And
the compiler helpfully issues a warning:

test.c:1: warning: conflicting types for built-in function ‘expl’

Actually, the warning is a bit surprising, because there's only one
(wrong) declaration for expl, so what is there for it to conflict
with? The answer is that there are some commonly used library
functions that gcc has builtin versions for, so already knows the
signature of; expl is one of these. This is backed up by the fact
that when compiling with -fno-builtin-expl, no warning is issued.
i.e., in *both* cases, expl would get passed a double (which gets
automatically cast into a long double),

But how on earth would ctypes *know* it's supposed to convert
(nitpick: not cast) to a long double? The function signature isn't
available to ctypes; it's only through you setting .argtypes
and .restype that ctypes knows anything about it.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,744
Messages
2,569,484
Members
44,903
Latest member
orderPeak8CBDGummies

Latest Threads

Top