Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

python

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

like image 523
JohnGalt Avatar asked Sep 26 '17 17:09

JohnGalt


People also ask

What is float ('- INF ') in Python?

Representing infinity as an Integer in python But in python, as it is a dynamic language, float values can be used to represent an infinite integer. One can use float('inf') as an integer to represent it as infinity.

Can float multiply?

First off, you can multiply floats. The problem you have is not the multiplication itself, but the original number you've used. Multiplication can lose some precision, but here the original number you've multiplied started with lost precision. This is actually an expected behavior.

What does INF mean in Python?

Definition and Usageinf constant returns a floating-point positive infinity. For negative infinity, use -math. inf . The inf constant is equivalent to float('inf') .

Why use integers over floats and not just floats all the time?

The int type automatically promotes to long when a computation goes beyond its range, thereby retaining precision. float doesn't get bigger to remain precise.


1 Answers

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.

like image 181
randomir Avatar answered Oct 06 '22 22:10

randomir