domingo, 23 de agosto de 2015

[Lenguaje Arduino 04] Variables (parte 5: Cambio de tipo de datos)

Cambio de tipo de datos (numéricos)
Cuando se inicia el sketch no se puede cambiar nunca el tipo de una variable: si esta se declaró de un tipo concreto, seguirá siendo de ese tipo a lo largo de todo el sketch. Pero lo que sí se puede hacer es cambiar durante un momento concreto el tipo del valor que contiene. Esto se llama “casting”, y puede ser útil cuando se quiere utilizar ese valor en cálculos que requieran un tipo diferente del original, o también cuando se quiere asignar el valor de una variable de un tipo a otra variable de tipo diferente. Para convertir un valor (del tipo que sea) en otro, podemos utilizar alguna de las siguientes instrucciones:
  • char() : escribiremos entre ( ) el valor –o el nombre de la variable que lo contiene- que queremos convertir en tipo “char”
  • byte() : escribiremos entre ( ) el valor –o el nombre de la variable que lo contiene– que queremos convertir en tipo “byte”
  • int() : escribiremos entre ( ) el valor –o el nombre de la variable que lo contiene– que queremos convertir en tipo “int”
  • word() : escribiremos entre ( ) el valor –o el nombre de la variable que lo contiene– que queremos convertir en tipo “word”
  • long() : escribiremos entre ( ) el valor –o el nombre de la variable que lo contiene– que queremos convertir en tipo “long”
  • float(): escribiremos entre ( ) el valor –o el nombre de la variable que lo contiene– que queremos convertir en tipo “float”
Ejemplo 2:
float variablefloat=3.4;
byte variablebyte=126;
void setup() {
          Serial.begin(9600);
          Serial.println(byte(variablefloat));
          Serial.println(int(variablefloat));
          Serial.println(word(variablefloat));
          Serial.println(long(variablefloat));
          Serial.println(char(variablebyte));
          Serial.println(float(variablebyte));
}
void loop() {
  //No se ejecuta nada aquí
}

Se puede comprobar, una vez ejecutado el sketch anterior, que todas las conversiones del valor float a valores de tipo entero (sin tomar en cuenta si son “byte”, “int”, ”word” o “long”) truncan el resultado: pasamos de tener un 3,4 a tener un 3. La diferencia está en los bytes que ocupa ese 3 en la memoria. También podemos comprobar que al convertir un valor numérico a tipo “char”, se muestra el carácter correspondiente de la tabla ASCII. Finalmente se puede decir que la conversión de un valor entero en un valor decimal provoca que podamos trabajar precisamente con decimales a partir de entonces.

Ejemplo 3: Una situación donde debemos controlar que los tipos de las variables sean los correctos es en el cálculo matemático. Por ejemplo, si se ejecuta el siguiente código, se puede ver que el resultado obtenido es 2 cuando debería ser 2,5.
float resultado;
int numerador=5;
int denominador=2;
void setup() {
          Serial.begin(9600);
          resultado=numerador/denominador;
          Serial.println(resultado);
}
void loop() {}
Esto pasa porque tanto las variables “numerador” como “denominador” son enteras, por esto mismo, el resultado siempre será entero, a pesar de que lo guardemos en una variable de tipo “float”. Esto es así porque el resultado de un cálculo matemático donde intervienen diferentes tipos enteros siempre es del tipo entero con mayor uso de memoria y con signo (para no perder información) pero nunca es decimal. Solo si interviene en el cálculo algún valor de tipo decimal, entonces el resultado será de tipo decimal (aunque seguirá siendo responsabilidad nuestra guardar ese valor en una variable de tipo decimal para que no haya truncamientos).
Para solucionar el problema del código anterior, debemos realizar un “casting” a “float” de uno de los dos elementos de la división (o de los dos si queremos) para que la operación se realice utilizando esos nuevos tipos, y se obtenga un resultado ya “re tipeado” listo para ser asignado a la variable de tipo decimal. O sea, tenemos que sustituir resultado=numerador/denominador; por, por ejemplo: resultado=float(numerador)/denominador;.

Ejemplo 4: Otro problema relacionado con el cambio de tipos de datos lo podemos observar en el siguiente código:
int numero=100;
long resultado;
void setup() {
          Serial.begin(9600);
          resultado=numero*1000;
          Serial.println(resultado);
}
void loop() {}
Al ejecutar el código anterior veremos por el “Monitor serial” un número que no es el resultado correcto de multiplicar 100 por 1000. ¿Por qué, si la variable “resultado” es de tipo “long” por lo tanto puede almacenar un número de esta magnitud? Esto para porque el valor que se le asigna es el resultado de multiplicar el valor de “numero” (de tipo “int”) por el número 1000 (que, si no se especifica explícitamente, es de tipo “int” también ya que debemos saber que todo número escrito literalmente es siempre de tipo “int”). 
Es decir, se multiplican dos valores de tipo “int”, y el resultado de esta multiplicación sobrepasa el rango aceptado por ese tipo de datos. Hacer que la variable “resultado” sea de tipo “long” no influye para que el número obtenido de la multiplicación de dos números “int” deje de ser incorrecto, porque el cálculo de la multiplicación se realiza antes de asignar el resultado a “resultado”. Es decir, cuando se asigna el valor a “resultado”, este ya ha sufrido el "overflow".
Para evitar esto, lo más fácil sería forzar a que alguno de los elementos presentes en el cálculo sea del mismo tipo que el de la variable “resultado” porque, como ya se ha comentado anteriormente, el tipo de datos del número resultante es el mismo que el tipo de datos del operando con mayor capacidad (memoria). Por lo tanto, si por ejemplo hacemos que la variable “numero” sea “long”, el resultado de la multiplicación será de tipo “long”, con lo que no se producirá “overflow” y se asignará finalmente de forma correcta a la variable “resultado”. Es decir:
int numero=100;
long resultado;
void setup() {
          Serial.begin(9600);
          resultado=long(numero)*1000;
          Serial.println(resultado);
}
void loop() {}
Finalment ya sabemos que cuando dentro del código Arduino escribimos directamente números, se asume por defecto que son de tipo “int”. Sin embargo, si tras su valor literal añadimos la letra “U”, su tipo por defecto será “word”, si añadimos “L”, su tipo será “long” y si se añadimos “UL”, su tipo será “unsigned long”. Es decir, una línea como resultado=numero*1000L; hubiera conseguido el mismo efecto que la línea presente en el código anterior.

1 comentario: