ctypes' c_longdouble: underflow error (bug?)

Discussion in 'Python' started by kj, Jul 14, 2010.

  1. kj

    kj Guest

    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:

    >>> 0 == MYLIB.hg(100, 200, 100, 6000)

    from hg: 2.96517e-161
    False
    >>> 0 == MYLIB.hg(200, 200, 200, 6000)

    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?

    TIA!

    ~K
    kj, Jul 14, 2010
    #1
    1. Advertising

  2. On 07/14/2010 03:24 PM, kj wrote:
    >
    >
    >
    > 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:
    >
    >>>> 0 == MYLIB.hg(100, 200, 100, 6000)

    > from hg: 2.96517e-161
    > False
    >>>> 0 == MYLIB.hg(200, 200, 200, 6000)

    > 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
    Thomas Jollans, Jul 14, 2010
    #2
    1. Advertising

  3. kj

    kj Guest

    kj, Jul 15, 2010
    #3
  4. On 07/15/2010 06:41 PM, kj wrote:
    > In <> Thomas Jollans <> writes:
    >
    >> http://docs.python.org/library/ctypes.html#fundamental-data-types

    >
    >> c_longdouble maps to float

    >
    > 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.
    Thomas Jollans, Jul 15, 2010
    #4
  5. kj

    kj Guest

    In <> Thomas Jollans <> writes:

    >On 07/15/2010 06:41 PM, kj wrote:
    >> In <> Thomas Jollans <> writes:
    >>
    >>> http://docs.python.org/library/ctypes.html#fundamental-data-types

    >>
    >>> c_longdouble maps to float

    >>
    >> 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).


    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...
    kj, Jul 16, 2010
    #5
  6. On Jul 16, 2:53 pm, kj <> wrote:
    > 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.

    --
    Mark
    Mark Dickinson, Jul 16, 2010
    #6
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Wolfgang
    Replies:
    4
    Views:
    5,106
    Andrew Hobbs
    Feb 24, 2004
  2. Davis King
    Replies:
    0
    Views:
    500
    Davis King
    Jul 23, 2003
  3. Oplec
    Replies:
    9
    Views:
    9,477
    lvicks
    May 27, 2010
  4. Henk Punt
    Replies:
    0
    Views:
    391
    Henk Punt
    Jul 23, 2004
  5. Replies:
    0
    Views:
    495
Loading...

Share This Page