Perfiles en Apache Maven
Apache Maven está diseñado para crear compilaciones portátiles que se espera que funcionen en diferentes plataformas y en varios entornos de tiempo de ejecución. Puede…
En este artículo vamos a aprender sobre el manejo de números en Scala. En Scala, los tipos Byte, Short, Int, Long y Char se conocen como tipos integrales porque están representados por enteros o números enteros. Los tipos integrales junto con Double y Float comprenden los tipos numéricos de Scala. Estos tipos numéricos amplían el trait AnyVal, al igual que los tipos Boolean y Unit.
La relación de los tipos de valores predefinidos con AnyVal y Any (así como Nothing) se muestra en la siguiente figura.

Como se muestra en la imagen:
AnyVal.AnyRef.La siguiente tabla resume los tipos numéricos de Scala, su tamaño en bits y el rango de valores que pueden almacenar:
| Tipo | Tamaño (bits) | Rango |
|---|---|---|
Byte |
8 | -128 a 127 |
Short |
16 | -32,768 a 32,767 |
Int |
32 | -2,147,483,648 a 2,147,483,647 |
Long |
64 | -9,223,372,036,854,775,808 a 9,223,372,036,854,775,807 |
Float |
32 | ±3.4028235E38 (precisión ~7 dígitos) |
Double |
64 | ±1.7976931348623157E308 (precisión ~16 dígitos) |
Si alguna vez necesita saber los valores exactos de los rangos de datos puede encontrarlos en Scala REPL de la siguiente forma:
scala> Short.MinValue
val res0: Short = -32768
scala> Short.MaxValue
val res1: Short = 32767
scala> Int.MinValue
val res2: Int = -2147483648
scala> Int.MaxValue
val res3: Int = 2147483647
scala> Long.MinValue
val res4: Long = -9223372036854775808
scala> Long.MaxValue
val res5: Long = 9223372036854775807
scala> Float.MinValue
val res6: Float = -3.4028235E38
scala> Float.MaxValue
val res7: Float = 3.4028235E38
scala> Double.MinValue
val res8: Double = -1.7976931348623157E308
scala> Double.MaxValue
val res9: Double = 1.7976931348623157E308
Scala 2.13 introdujo la capacidad de usar guiones bajos en valores literales numéricos, veamos algunos ejemplos:
val x = 1_000_000 // Int: 1000000
val y = 1_000_000L // Long: 1000000
val z = 1_000_000.00 // Double: 1000000.0
val a = 1_000_000.00f // Float: 1000000.0
val hex = 0xFF_FF_FF // Int en hexadecimal: 16777215
val bin = 0b1000_0000 // Int en binario: 128
Esta característica mejora enormemente la legibilidad del código cuando trabajamos con cantidades grandes, ya que permite separar visualmente los miles, millones, etc., sin afectar el valor numérico real.
Puede que en muchas ocasiones deseemos convertir un String a uno de los tipos numéricos de Scala. Para poder realizar esto debemos usar los métodos to* que están disponibles en un String. Veamos cómo realizarlo:
scala> "1".toInt
val res0: Int = 1
scala> "1".toByte
val res1: Byte = 1
scala> "1".toShort
val res2: Short = 1
scala> "1".toLong
val res3: Long = 1
scala> "1.5".toFloat
val res4: Float = 1.5
scala> "1.5".toDouble
val res5: Double = 1.5
Debemos tener cuidado porque estos métodos pueden generar una excepción del tipo NumberFormatException. Veamos un ejemplo:
scala> "hello".toInt
java.lang.NumberFormatException: For input string: "hello"
scala> "1.5".toInt
java.lang.NumberFormatException: For input string: "1.5"
Es posible que se prefiera utilizar los métodos to*Option, que devuelven Some cuando la conversión es exitosa y None cuando la conversión falla:
scala> "1".toIntOption
val res0: Option[Int] = Some(1)
scala> "hello".toIntOption
val res1: Option[Int] = None
scala> "1.5".toDoubleOption
val res2: Option[Double] = Some(1.5)
scala> "abc".toDoubleOption
val res3: Option[Double] = None
El uso de los métodos to*Option es una práctica recomendada en Scala, ya que nos permite manejar las conversiones fallidas de forma segura sin necesidad de capturar excepciones con try/catch. Esto es especialmente útil cuando procesamos datos de entrada del usuario o datos provenientes de fuentes externas donde no podemos garantizar el formato.
A menudo cuando trabajemos con el manejo de números en Scala tendremos que convertir de un tipo numérico a otro, como de Int a Double, Double a Int, o posiblemente una conversión que involucre BigInt o BigDecimal.
Los valores numéricos normalmente se convierten de un tipo a otro con una colección de métodos to*, incluidos toByte, toChar, toDouble, toFloat, toInt, toLong y toShort.
Los valores numéricos se convierten fácilmente en la dirección de menor a mayor precisión:
Byte → Short → Int → Long → Float → Double
Es importante tener en cuenta que las conversiones en la dirección contraria (de mayor a menor precisión) pueden resultar en pérdida de datos. Por ejemplo, convertir un Double a Int trunca la parte decimal, y convertir un Long grande a Int puede producir un valor incorrecto por desbordamiento.
Veamos algunos ejemplos:
scala> val i = 42
val i: Int = 42
scala> i.toDouble
val res0: Double = 42.0
scala> i.toLong
val res1: Long = 42
scala> i.toFloat
val res2: Float = 42.0
scala> val d = 3.14159
val d: Double = 3.14159
scala> d.toInt
val res3: Int = 3
scala> d.toLong
val res4: Long = 3
scala> d.toFloat
val res5: Float = 3.14159
asInstanceOfDependiendo de las necesidades, podemos hacer un cast usando asInstanceOf:
scala> val i = 42
val i: Int = 42
scala> i.asInstanceOf[Double]
val res0: Double = 42.0
scala> i.asInstanceOf[Long]
val res1: Long = 42
scala> i.asInstanceOf[Byte]
val res2: Byte = 42
Nota: En general, se recomienda usar los métodos
to*en lugar deasInstanceOfpara conversiones numéricas, ya queasInstanceOfes un cast de bajo nivel que puede provocar errores en tiempo de ejecución si se usa incorrectamente, mientras que los métodosto*son más seguros y expresivos.
Cuando se utiliza un estilo de declaración de tipo implícito, Scala asigna automáticamente los tipos de datos en función de sus valores numéricos. Puede suceder que deseemos anular el tipo predeterminado cuando creamos un campo numérico. Por ejemplo, si asigna 1 a una variable sin declarar explícitamente su tipo, Scala le asigna el tipo Int:
scala> val x = 1
val x: Int = 1
scala> val y = 1.5
val y: Double = 1.5
Por lo tanto, cuando necesite controlar el tipo, declárelo explícitamente de la siguiente forma:
val b: Byte = 1
val s: Short = 1
val l: Long = 1
val f: Float = 1.0f
val d: Double = 1.0
Para longs, doubles y floats también puedes usar este estilo:
val l = 1L // Long
val d = 1.0D // Double (explícito, aunque 1.0 ya es Double por defecto)
val f = 1.0F // Float
Esta segunda forma es más concisa y es especialmente útil cuando queremos dejar claro el tipo sin recurrir a una anotación de tipo completa. Es habitual ver este estilo en código Scala que opera con literales numéricos de distintos tipos.
El manejo de números en Scala puede ser algo complicado si está escribiendo una aplicación y necesita usar valores enteros o decimales muy grandes.
Si estamos en esta situación y los tipos Long y Double no son lo suficientemente grandes, podemos usar las clases de Scala BigInt y BigDecimal. Veamos a continuación un ejemplo:
scala> val b = BigInt(1234567890123456789L)
val b: scala.math.BigInt = 1234567890123456789
scala> val bd = BigDecimal(123456.789)
val bd: scala.math.BigDecimal = 123456.789
scala> val big = BigInt("99999999999999999999999999999")
val big: scala.math.BigInt = 99999999999999999999999999999
BigInt y BigDecimal admiten todos los operadores que utilizamos con tipos numéricos en Scala:
scala> val a = BigInt(100)
val a: scala.math.BigInt = 100
scala> val b = BigInt(200)
val b: scala.math.BigInt = 200
scala> a + b
val res0: scala.math.BigInt = 300
scala> a * b
val res1: scala.math.BigInt = 20000
scala> a - b
val res2: scala.math.BigInt = -100
scala> b / a
val res3: scala.math.BigInt = 2
scala> b % a
val res4: scala.math.BigInt = 0
scala> b > a
val res5: Boolean = true
Además, podemos convertirlos a otros tipos numéricos:
scala> val b = BigInt(1234)
val b: scala.math.BigInt = 1234
scala> b.toInt
val res0: Int = 1234
scala> b.toLong
val res1: Long = 1234
scala> b.toDouble
val res2: Double = 1234.0
scala> b.toFloat
val res3: Float = 1234.0
scala> val bd = BigDecimal(3.14159)
val bd: scala.math.BigDecimal = 3.14159
scala> bd.toInt
val res4: Int = 3
scala> bd.toDouble
val res5: Double = 3.14159
Es importante recordar que al convertir un BigInt o BigDecimal a un tipo más pequeño (como Int o Long), podemos sufrir pérdida de datos si el valor excede el rango del tipo destino. En estos casos, Scala no lanza una excepción sino que trunca el valor silenciosamente, lo cual puede llevar a errores difíciles de detectar.
En algunas situaciones puede darse el caso de que necesitemos crear números aleatorios, como cuando probamos una aplicación, realizamos una simulación o cualquier otra situación.
En Scala podemos crear números aleatorios con la clase scala.util.Random. Los siguientes ejemplos muestran casos de uso comunes de generación de números aleatorios:
scala> import scala.util.Random
// Número entero aleatorio
scala> val r = Random.nextInt()
val r: Int = -1453728456
// Número entero aleatorio entre 0 (inclusive) y 100 (exclusive)
scala> val r = Random.nextInt(100)
val r: Int = 47
// Número Double aleatorio entre 0.0 y 1.0
scala> val r = Random.nextDouble()
val r: Double = 0.7254382937
// Número Float aleatorio entre 0.0 y 1.0
scala> val r = Random.nextFloat()
val r: Float = 0.31458
// Booleano aleatorio (true o false)
scala> val r = Random.nextBoolean()
val r: Boolean = true
// Número Long aleatorio
scala> val r = Random.nextLong()
val r: Long = 4523876918274659812
Si necesitamos resultados reproducibles (por ejemplo, para pruebas unitarias), podemos crear una instancia de Random con una semilla fija:
scala> val rng = new Random(42)
val rng: scala.util.Random = scala.util.Random@5a2e4553
scala> rng.nextInt(100)
val res0: Int = 0
scala> rng.nextInt(100)
val res1: Int = 68
Al usar la misma semilla, la secuencia de números generados siempre será la misma, lo que resulta muy útil para escribir tests determinísticos.
En este artículo hemos explorado las principales operaciones de manejo de números en Scala, desde los tipos numéricos básicos (Byte, Short, Int, Long, Float, Double) hasta las clases para números de precisión arbitraria (BigInt, BigDecimal). Aprendimos cómo consultar los rangos de cada tipo, cómo convertir entre String y tipos numéricos de forma segura con los métodos to*Option, cómo realizar casting entre tipos, y cómo generar números aleatorios con scala.util.Random.
Dominar el manejo de números es fundamental para cualquier desarrollador Scala, ya que estas operaciones aparecen constantemente en el desarrollo de aplicaciones, desde el procesamiento de datos con Apache Spark hasta la implementación de algoritmos y lógica de negocio.