This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

Title: C API Function PyLong_AsDouble Returning Wrong Value
Type: behavior Stage: resolved
Components: Versions: Python 3.7
Status: closed Resolution: not a bug
Dependencies: Superseder:
Assigned To: Nosy List: sajjadfx, tim.peters
Priority: normal Keywords:

Created on 2019-05-16 02:43 by sajjadfx, last changed 2022-04-11 14:59 by admin. This issue is now closed.

Messages (5)
msg342619 - (view) Author: Farhan Sajjad (sajjadfx) Date: 2019-05-16 02:43
Found this rather obscure behavior where certain 64 bit numbers are changing (probably losing precision somewhere down the call chain) if converted from PyLong to double using the PyLong_AsDouble C API function.


#define __SIZEOF_STRS__ 512

static PyObject*
test_pylong(PyObject* self, PyObject* args)
    char rBuffer[__SIZEOF_STRS__];
    char* strValue;
    if (!PyArg_ParseTuple(args, "s", &strValue))
    return NULL;

        printf("%s AS INGRESS\n", strValue);
        double dblValue = PyLong_AsDouble(
                PyLong_FromString(strValue, NULL, 10));

        snprintf(rBuffer, __SIZEOF_STRS__, "%.0f",
                 PyLong_AsDouble(PyLong_FromString(strValue, NULL, 10)));

        printf("CONVERT 1: %.0f\nCONVERT 2: %s\n", dblValue, rBuffer);



>>> test_pylong("1639873214337061279")
1639873214337061279 AS INGRESS
CONVERT 1: 1639873214337061376
CONVERT 2: 1639873214337061376
msg342620 - (view) Author: Tim Peters (tim.peters) * (Python committer) Date: 2019-05-16 02:58
Note that this pure Python gives the same result:

>>> "%.0f" % float(1639873214337061279)

That's not surprising:  int -> float conversion is limited to the precision of a Python float, which is a C double, which is almost certainly just 53 (not 64) significant bits.
msg342622 - (view) Author: Farhan Sajjad (sajjadfx) Date: 2019-05-16 03:14
Thanks for your input Tim. Here is what I understand:
1. In Python 3, int can be arbitrarily large.
2. C double data type can hold very large numbers, and the number tested here is quite small compared to the max. It even fits fine in a long long int.
3. Quite interestingly, this function/conversion works in Python 2.

>>> sys.float_info
sys.float_info(max=1.7976931348623157e+308, max_exp=1024, max_10_exp=308, min=2.2250738585072014e-308, min_exp=-1021, min_10_exp=-307, dig=15, mant_dig=53, epsilon=2.220446049250313e-16, radix=2, rounds=1)
msg342624 - (view) Author: Farhan Sajjad (sajjadfx) Date: 2019-05-16 04:03
Maybe I need to go back and understand why this loss of precision is taking place for the int->float conversion, and why for certain numbers.

Also, it does not work in Python 2. Please disregard the previous message.
msg342625 - (view) Author: Tim Peters (tim.peters) * (Python committer) Date: 2019-05-16 04:21
It sounds like you need to read an introduction (any!) to computer floating-point formats.  The relevant part in the output you showed is this:


As on almost other current machines, your platform's floating point format is restricted to 53 bits of precision.  Therefore it's impossible to convert any positive integer to float without losing bits if the integer is of the form I * 2**E where I is odd and I.bit_length() > 53.

That doesn't mean integers you can convert faithfully must be "small".  For example 2**200 can be converted to float exactly.  Apart from the power-of-2-exponent, that has only 1 significant bit.  You can also convert (2**52 + 1) * 2**200 to float exactly.  But NOT (2**53 + 1) * 2**200, because that requires 54 significant bits.
Date User Action Args
2022-04-11 14:59:15adminsetgithub: 81115
2019-05-16 04:21:47tim.peterssetmessages: + msg342625
2019-05-16 04:03:23sajjadfxsetstatus: open -> closed
resolution: not a bug
messages: + msg342624

stage: resolved
2019-05-16 03:14:57sajjadfxsetmessages: + msg342622
2019-05-16 02:58:40tim.peterssetnosy: + tim.peters
messages: + msg342620
2019-05-16 02:43:21sajjadfxcreate