Curso C++

4. Casteo

Conversión implícita

Si llegaste hasta acá, seguramente notaste que muchas veces los datos de tipo numérico (int, float) no se comportan como esperamos. Por ejemplo, si escribimos la siguiente instrucción:

float valorFlotante = 5 / 2;

Vamos a notar que esta cuenta simple no entrega como resultado 2.5, sino que termina dándonos el valor de 2.

¿Por qué ocurre eso? Porque el procesador puede diferenciar entre cálculos de tipo entero y cálculos con decimales. Si en la operación los operandos que participan son todos de tipo int, el procesador realiza un cálculo de tipo entero y nos devuelve un resultado también de tipo int.

De esta manera, si el resultado esperado es 2.5, al ser un resultado de tipo int, termina “truncando” los decimales y el resultado final es 2. El .5 se pierde. Esto es similar a las divisiones que hacíamos en la escuela primaria, en las que obteníamos, por un lado, el cociente y, por el otro, el resto.

Pero si hacemos lo siguiente:

float variableFlotante = 5.1 / 2;

Sucede un comportamiento diferente. En este caso, es el compilador quien decide cómo va a realizarse la cuenta. Tenemos ambos tipos de datos como operandos, tanto int como float, y, lógicamente, el resultado solo puede ser de un tipo: o es int o es float.

En este punto, el compilador tiene que realizar una conversión antes de que la instrucción llegue al procesador. La decisión que el compilador toma se basa en cuál conversión genera menor o nula pérdida de datos. Entonces, observa ambos operandos y analiza: “Si paso todo a int, perdemos el decimal de 5.1 y el resultado sería 2; pero si, en cambio, paso todo a float, el valor de 5.1 continúa intacto y el 2 pasaría a ser 2.0. No hay pérdida de datos”.

Entonces se produce la primera conversión de datos de la que nosotros no tomamos ninguna decisión. Lo realiza el compilador porque no puede entregarle al procesador operandos distintos. Este tipo de conversiones se llaman conversión implícita. Tenemos nulo control sobre esa acción; es el compilador quien toma las decisiones y hace lo que considera mejor.

Otro ejemplo en el que la conversión implícita aparece es el siguiente:

int variableEntera = 2.1;

Necesitamos almacenar el valor 2.1, que es float (en realidad es un literal double, pero para el ejemplo es lo mismo), en una variable que solo acepta valores de tipo int. Entonces el compilador, para que esa instrucción pueda realizarse, primero ejecuta una conversión pasando el valor de float a int. De vuelta, no somos partícipes de la decisión; es una acción que el compilador debe realizar para llevar adelante la instrucción. El compilador termina almacenando el valor 2 y se pierde información al truncarse los decimales.

En este tipo de conversión, si tenemos activadas las banderas (flags) de advertencias (warnings), vamos a notar cuando queramos compilar que aparece un mensaje indicando que se está realizando una conversión con posible pérdida de datos.

Esta estrategia no es un error. Saber que el compilador realiza estas conversiones puede ser utilizado en nuestro beneficio. Por ejemplo, supongamos que quiero saber cuántas cajas de 12 huevos puedo llenar con 97 huevos. Realizar una división de tipo flotante y entregar como resultado 8.08 no sería correcto; nos piden saber cuántas cajas enteras hay. Ese valor debería ser un número de tipo entero. Entonces, podemos hacer lo siguiente:

int cantidadCajas = 97 / 12;

Al igual que en el primer ejemplo, sabiendo que los decimales se truncan por ser una cuenta de tipo entero (ambos operandos son int), estamos utilizando la naturaleza de la división entera a nuestro favor.

Pero también podemos caer en un error por desconocer las reglas del compilador. Por ejemplo, si quisiéramos mostrar un resultado con decimales al final de una solución, imaginemos que se nos pide sacar un promedio de notas de 10 estudiantes. Para sacar el promedio, hacemos la suma de las notas dividida la cantidad de alumnos:

int notas = 72;
int cantidadAlumnos = 10;

float promedio = notas / cantidadAlumnos;

Al dividir dos operandos de tipo entero, sabemos que como resultado vamos a obtener un número de tipo int. No importa que luego utilicemos una variable flotante para almacenar el resultado: la operación de división ya se resolvió como entera. Cuando el resultado llega a la variable de tipo float, los decimales ya fueron truncados. Finalmente mostraremos 7.0 y no 7.2.

Conversión explícita

Por otro lado, a diferencia de la conversión implícita, tenemos la conversión explícita. En este caso, nosotros como programadores podemos decidir qué hacer con los tipos de datos. Tenemos el control de la conversión y podemos hacer que un valor de un tipo se convierta en otro.

Para esto, debemos indicarle al compilador de manera explícita que queremos realizar la conversión. Si bien hay muchas maneras de hacerlo, para esta primera parte del tutorial vamos a utilizar la más simple. Tomemos el ejercicio del promedio; para solucionarlo, podríamos hacer lo siguiente:

int notas = 72;
int cantidadAlumnos = 10;

float promedio = (float)notas / cantidadAlumnos;

La conversión explícita o casteo consiste en poner como prefijo, y entre paréntesis, el tipo al que queremos convertir una variable. En este caso, tenemos la variable notas que es de tipo int y le damos la indicación al compilador de que la trate como float. Esto lo hacemos para que se realice un cálculo de tipo flotante en lugar de uno entero.

¿Con convertir un solo dato es suficiente? En este ejemplo, sí. Como vimos más arriba, el compilador nota que los operandos son de distintos tipos. Determina que con float se pierden menos datos y, por lo tanto, pasa la variable cantidadAlumnos a float de forma implícita. Al procesador le llegan ambos operandos como el mismo tipo de dato.

Al igual que con la conversión implícita, tenemos que entender cómo funciona el casteo para no caer en errores. Por ejemplo, imaginemos una cuenta más compleja:

int A = 10;
int B = 20;
int C = 30;

float resultado = A / B * (float)C;

Si decidimos realizar un casteo solo a la variable C, terminamos teniendo el mismo resultado que si no hubiésemos realizado ninguna conversión. El cálculo que potencialmente puede entregar decimales es la división; en el momento en que se calcula, ambos valores (A y B) son de tipo entero. Por lo tanto, el procesador realiza 10 / 20 como enteros, lo que da 0. Luego multiplicamos ese 0 por el valor de C (convertido a float), pero ya es tarde: los decimales se perdieron en el primer paso. El resultado será 0 en lugar de 15.

Para realizar la conversión correctamente, debemos aplicarla en el cálculo que tiene la posibilidad de entregar decimales:

int A = 10;
int B = 20;
int C = 30;

float resultado = (float)A / B * C;

O también:

float resultado = A / (float)B * C;

Lo que estamos haciendo es indicarle al compilador que convierta uno de los dos operandos que participan de la división a un dato de tipo float. Es indistinto cuál de los dos sea, porque el compilador realizará una conversión implícita sobre el otro valor antes de entregárselo al procesador.

Extra

La conversión explícita se puede realizar siempre que lo creamos conveniente, incluso en los casos en que el compilador la haría por su cuenta:

float numeroFlotante = 10.1;
int variableEntera = (int)numeroFlotante;

¿Tiene sentido indicar explícitamente una conversión que de todas formas el compilador realizaría? Sí, por dos razones:

  1. Claridad: Dejamos en claro nuestra intención de que la variable pierda los decimales. Al castear, documentamos que sabemos lo que estamos haciendo. Si no lo hacemos, otro programador podría pensar que cometimos un error por descuido.
  2. Silenciar advertencias: Al decirle explícitamente al compilador que realice la conversión, los mensajes de advertencia (warnings) sobre pérdida de datos desaparecerán. El compilador comprende que no es un olvido, sino nuestra intención.

Tabla de comportamiento

Como resumen, aquí tienen la tabla de qué tipo de datos obtenemos como resultado dependiendo de los operandos:

OPERANDO A OPERANDO B RESULTADO
int int int
int float float
float int float
float float float

Siempre que participe un valor decimal (float o double), el resultado será también decimal. Como dijimos, esto se debe a que esta conversión conlleva una menor o nula pérdida de datos.