Kitabı oku: «El gran libro de Python», sayfa 8

Yazı tipi:

Operadores de comparación

El símbolo == representa el operador de igualdad y permite comprobar si dos objetos tienen el mismo valor:


Aunque dos objetos tengan el mismo valor, no significa que sean el mismo objeto en memoria. Como vimos en el Capítulo 1, debemos utilizar la palabra clave is para saber si dos objetos tienen la misma identidad:


NOTA

Como ya dijimos en el Capítulo 1, cuando Python encuentra los literales de los tipos inmutables, por cuestiones de optimización (independientemente de la implementación) prefiere reutilizar objetos ya existentes antes que crear otros nuevos, sobre todo si se trata de objetos que ocupan poco espacio en memoria:


El operador != devuelve True si sus operandos tienen valores distintos:


Los operadores < , <=, > y >= devuelven True si su primer operando es, respectivamente, menor, menor o igual, mayor, mayor o igual que su segundo operando:


Estos operadores pueden ser también vinculados el uno al otro:


Operadores de desplazamiento

Los operadores de desplazamiento a la izquierda y desplazamiento a la derecha son representados, respectivamente, por los símbolos << y >>. Para mostrar su funcionamiento utilizamos la representación binaria:


Dado un número num, efectuar un desplazamiento a la izquierda de n posiciones equivale a multiplicarlo por 2 ** n:


Acabamos con algún ejemplo de desplazamiento a la derecha:


Las enumeraciones de enteros

Con la PEP-0435 (Python 3.4) se presentaron las enumeraciones. Estas forman parte de los tipos integrados, por lo que, si queremos utilizarlas, debemos importar el módulo enum. En esta sección nos ocuparemos solo de las enumeraciones de enteros, mientras que en el Capítulo 6 veremos con más detalles las enumeraciones genéricas. Las enumeraciones de enteros son instancias del tipo enum.IntEnum:


El primer argumento pasado a IntEnum es el nombre que hemos asignado a la enumeración:


El segundo argumento es una secuencia que contiene los elementos (los atributos) de la enumeración. Por ejemplo, a la cadena 'espagueti lasaña margaritas', le corresponden tres elementos:



NOTA

Hemos utilizado la etiqueta Pasta (con la inicial en mayúsculas) porque las instancias de IntEnum son clases. Profundizaremos en este concepto en el Capítulo 6, en el cual hablaremos de las metaclases.

Cuando los elementos se indican mediante una cadena de texto, como en el caso precedente, además de con un espacio pueden ser separados con una coma:


En general, pueden ser expresados como elementos de una secuencia:


Los elementos tienen un atributo name, que es una cadena que representa el nombre del elemento, y un atributo value, que representa el valor:


Los objetos de tipo enum.IntEnum se denominan enumeraciones de enteros porque sus elementos son números enteros:


y se comportan como tales:


Pueden ser utilizados en cualquier lugar, en el sitio de un entero:


por tanto, podemos compararlos entre ellos o con otros números reales:


Los elementos de una enumeración son de solo lectura: no pueden ser reasignados:


ni cancelados:


Tampoco su valor puede ser reasignado:


Hablaremos con detalle de las enumeraciones en el ejercicio final del Capítulo 6.

Aritmética de los números enteros

Hemos dicho que el tipo int permite representar cualquier entero, sin ninguna excepción. Solo la memoria de nuestra máquina puede limitar el rango de valores admitidos, dado que el número de bytes necesarios para codificar un entero aumenta cuando se aumenta el valor absoluto del mismo entero. Podemos comprobar lo que acabamos de decir utilizando la función sys.getsizeof(), la cual devuelve el tamaño de un objeto expresado en bytes:



Esta carácterística no es evidente, puesto que no todos los lenguajes de programación gestionan los enteros como lo hace Python. Por ejemplo, con los tipos nativos de algunos importantes lenguajes de más bajo nivel, como C, C++ o Java, no es posible representar todos los enteros, sino solo aquellos mayores que un valor proporcionado mínimo y menores que un valor proporcionado máximo.

Aquellos que no atienden a estas problemáticas corren el riesgo de cometer graves errores, incluso efectuando una sencilla suma de dos enteros. Consideremos por ejemplo el siguiente código C++:


Este asigna el valor 9223372036854775807 a la variable a, el valor -(a+1) a la variable b, muestra a, a+1, b y b-1. Esto es lo que ocurre si lo compilamos y lo ejecutamos:


Como podemos ver, 9223372036854775807 + 1 da un resultado negativo, mientras que -9223372036854775808 -1 da un resultado positivo. Este error es debido al hecho de que a la variable a se ha asignado el valor máximo que puede tener un entero con signo, codificado con 64 bit, por lo que, sumando otro entero a este valor máximo, obtenemos un valor demasiado elevado para ser representado dentro del espacio disponible para su memorización. En este caso se dice que hay un overflow.

Aunque pueda parecer increíble, muchísimos programas cometen errores de este tipo (quizás sin darse cuenta de ello), debido principalmente al hecho de que no tienen suficiente conocimiento del sistema hardware/software sobre el que trabajan.

Volvamos a lo nuestro, es decir, a Python. Hemos dicho que el tipo int permite presentar cualquier valor, por lo que, las sumas y las restas que en C++ han generado un overflow, con Python se ejecutan de manera correcta:


Llegados a este punto seguramente nos estaremos preguntando por qué deberíamos preocuparnos por el overflow, si este problema solo persigue a aquellos desafortunados que programan con otros lenguajes. Como podemos ver, esto no es exactamente así:


De hecho, podría ocurrir que una librería externa no utilizara el tipo int de Python para trabajar con los enteros; en el caso anterior, por ejemplo, hemos utilizado la famosa librería NumPy y hemos creado una matriz de enteros. Sin embargo, NumPy ha convertido implícitamente los elementos de la lista en objetos de tipo numpy.int64:


El tipo numpy.int64 codifica los enteros con 64 bit y este es el motivo por el cual estos enteros no pueden asumir un valor mayor que 9223372036854775807 y menor que -9223372036854775808. De hecho, si n es el número de bits que podemos utilizar para codificar un entero con signo, entonces el valor máximo representable lo da 2n-1 - 1, mientras que el mínimo lo da -2n-1.

NOTA

Veamos cómo se codifican los enteros con signo cuando se dispone de un número limitado de bits. Si, por ejemplo, utilizamos n = 3 bits para representar los enteros, entonces podemos tener solo las siguientes codificaciones: 000, 001, 010, 011, 100, 101, 110, 111. El primer bit se utiliza para indicar el signo del número: 0 indica que el número tiene un signo positivo, 1, negativo. Así, 000 representa el número decimal 0; 001, el número 1; 010, el número 2, y 011, el número 3, según la fórmula general que dice que el valor máximo representable lo da 2n-1 - 1. Los códigos que empiezan por 1 representan números negativos. La codificación más simple de los números negativos consiste en asignar al código 100 el valor decimal 0, al código 101, el valor -1, al código 110, el valor -2 y al código 011, el valor -3. Este tipo de codificación se denomina de módulo y signo, y se utiliza raramente. Es preferible utilizar una codificación denominada del complemento a dos, en la cual, cuando el número es negativo (el primer bit igual a 1), se invierten todos los bits (los 1 se convierten en 0 y viceversa), se les suma 1 y se calcula el valor del número sobre todos los n bits (incluido el primero). Como el número es negativo, este valor cambia de signo. Por ejemplo, el código 100 corresponde al número decimal -4. En realidad, si se invierte, se convierte en 011, sumado a 1 se convierte de nuevo en 100, por lo que su valor decimal es 4 y, en definitiva, con el cambio de signo se convierte en -4. El código 101 invertido se convierte en 010, y sumado a 1 se convierte en 011, al cual corresponde el número 3, es decir, -3. El código 110 se convierte en 001, sumado a 1 se convierte en 010, al cual corresponde el número 2, es decir -2. Por último, el código 111 se convierte en 000, sumado a 1 se convierte en 001, al cual corresponde el número 1, es decir -1. Respecto a la codificación de módulo y signo existe solo una representación del cero:

000 → 0 001 → 1 010 → 2 011 → 3 100 → -4 101 → -3 110 → -2 111 → -1

El valor mínimo representable lo da, por tanto, -2n-1. Esto también explica por qué en nuestros ejemplos el overflow de una unidad ha generado en un caso el opuesto positivo y en otro, el negativo. De hecho, sumando una wunidad al máximo positivo, obtenemos el extremo negativo: 011 + 001 = 100, es decir, -4. Y viceversa, restando una unidad al extremo negativo obtenemos el extremo positivo: 100-001 = 100 + 111 = 011, es decir, 3. Por tanto, resumiendo, cuando hay un overflow positivo de m, en lugar de obtener (max + m) se obtiene (min - m + 1), mientras que cuando hay un overflow negativo de m, en lugar de obtener (min - m) se obtiene (max + m - 1).

Moraleja: si trabajamos con Python utilizando librerías externas, debemos conocer el tipo con el cual dicha librería representa los enteros. Si no es int y los enteros están codificados con un número finito n de bits, debemos estar atentos, porque el conjunto de los números representables está limitado superiormente por 2n-1-1 e inferiormente por -2n-1, y por tanto, si se superan estos límites, habrá overflow.

NOTA

El módulo ctypes de la librería estándar nos permite utilizar tipos de datos compatibles con los correspondientes tipos nativos del C:


Para más información sobre el módulo ctypes: http://docs.python.org/3/library/ctypes.html.

Números de punto flotante

Como ya sabemos, los literales de punto flotante se distinguen de los enteros por la presencia del punto decimal. Existen otras representaciones, en las cuales el número se muestra en notación exponencial, con el exponente seguido de los caracteres e o E:


NOTA

Dado que una cifra seguida de un punto se utiliza para representar el literal de un número de punto flotante, no podemos acceder a un atributo de un número entero mediante su literal de manera clásica:


En este caso, estamos obligados a encerrar el literal entero entre paréntesis:


Los operadores de división se comportan de manera parecida a cuanto hemos visto para los números enteros. Recordemos que la división floor devuelve un float cuando como mínimo uno de sus operandos es del tipo float:


Conversiones

Iniciamos esta sección con una precisión: la operación de conversión de un punto flotante al entero inferior se indica con el término floor; la conversión de un punto flotante a un entero mediante la pérdida de sus cifras decimales se denomina truncamiento; la conversión de un punto flotante al entero más cercano se denomina redondeo.

Conversiones explícitas

La clase integrada float convierte un entero o una cadena en float:


La clase integrada int permite pasar de punto flotante a entero realizando un truncamiento:


La función integrada round() redondea un float al entero más cercano:


La función integrada round() acepta un segundo argumento que indica la precisión:


La conversión de un float al entero inferior puede realizarse con la función math.floor(). Aquí está el cuadro completo:



Conversiones implícitas

En las expresiones en que, además del operando de punto flotante, hay un operando entero, este último se convierte en float y el resultado también lo es:


Aritmética de los números de punto flotante

Un programador novel podría pensar que el tipo float nos permite trabajar con números naturales. Esto es solo una ilusión, porque un ordenador es capaz de representar solo un conjunto finito de números, denominados números máquina. A menudo no somos conscientes de este límite arquitectural, porque en ciertos casos los cálculos producen el mismo resultado que se obtiene con los números naturales:


En otros casos, sin embargo, puede haber una diferencia:


Por muy “pequeña” que sea, esta diferencia puede llevarnos a cometer grandes errores lógicos:


Quizás los resultados podrían ser incluso muy distintos a los esperados:



Para poder entender cuáles son los problemas a los que nos enfrentamos al ejecutar operaciones con los float, debemos situarnos lejos, es decir, en el concepto de punto flotante y en su implementación según el estándar IEEE 754. En las dos secciones siguientes (tituladas La notación de punto flotante e IEEE Standard for Binary Floating-Point Arithmetic) veremos la teoría. Pero no sufráis, puesto que volveremos rápidamente a la práctica a partir de la sección Valores máximo y mínimo representables.

Dado que en todos los lenguajes de programación más conocidos el tipo float se im plementa según este estándar, los conceptos que trataremos son válidos en general. La sección es muy concreta, por lo que, si ahora todos estos detalles no os interesan, podéis retomar los argumentos en otro momento. Sin embargo, debemos tener en cuenta que, si hacemos un pequeño esfuerzo por estudiar las siguientes páginas de teoría, podremos evitar que en nuestros programas (escritos en Python o en cualquier otro lenguaje) haya grandes errores lógicos.

La notación de punto flotante

En las secciones anteriores hemos visto que con Python es posible representar cualquier número entero, mientras que esto no es posible con los tipos nativos de otros importantes lenguajes de programación. Hemos dicho que la mayor flexibilidad de Python se debe al hecho de que usa un número variable de bytes para representar los enteros. En cambio, los float están codificados con un número fijo de bytes, de hecho se basan en los double (doble precisión) del lenguaje C, los cuales están implementados según el estándar IEEE 754 que, como veremos en breve, define convenciones para representar con una secuencia finita de bytes la notación de punto flotante de los números. Como podemos comprobar, el tamaño en bytes de los float es constante, es decir, no aumenta al aumentar el valor absoluto del número:


Pero vayamos por partes y veamos en qué consiste la notación de punto flotante. Un número real r puede estar escrito en la forma r = mbq, donde m ∈ R se denomina mantisa, b, base del sistema de numeración y q ∈ Z, exponente. Por ejemplo, según esta notación, el número r = 344.75 puede ser escrito en base 10 como r = 3.4475 · 102. En este caso, m = 3.4475, b = 10, q = 2.

Esta notación se denomina floating-point (punto flotante) y no es única, es decir, permite representar un número con distintos valores de m, b y q. De hecho, podemos escribir r = 0.34475 · 103 y también r = 34.475 · 101.

Veamos ahora algún ejemplo de representación de punto flotante en base 2, que nos interesa más, dado que es la que utilizan los ordenadores. Veamos en primer lugar cómo convertir un número con parte fraccionaria de representación decimal a binaria. Consideremos, por ejemplo, r = 100.11101. La primera cifra a la izquierda del punto tiene un índice 0; la segunda, un índice 1, y la tercera, un índice 2. La primera cifra a la derecha del punto tiene un índice -1; la segunda, -2, etc. La conversión de representación binaria a decimal se obtiene utilizando los índices como exponentes de la base:

100.11101 = 1 · 22 + 0 · 21 + 0 · 20 + 1 · 2-1 + 1 · 2-2 + 1 · 2-3 + 0 · 2-4 + 1 · 2-5 = 4.90625

La Figura 2.1 ilustra lo que acabamos de decir.


Figura 2.1 - Conversión del número r = 100.11101 de representación binaria a decimal.

Si utilizamos la notación de punto flotante en base 2, podemos escribir r = 10.011101 · 21, con m = 10.011101 y q = 1, o también r = 1.0011101 · 22, con m = 1.0011101 y q = 2.

La representación de r se considera normalizada cuando la mantisa, antes del punto decimal, tiene una única cifra y esta es distinta a cero. Esta cifra está comprendida entre 1 y 9 si la base es 10 y siempre es 1 si la base es 2. En este caso, las cifras de m que siguen al punto se denominan partes fraccionarias o cifras significativas y se indican con f. Por ejemplo, en base 10 la representación normalizada de r = 77.15 es r = 7.715 · 101, con m = 7.715, f = 0.715 y q = 1, mientras que la de r = 0.000431 es r = 4.31000 · 10-4. En base 2 la representación normalizada de r = 100.11101 es r = 1.0011101 · 22, con f = 0.0011101 y q = 2.

Una vez fijada la base b, todo número real r en representación de punto flotante normalizada es de manera unívoca determinado por la pareja (q, m). En la notación de punto flotante en base 2, además, dado que la primera cifra de m siempre es igual a 1, para representar a r unívocamente determinado basta con conocer la pareja (q, f) y el signo s (+ o -) del número (dado que este no está incluido en f): r ⇔ (s, q, f).

IEEE Standard for Binary Floating-Point Arithmetic

El estándar IEEE para el cálculo de punto flotante en base 2, indicado en IEEE 754, fue sustituido en 1985 por el IEEE (Institute of Electrical and Electronics Engineers). La última versión, publicada en 2008, extiende el estándar separándolo de la base de numeración.

Este estándar establece cómo representar, con un número finito de bytes, tanto los números de punto flotante normalizados como algunos casos especiales. En otras palabras, establece tanto el número de bits que se deben utilizar para representar la mantisa, el exponente y el signo, como el significado de las codificaciones.

NOTA

Para establecer el número de bits que se deben asignar a la parte fraccionaria y al exponente, ha sido preciso encontrar un acuerdo entre rangos de valores admisibles y precisión. De hecho, cuanto mayor es el número de bits que se asignan a la parte fraccionaria, mayor es la precisión, mientras que cuanto mayor es el número de bits que se asignan al exponente, más alto es el valor máximo representable.

Por ejemplo, en el caso de Python, el tipo float es mapeado sobre los double del lenguaje C, por lo que cada número de punto flotante es codificado con 8 bytes. En este caso, el estándar recomienda utilizar un bit para representar el signo (0 significa signo positivo, 1 significa signo negativo), 11 bits para el exponente y 52 bits para la parte fraccionaria, como se muestra en la Figura 2.2.


Figura 2.2 - Significado de los bits de un punto flotante en doble precisión según el estándar IEEE 754.

Para indicar el signo del exponente no ha sido asignado ningún bit porque a q se le ha restado un bias:


El bias se selecciona de manera que q - bias pueda asumir valores tanto negativos como positivos, y en el caso de la doble precisión es igual a 1023. Puesto que q se codifica con 11 bits, su valor va de 0 (once bits a cero) a 2047 (once bits a uno). Como veremos en breve, el estándar prevé casos especiales para estos dos valores, por lo que en realidad los valores de q que pueden ser utilizados para representar los puntos flotantes normalizados van de 1 a 2046. Esto significa que en la fórmula 2.1 el exponente que se desprende de la base del valor de q puede asumir solo los valores enteros que van del mínimo:


al máximo:


Pasemos a la práctica y veamos finalmente qué comporta todo esto.

Ücretsiz ön izlemeyi tamamladınız.

₺548,57

Türler ve etiketler

Yaş sınırı:
0+
Hacim:
1892 s. 2471 illüstrasyon
ISBN:
9788426729057
Yayıncı:
Telif hakkı:
Bookwire
İndirme biçimi:
Metin
Ortalama puan 0, 0 oylamaya göre
Ses
Ortalama puan 0, 0 oylamaya göre
Metin
Ortalama puan 0, 0 oylamaya göre
Metin
Ortalama puan 4,8, 5 oylamaya göre
Metin
Ortalama puan 0, 0 oylamaya göre
Metin
Ortalama puan 0, 0 oylamaya göre
Metin
Ortalama puan 0, 0 oylamaya göre