3.4 Kernel-Numbers

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

 CuisLogo 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

 CuisLogo ¿Cómo podemos multilicar el entero 360 por 1024, sin utilizar la operación de multiplicación?

Ejercicio 3.3: Multiplicar por 1024

Interrupción con números decimales

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.

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

 CuisLogo 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/25/2. Cuis-Smalltalk devuelve un número racional, no calcula un decimal.

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

 CuisLogo 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

Notas al pie

(15)

Más estrictamente, repetir un número entero de veces.

(16)

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.