JohnGalt
JohnGalt

Reputation: 897

Python: How come a large float turns into *inf* automatically?

on Python 3.6.2, Win10-x64

Just something curious that I encountered and couldn't quite explain.

In:

x = 10.0
for i in range(10):
    print(str(i) + " | " + str(x))
    x *= x

Out:

0 | 10.0
1 | 100.0
2 | 10000.0
3 | 100000000.0
4 | 1e+16
5 | 1e+32
6 | 1.0000000000000002e+64
7 | 1.0000000000000003e+128
8 | 1.0000000000000005e+256
9 | inf

How come it just turns into inf? Why doesn't it throw an exception?

If I replace the *= in the last line with **= for example, then by the second iteration it raises OverflowError: (34, 'Result too large'), which makes sense (since it is a mere 10.000.000.000^10.000.000.000).

Does this mean that there is a kind of "soft" limit on floats that -- when exceeded -- turns them into inf? And if so, what is that limit and is it the same regardless of the arithmetic operation? And wouldn't that imply something like inf == this_limit + anything?

.

ADD:

I get that there is the sys.float_info.max. Is that the limit?

I just got an idea and tested some stuff out:

print(sys.float_info.max)
print(sys.float_info.max + 1)
print(sys.float_info.max * 2)
print(sys.float_info.max * 1.000000000000001)
print(sys.float_info.max * 1.0000000000000001)

This gives me:

1.7976931348623157e+308
1.7976931348623157e+308
inf
inf
1.7976931348623157e+308

This is strange to me...

Upvotes: 14

Views: 3101

Answers (2)

randomir
randomir

Reputation: 18697

Go to source

If you inspect float operations implementation in CPython (floatobject.c), you can see that most operations are simply deferred to C double ops, like for example in float_add, float_sub, or float_mul:

static PyObject *
float_mul(PyObject *v, PyObject *w)
{
    double a,b;
    CONVERT_TO_DOUBLE(v, a);
    CONVERT_TO_DOUBLE(w, b);
    PyFPE_START_PROTECT("multiply", return 0)
    a = a * b;
    PyFPE_END_PROTECT(a)
    return PyFloat_FromDouble(a);
}

The result, a = a * b, is calculated on CPU, according to the IEEE 754 standard -- which proscribes the default result of -inf/+inf in case of an overflow (i.e. when the result can not be stored within the finite 32-bit float or 64-bit double).

Another thing to note is that any of the five floating-point exceptions defined in the IEEE 754 (like overflow) can either (1) produce the default value and store the exception information in the status word, or (2) raise SIGFPE signal if FP exception traps are enabled (see GNU lib C docs on FP exceptions).

In Python, traps are never enabled, so the program runs in a non-stop mode.

Multiplication overflow

That means, in turn, that the result of an overflow in the float_mul routine above will default to float inf (as defined in IEEE 754), and Python will simply return PyFloat_FromDouble(a), where a is inf.

Exponentiation overflow

On the other hand, if we inspect the float_pow (shortened version below):

static PyObject *
float_pow(PyObject *v, PyObject *w, PyObject *z)
{
    ...
    errno = 0;
    PyFPE_START_PROTECT("pow", return NULL)
    ix = pow(iv, iw);
    PyFPE_END_PROTECT(ix)
    Py_ADJUST_ERANGE1(ix);

    if (errno != 0) {
        PyErr_SetFromErrno(errno == ERANGE ? PyExc_OverflowError :
                             PyExc_ValueError);
        ...
    }
    ...
}

we can see the result of your x **= x would be inf, if it were not for the additional status word (~ errno) checking -- and raising of the Python OverflowError exception in case the underlying pow overflowed.


In conclusion (as already noted here), Python is not consistent in handling floating point exceptions -- sometimes will return the default (from C/libc/CPU), and sometimes will raise a Python exception.

Upvotes: 6

user2357112
user2357112

Reputation: 281842

How come it just turns into inf?

Because the result is too big for even the biggest finite float, so it overflows.

Why doesn't it throw an exception?

Because Python isn't very consistent about when it'll turn an inf/nan into an exception. Some operations give exceptions. Some give inf/nan.

Does this mean that there is a kind of "soft" limit on floats that -- when exceeded -- turns them into inf? And if so, what is that limit and is it the same regardless of the arithmetic operation? And wouldn't that imply something like inf == this_limit + anything?

Any result that would be bigger than 1.7976931348623157e+308 after rounding becomes an inf (or an exception, if Python feels like it). The exact limit is not expressible as a float; you cannot actually perform this_limit + anything, because trying to put this_limit into a float rounds it down to 1.7976931348623157e+308.

Upvotes: 7

Related Questions