La clase jerárquica superior Number muestra la mayoría de los
comportamientos heredados por las subclases como Float,
Integer y Fraction. La forma de Smalltalk de
aprender sobre un comportamiento es apuntar el navegador del sistema
hacia una clase jerárquica superior y explorar las categorías de
métodos.
Supongamos que queremos redondear un número flotante. En Number,
exploramos la categoría Truncation and round off method
(Métodos de truncamiento y redondeo) para descubrir varios
comportamientos. Lo siguiente que debemos hacer es probar estos mensajes
en un espacio de trabajo para descubrir el que estamos buscando:
1.264 roundTo: 0.1 ⇒ 1.3 1.264 roundTo: 0.01 ⇒ 1.26 1.264 roundUpTo: 0.01 ⇒ 1.27 1.264 roundTo: 0.001 ⇒ 1.264
Ejemplo 3.3: Redondeando números, pruebas en el Workspace
Number es un lugar muy extraño para buscar un bucle indexado en un
intervalo determinado. Sin embargo, un intervalo se define por los
números inicial y final. En la clase Number, la categoría de
métodos intervals (intervalos) revela comportamientos
relacionados. Estos métodos funcionan de forma polimórfica con la
mayoría de los tipos de números:
1 to: 10 do: [:i | Transcript show: 1 / i; space] ⇒ 1 (1/2) (1/3) (1/4) (1/5) (1/6) (1/7) (1/8) (1/9) (1/10) 1 to: 10 by: 2 do: [:i | Transcript show: 1 / i; space] ⇒ 1 (1/3) (1/5) (1/7) (1/9) 1/10 to: 5/3 by: 1/2 do: [:i | Transcript show: i; space] ⇒ (1/10) (3/5) (11/10) (8/5) (1/10) (3/5) (11/10) (8/5) Float pi to: 5 by: 1/3 do: [:i | Transcript show: i; space] ⇒ 3.141592653589793 3.4749259869231266 3.80825932025646 4.141592653589793 4.474925986923126 4.808259320256459
Ejemplo 3.4: Bucles de intervalo (for-loop)
Ahora, en la clase Integer, busca la categoría de métodos
enumerating, para encontrar timesRepeat:. Cuando un
trozo de código necesita ser ejecutado varias veces15, sin necesidad de
un índice, se envía el mensaje #timesRepeat: a un entero. Ya
hemos visto esta variante en una sección anterior de este
capítulo. Lanzar un dado de 6 caras 5 veces se puede simular con un
número entero:
5 timesRepeat: [Transcript show: 6 atRandom; space] ⇒ 1 2 4 6 2
Ejemplo 3.5: Lanzar un dado 5 veces
Note: Espera un resultado distinto cada vez.
Se pueden definir intervalos de números por separado, para utilizarlos en el futuro:
1 to: 10 ⇒ (1 to: 10) 1 to: 10 by: 2 ⇒ (1 to: 9 by: 2)
Ejemplo 3.6: Intervalos
Los intervalos también funcionan con otro tipo de objetos como
Character:
$d to: $h ⇒ #($d $e $f $g $h)
De hecho, un intervalo es un objeto por sí mismo. Es una especie de colección:
(1 to: 10) class ⇒ Interval (1 to: 10 by: 2) squared ⇒ #(1 9 25 49 81) (1 to: 10) atRandom ⇒ 4 "different result each time"
En Spacewar!, cuando una nave se destruye se la teletransporta a una
dirección al azar en el cuadrado del área de juego. Los intervalos son
útiles para seleccionar coordenadas aleatorias. En el ejemplo de abajo,
la variable randomCoordinate contiene un bloque de código
–llamado función anónima en otros lenguajes–. Selecciona un valor
aleatorio en un intervalo consistente en los extremos izquierdo y
derecho del área de juego:
randomCoordinate := [(area left to: area right) atRandom]. aShip velocity: 0 @ 0; morphPosition: randomCoordinate value @ randomCoordinate value
Ejemplo 3.7: Teletransportar una nave
Calcular los valores de los cosenos en el intervalo [0 ; 2PI], cada 1/10. Mostrando el resultado en el Transcript.
Ejercicio 3.2: Tabla de cosenos
Los números enteros se representan en diferentes bases cuando se les
antepone la base y letra «r». La r significa raíz, la base
raíz con la que se interpreta el número siguente. Cuando ejecutamos e
imprimimos con Ctrl-p en cualquier número de este tipo, se
muestra inmediatamente en la base decimal:
2r1111 ⇒ 15 16rF ⇒ 15 8r17 ⇒ 15 20rF ⇒ 15 10r15 ⇒ 15
Ejemplo 3.8: Entero representado en diversas bases
Escribiendo números como Mayas o Babilonios16:
"The Babylonians" 60r10 ⇒ 60 60r30 ⇒ 180 60r60 ⇒ 360 60r30 + 60r60 ⇒ 540 (60r30 + 60r60) printStringRadix: 60 ⇒ '60r90' "The Mayans" 20r10 ⇒ 20 20r40 ⇒ 80 "pronounced 4-twenties in some languages" 20r100 ⇒ 400
Ejemplo 3.9: Contando como los antiguos
Debido a la naturaleza de un número representado en base 2, desplazar sus bits hacia la izquierda y hacia la derecha equivale a multiplicar por 2 y dividir por 2:
(2r1111 << 1) printStringBase: 2 ⇒ '11110' 2r1111 << 1 ⇒ 30 (2r1111 >> 1) printStringBase: 2 ⇒ '111' 2r1111 >> 1 ⇒ 7
Ejemplo 3.10: Desplazando bits
¿Cómo podemos multilicar el entero 360 por 1024, sin utilizar la operación de multiplicación?
Ejercicio 3.3: Multiplicar por 1024
Hemos visto que los números decimales se escriben con un punto «.» que
separa la parte entera de la decimal: 1.5,
1235.021 o 0.5. El número 0.0000241
se escribe más fácilmente con la notación científica
2.41e-5; que significa 2 precedido por 5 ceros o 2 como el
quinto dígitos después del punto decimal.
Los ordenadores codifican y almacenan los números decimales de manera imprecisa. Debes prevenirte cuando hagas cálculos y comparaciones de igualdad. Muchos sistemas ocultas estos errores porque son muy pequeños. Cuis-Smalltalk no oculta esta imprecisión. Hay mejor información sobre esto en el comentario de la clase
Float.
0.1 + 0.2 - 0.3 ⇒ 5.551115123125783e-17
Ejemplo 3.11: ¡Discalculia del ordenador!
En el Ejemplo 3.11, el valor devuelto debería ser cero, pero no
es el caso. El ordenador devuelve 5.55e-17, o
0.0000000000000000555, que es muy cercano a cero, pero que
es un error.
Realiza 3 cálculos mostrando errores comparando con los valores esperados.
Ejercicio 3.4: Errores de cálculo con números decimales
Cuando la precisión esté obligada en Cuis-Smalltalk se utiliza la representación como números racionales.
Un número racionas se escribe con el símbolo de división entre dos
enteros: haz Ctrl-p en 5/2 ⇒
5/2. Cuis-Smalltalk devuelve un número racional, no calcula un
decimal.
¿Qué ocurre cuando ejecutamos este código
5/0?
Ejercicio 3.5: Hacia el infinito
Regresemos a la discalculia de nuestro ordenador con los números decimales. Cuando utilizamos números racionales, el Ejemplo 3.11 se convierte en:
(1/10) + (2/10) - (3/10) ⇒ 0
Ejemplo 3.12: ¡El cálculo es correcto utilizando fracciones!
Esta vez obtenemos el resultado esperado. Debajo de la cubierta, el ordenador sólo realiza cálculos con componentes enteros por lo que no hay resultados redondeados. Esto es un buen ejemplo de que resolver algunos problemas necesita cambiar el paradigma.
Regresa a Ejercicio 3.4 y utilizar números racionales para representar los números decimales. Los errores deberían estar resueltos.
Ejercicio 3.6: Arreglando errores
Cuis-Smalltalk sabe cómo convertir un número decimal a una fracción, enviando
el mensaje #asFraction. Ya hemos señalado la discalculia del
ordenador con los números decimales, esto es por lo que podemos obtener
resultados extraños cuando pedimos una fracción. La representación
interna de 1.3 no es exactamente como parece:
(13/10) asFloat ⇒ 1.3 (13/10) asFloat asFraction ⇒ 5854679515581645/45035996273704
Más estrictamente, repetir un número entero de veces.
La representación de números en base 20 o 60 no es exclusiva de estas civilizaciones, aunque son los casos de uso más documentados.