Pílula K#09 — Tipos Numéricos

Klaus Dellano
HavanLabs
Published in
5 min readApr 25, 2024

No universo da programação, os números são mais do que meros dígitos; eles formam a base de quase todas as operações computacionais. Entender como os números funcionam em Kotlin, pode melhorar significativamente a qualidade do código e a eficiência da execução. Na pílula de hoje, exploraremos detalhadamente os tipos numéricos em Kotlin, suas peculiaridades, e como utilizá-los eficazmente em diferentes cenários.

1. Tipos de Números e Suas Inferências

Kotlin é inteligente o suficiente para inferir tipos de dados, simplificando o trabalho dos programadores. Por exemplo, ao atribuir um número inteiro a uma variável sem especificar seu tipo, Kotlin automaticamente assume que é um Int:

fun main() {
val milhao = 1_000_000
println(milhao)
}

/* Saída:
* 1000000
*/

Essa inferência automática ajuda a manter o código limpo e legível. Além disso, Kotlin suporta a adição de underscores em números grandes para melhorar a legibilidade, uma pequena, mas valiosa, característica de design da linguagem.

2. Operações Matemáticas Básicas e Precedência de Operadores

Operações matemáticas são fundamentais em qualquer linguagem de programação, e Kotlin oferece todos os operadores básicos como adição (+), subtração (-), multiplicação (*), divisão (/) e módulo (%). A precedência desses operadores segue regras universais, onde multiplicação e divisão são priorizadas em relação à adição e subtração:

fun main() {
println(45 + 5 * 6)
println((45 + 5) * 6)
}

/* Saída
* 75
* 300
*/

3. Divisão de Números: Inteiros vs. Flutuantes

Ao dividir números em Kotlin, é crucial entender a diferença entre a divisão inteira e a divisão flutuante. A divisão inteira descarta o resto, enquanto a divisão flutuante leva em consideração o valor total dos operandos:

fun main() {
val numerador: Int = 19
val denominador: Int = 10
println(numerador / denominador)
}

/* Saída:
* 1
*/

Uso Prático

1. Calculando o Índice de Massa Corporal (IMC)

Usar tipos numéricos para resolver problemas do mundo real, como o cálculo do IMC. O Kotlin permite uma manipulação precisa dos dados ao utilizar o tipo Double para esse tipo de cálculo:

fun metricaIMC(
peso: Double,
altura: Double
): String {
val imc = peso / (altura * altura) // [**]
return if (imc < 18.5) "Abaixo do peso"
else if (imc < 25) "Peso normal"
else "Sobrepeso"
}

fun main() {
val peso = 72.57
val altura = 1.727
val status = metricaIMC(peso, altura)
println(status)
}

/* Saída:
* Peso normal
*/

[**] Se você remover os parênteses, você dividirá o peso pela altura e depois multiplicará esse resultado pela altura. Esse é um número muito maior, e a resposta errada.

metricaIMC() usa Double para o peso e a altura.

Um Double armazena números flutuantes muito grandes e muito pequenos.

Aqui está uma versão usando unidades inglesas, representadas por parâmetros Int:

fun metricaInglesaIMC(
peso: Int,
altura: Int
): String {
val imc = peso / (altura * altura) * 703.07 // [**]
return if (imc < 18.5) "Abaixo do peso"
else if (imc < 25) "Peso normal"
else "Sobrepeso"
}

fun main() {
val peso = 160 // em libras
val altura = 68 // em polegadas
val status = metricaInglesaIMC(peso, altura)
println(status)
}

/* Saída:
* Abaixo do peso
*/

Por que o resultado difere de metricaIMC(), que usa Doubles? Quando você divide um inteiro por outro inteiro, Kotlin produz um resultado inteiro.

A maneira padrão de lidar com o resto durante a divisão inteira é a truncagem, ou seja, "corte e jogue fora" (não há arredondamento).

Então, se você dividir 5 por 2, obterá 2, e 7/10 é zero.

Quando Kotlin calcula imc na expressão [**], ele divide 160 por 68 * 68 e obtém zero. Depois, multiplica zero por 703.07 para obter zero.

Para evitar esse problema, mova 703.07 para o início do cálculo. Os cálculos são então forçados a serem Double: val imc = 703.07 * peso / (altura * altura)

Os parâmetros Double em metricaIMC() previnem esse problema. Converta os cálculos para o tipo desejado o mais cedo possível para preservar a precisão.

2. Lidando com Limites…

Todas as linguagens de programação têm limites para o que podem armazenar dentro de um inteiro. O tipo Int do Kotlin pode assumir valores entre -²³¹ e +²³¹-1, uma restrição da representação Int de 32 bits. Se você somar ou multiplicar dois Ints grandes o suficiente, você estourará o resultado:

fun main() {
val i: Int = Int.MAX_VALUE
println(i + i)
}

/* Saída:
* -2
*/

Int.MAX_VALUE é um valor predefinido que é o maior número que um Int pode armazenar. O estouro produz um resultado claramente incorreto, pois é negativo e muito menor do que esperamos.

Kotlin emite um aviso sempre que detecta um estouro potencial.

Se o seu programa contiver números grandes, você pode usar Longs, que acomodam valores de -²⁶³ a +²⁶³-1. Para definir um val do tipo Long, você pode especificar o tipo explicitamente ou colocar um L no final de um literal numérico, que informa ao Kotlin para tratar esse valor como um Long:

fun main()
val i = 0 // Infere Int
val l1 = 0L // L cria Long
val l2: Long = 0 // Tipo explícito
println("$l1 $l2")
}

/* Saída:
* 0 0
*/

Ao mudar para Longs, evitamos o estouro em IntegerOverflow.kt:

fun main() {
val i = Int.MAX_VALUE
println(0L + i + i) // [1]
println(1_000_000 * 1_000_000L) // [2]
}

/* Saída:
* 4294967294
* 1000000000000
*/

Usando um literal numérico tanto em [1] quanto em [2] força os cálculos Long, e também produz um resultado do tipo Long. A localização onde o L aparece é irrelevante. Se um dos valores for Long, a expressão resultante será Long.

Embora possam armazenar valores muito maiores que os Ints, os Longs ainda têm limitações de tamanho:

fun main() {
println(Long.MAX_VALUE)
}

/* Saída:
* 9223372036854775807
*/

Long.MAX_VALUE é o maior valor que um Long pode armazenar.

eeeeeeeeeeeeeeeeeeeeee….

E chegamos ao final de outra dose… e com ela espero que você tenha entendido (pelo menos um pouco) os tipos numéricos no Kotlin. Por "parível que increça", não aplicar corretamente os tipos, pode impactar significativamente o desempenho e a confiabilidade do seu aplicativo.

vlwflw, Até a próxima….

--

--

Klaus Dellano
HavanLabs

Developer Engineer at HAVAN, focusing on Mobile and RFID.