Todo lo que tu maestra no te dijo sobre los números en la clase de Ruby
Publicado por Aníbal Rojas 10 Abril 2006 en Ruby. english • españolEn Ruby todo es un objeto. Por eso es que es natual que cuando se le pasa el mensaje next a un objeto entero (Fixnum en este caso) la respuesta sea el próximo número entero, por ejemplo:
-
irb(main):001:0> 2.next
-
=> 3
Por supuesto la aritmética funciona tal y cual como nuestras maestras se esforzaron en enseñarnos:
-
irb(main):002:0> 2 + 2
-
=> 4
Sin embargo los enteros (Integers) son capaces de algunas cositas interesantes como:
-
irb(main):002:0> 5.times { |i| printf("2^%d=%d\n",i,2**i) }
-
2^0=1
-
2^1=2
-
2^2=4
-
2^3=8
-
2^4=16
-
=> 5
El método times básicamente ejecuta el bloque (la construcción entre llaves en este caso) el número de veces indicado por el número entero al que se le aplica. La variable i, que es local al bloque, toma los valores desde 0 hasta número - 1 para cada iteración donde se ejecuta el bloque de código, que en este caso sencillamente imprime las potencias de 2.
Ajá, muy bonitos los ejemplos, pero ¿Y cómo sabemos que es 1, 70, o 15000 son Fixnums? Sencillo, se lo preguntamos, para eso son objetos ¿No?:
-
irb(main):001:0> 1.class
-
=> Fixnum
-
irb(main):002:0> 70.class
-
=> Fixnum
-
irb(main):003:0> 15000.class
-
=> Fixnum
-
irb(main):004:0> 15_000.class
-
=> Fixnum
Con 1, 70 y 15000 no tenemos problemas... Pero ¿Es 15_000 un Fixnum también? Pues sí, sólo que Ruby nos permite agregar entre los digitos underscores en forma arbitraria para mejorar la legibilidad como si fueran puntos: Azúcar Sintáctico.
Por otra parte si el entero es lo suficientemente grande, eventualmene pasará esto:
-
irb(main):005:0> 9876543210.class
-
=> Bignum
Ruby no fué capaz de representar 9876543210 con un Fixnum y utilizó la clase Bignum. Sobre este punto elaboraremos más adelante.
Como ya vimos, las operaciones que comúnmente asociamos a los números están disponibes por supuesto. Pero en forma consistente con el manejo de los números en Ruby, estas operaciones están implementadas como métodos.
De esta forma podríamos redefinir el comportamiento del operador de suma "+" para siempre usar los valores absolutos de los operandos. Cosa que sin duda horrorizaría a nuestra maestras.
-
puts 2 + (-3) # => -1
-
-
class Fixnum
-
alias sumaAnterior +
-
def +(otro)
-
self.abs.sumaAnterior(otro.abs)
-
end
-
end
-
-
puts 2 + (-3) # => 5
Aquí sencilamente hemos creado un alias (copia, no referencia) al método que implementa la suma, para poder conservar ese comportamiento y recurrir a él posteriormente. Esto es posible hacerlo por que en Ruby los métodos son ¿Adivinen? Objetos, correcto.
Posteriormente cuando redefinimos la suma sencillamente calculamos el valor absoluto primero usando el método abs que Fixnum hereda de Integer, que a su vez lo hereda de Numeric. No me lo crean a mí, ustedes mismos pueden verificarlo:
-
irb(main):013:0> clase = 1.class
-
=> Fixnum
-
irb(main):014:0> clase = clase.superclass
-
=> Integer
-
irb(main):015:0> clase = clase.superclass
-
=> Numeric
-
irb(main):016:0> clase = Numeric.instance_methods.include?('abs')
-
=> true
Si bien el ejemplo anterior funciona, yo no les recomendaría dedicarse a ese tipo de manipulaciones ya que estarían efectivamente modificando el comportamiento de buena parte de los enteros para su aplicación de una forma más bien poco ortodoxa, por no decir demencial. Y seguramente serían castigados en el rincón, ya no digamos que por su maestra sino por su jefe.
Ahora, en lo que respecta a los número enteros sabemos que tenemos las clases Fixnum y los Bignum ¿Por qué tenemos dos clases distintas para representar los mismos comportamientos?
Bien, en cuanto a funcionalidad estas dos clases son básicamente iguales. Ruby elige en forma automática entre Fixnum y Bignum dependiendo del tamaño del literal a representar o resultado de la expresión evaluada. Los Fixnums permiten almacenar números enteros que pueden ser representados mediante el tamaño de una palabra nativa de la máquina, menos un bit.
-
irb(main):031:0> x = 123456
-
=> 123456
-
irb(main):032:0> x.class
-
=> Fixnum
-
irb(main):033:0> x = x * 10000
-
=> 1234560000
-
irb(main):034:0> x.class
-
=> Bignum
-
irb(main):035:0> x = x / 20000
-
=> 61728
-
irb(main):036:0> x.class
-
=> Fixnum
Lo bueno de todo esto, es que ha sido transparente para nosotros, ya que nunca necesitamos instanciar estos objetos en forma explícita. Y estos objetos exponen un comportamiento consistente para que nosotros podamos manipularlos sin problemas.
Fixnum sigue un patrón de Singleton. Esto se traduce en que nunca existirá más que un objeto Fixnum para cualquier entero.
-
irb(main):038:0> a = 2 - 1
-
=> 1
-
irb(main):039:0> a.object_id
-
=> 3
-
irb(main):040:0> b = 0 + 1
-
=> 1
-
irb(main):041:0> b.object_id
-
=> 3
En el ejemplo anterior pudimos constatar que que dos expresiones distintas, que dieron el mismo número entero como resultado, efectivamente "referencian" al mismo objeto 1 cuyo identificador único (object_id) es el mismo (3).
Al ser Singletons se gana en rendimiento ya que la instanciación de un nuevo objeto es mucho más costosa que "referenciar" a uno ya existente. El entrecomillado es a propósito, ya vamos a ver el motivo.
Si chequean la clase Fixnum en el Pickaxe, van a encontrar la siguiente oración "Los objetos Fixnum tienen un valor inmediato", mi traducción es libre ¿Inmediato? ¿Qué es eso de inmediato? (Este es el tipo de pregunta que nadie en el salón se atrevía a hacer, y donde todo el mundo ponía cara de inteligencia ;-)
Bueno, después de escarbar un poquito en comp.lang.ruby entendí. Los objetos Fixnum no existen más allá de la referencia a ellos, es decir su valor no reside en una ubicación de memoria que es referenciada por una variable o constante, sino que está almacenado en la propia referencia. Obviamente esto es una consideración realizada para mejorar el rendimiento en el manejo de estos números.
Una cosa que pareciera ser cierta y que no lo es (al menos como yo lo entiendo) es que los Fixnums son inmutables. Ya que es posible agregarle variables de instancia en el estilo de:
-
class Fixnum
-
attr_accessor :letras
-
end
-
-
1.letras = "uno"
-
10.letras = "diez"
-
-
puts 1.letras # uno
-
a = 1
-
puts a.letras # uno
-
puts (2-1).letras # uno
-
puts 10.letras # diez
Sin embargo el intérprete no permitirá agregarle singleton methods a un Fixnum, supongo que por no penalizar la resolución de los métodos. La discusión sobre este tópico es un tanto bizarra, y suele estar aderezada con frases al estilo de "Use The Source Luke, use The Source", cosa que en general ni nos va a interesar ni es realmente necesario, ya que a efectos prácticos seguimos entre cómodos objetos.
Cuando el intérprete no puede representar una valor entero con un Fixnum, entonces apela a los Bignums, siempre en forma transparente para nosotros los programadores. Y estos objetos ni nos singletons, ni son inmediatos.
-
irb(main):048:0> a = 9876543210
-
=> 9876543210
-
irb(main):049:0> b = 9876543210
-
=> 9876543210
-
irb(main):050:0> a.object_id
-
=> 24579420
-
irb(main):051:0> b.object_id
-
=> 24567756
Por supuesto podemos resolver a los número reales con la representación de puntos flotante de doble precisión, basada en el tamaño de la palabra de la arquitectura nativa, con los objetos de la clase Float. A pesar del suspiro de culebra anterior, en realidad a estas alturas eso tiene poco de sorprendente.
-
irb(main):010:0> ( 1.0 + 2.3 ).class
-
=> Float
-
irb(main):014:0> 33e-1
-
=> 3.3
-
irb(main):018:0> 1.e3
-
NoMethodError: undefined method `e3' for 1:Fixnum
-
from (irb):18
-
irb(main):019:0> 1.0e3
-
=> 1000.0
El error undefined method se genera porque se ha omitido un dígito al a derecha del punto, y Ruby intenta pasar el mensaje e3 al Fixnum 1, cosa que obviamente la clase no sabe como resolver.
En el caso de tener que manejar números decimales realmente grandes, tendremos que optar por usar la librería estándar BigDecimal, que además de las operaciones convencionales incluye algunas librerías matemáticas adicionales.
Ahora, nuestras abnegadas maestras se empeñaron en enseñarnos eso de sumar peras con manzanas no es una cosa buena. Así que de alguna forma hay que ver como hacemos para sumar números enteros y reales, con el fin de que nos nos castiguen después de clases, menos mal que los palmetazos ya no se usan.
Ruby tiene el concepto de protocolos de conversión, mediante el cual un objeto puede ser sujeto a una conversión a otra clase diferente de la propia.
-
irb(main):007:0> "2" + 2
-
TypeError: cannot convert Fixnum into String
-
from (irb):7:in `+'
-
from (irb):7
-
irb(main):008:0> "2".to_i + 2
-
=> 4
-
irb(main):009:0> "2".to_int + 2
-
NoMethodError: undefined method `to_int' for "2":String
-
from (irb):9
En el ejemplo anterior llevamos las cosas un poco lejos, brincando fuera del mundo de los números. Sin embargo estos ejemplos son interesantes, ya que a diferencia de otros populares lenguajes interpretados, Ruby no convertirá en forma implícita un String al número equivalente a la representación entre comillas.
Por otra parte si utilizamos una conversión aproximada ("loose") invocando el método to_i sobre el String, obtenemos el resultado deseado. Sin embargo si intentamos una conversión estricta, que se obtiene mediante el método to_int, al intérprete no le gusta.
La diferencia es sutil, pero puede ser importante ya que la conversión aproximada se toma licencias como:
-
irb(main):016:0> "tu maestra te puso un".to_i
-
=> 0
Claro, el caso que más nos interesa es al final es:
-
irb(main):002:0> a = 2.5 + 5
-
=> 7.5
-
irb(main):003:0> a.class
-
=> Float
-
irb(main):004:0> a = a + 2.5
-
=> 10.0
-
irb(main):005:0> a.class
-
=> Float
Donde se han aplicado las reglas de coerción numérica, para llevar a ambos números a una clase común donde la operación a ejecutar tenga sentido. La coerción numérica se basa en el método coerce de la clase Numeric.
Este método retorna un arreglo de dos elementos, donde ambos elementos del arreglo tienen la misma clase y sus valores son equivalentes a los valores suministrados. Cada vez que se ejecuta un operación sobre un número y el parámetro es de un clase distinta del receptor, se realiza la coersión.
En el Pickaxe van a encontrar este ejemplo muy bonito para proveer una conversión de la clase String a los descendientes de Numeric, ejemplo que me permito reproducir a continuación:
-
class String
-
def coerce(other)
-
case other
-
when Integer
-
begin
-
return other, Integer(self)
-
rescue
-
return Float(other), Float(self)
-
end
-
when Float
-
return other, Float(self)
-
else
-
super
-
end
-
end
-
end
-
-
puts 5 + "3" # => 8
-
puts 5 - "2.5" # => 7.5
-
puts 1.2 + "2.1" # => 3.3
-
puts 1.5 + "2" # => 2.5
Fíjense que si el parámetro es un entero se intenta llevar al receptor (self) a un entero, si esta operación falla con un ArgumentError: invalid value for Integer entonces se intenta con un Float para ambos.
Algo que amerita una aclaratoria son los métodos Integer, y Float que son métodos del módulo Kernel. Estos parecen clásicos constructores Java y violan la convención Ruby para los nombres métodos al tener capitalizada la primera letra. En palabras de Matz "Methods with class name constant e.g. Integer, String, etc. are converters in convention"
Bueno, para finalizar si hacen un require mathn, obtienen algo así como "Pimp My Numbers" ;-) y pueden hacer cosas como:
-
irb(main):012:0> require 'mathn'
-
=> true
-
irb(main):023:0> 1/2 + 1/2
-
=> 1
-
irb(main):024:0> 2/6 * 2
-
=> 2/3
-
irb(main):025:0> Math.sqrt(-1) + 1
-
=> Complex(1, 1)
No le enseñen a sus hijos a usar esta librería, o empezarán a ver que resuelven las tareas de matemáticas muy rápido.
Si llegaron hasta este punto, estoy seguro de que su maestra de primaría se sentiría orgullosos de ustedes y obtendrían una una calcomanía con estrella, un rubí o algo por el estilo. Espero que les haya gustado, y que se animen a investigar un poco sobre este facinante lenguaje de programación orientado a objetos que es Ruby.
serias tan amable dde dar una clase completa sobre numeros enteros
que son los numeros naturales
ME PARECE UNA FORMA DE HACER MAS EXPLICITO EL CONOCIMINTO DE LAS MATEMATICAS.GRACIAS POR TODO ESTE TRABAJO TAN INTERESANTE. GRACIAS MUCHAS GRACIAS.
Me parece muy interesante....pero como utilizo la libreria BigDecimal...para trabajar con numeros muy pequeños y que considere todos los decimales....???
Gracias
uatbzkx
wshnq
pamdzlk
nleu fylpsr hpoejdf mnfh
yszdmao oqhl duiwg bmlzh
dsunh
zfgjh alseon bungc paleh
opyq txwd zmup
msloufk chyd cpvrt htoyr
rclvom utbrm
hwdya plvkm
ryahx plmeor lkob mngpirc
qylpeif ohtpifs
nwvpf hmjfao scbv
wlmbnoc
ofmlh sdgxci uarn
cpofyw idqnu
blfk
wiznkub mlbd
cfewn kvnubm pmwbqy
cwloby iqng wejczbu
zajglv oamxi ueridk vbmw
frbdmwq zpdq
jhioexb
rxui
lkhvc uwjxn wiyxq nyqte
wcrhatn
ebnf
zmrgv gnfyai
hotq avko
nrjmx valz fadszu sizcury
jsfbrzo gustvfh lutj dfzxej
daes
ybum dprzk
ofbr hposa rgixmap
dves rqgcafi bpvidcu gfeaquz
jrpxmcy uwpnymt kbwjt
tlihv ouldabp
wpicxal gxabc nsel
idbkzj rqlck
mgxdce jlcdhpf
epkcg yhkdzl
rxoylu
psbizn nsmef
nwzckh zvxqjy hfkb jxzueg
wjfn
zspjn mbnjfkl
zihn efgp
vrde gjfw
usmzk
tenkmr fgijvw hlgx fuwr
zhsam pigkzoy ictv fsnwm
kuazdh ckshwd
sambiup xjghkld dlrek