En esta entrada vamos a ver cómo convertir una cadena de texto a número, bien sea entero o flotante, en un microprocesador como Arduino.
Esta es una petición que vemos habitualmente en páginas y foros de Internet, y sobre la que a veces existe confusión porque hay más de una forma de realizar la conversión, cada una con sus ventajas y desventajas.
En esta entrada veremos las principales formas de convertir una cadena a un número entero o flotante, y cuando resulta conveniente emplear una u otra.
La funciones DEBUG están únicamente para visualizar los resultados. Si usáis estos códigos, podéis quitar las partes relativas a su definición y uso.
Conversión con la clase String
La primera opción que vamos a ver es emplear la clase String, que como sabemos es un wrapper alrededor de un char array dinámico que se incluye en las librerías de Arduino.
La clase string dispone de las funciones toInt() y toFloat() que convierten, respectivamente, la cadena de texto a un número entero o flotante.
Así, el siguiente código muestra la conversión de String a Int,
#define DEBUG(a) Serial.println(a);
String text = "-12345";
void setup()
{
Serial.begin(9600);
long value;
value = text.toInt();
DEBUG(value);
}
void loop()
{
}
Mientras que la conversión de String a Float es la siguiente,
#define DEBUG(a) Serial.println(a);
String text = "-123.45";
void setup()
{
Serial.begin(9600);
float value;
value = text.toFloat();
DEBUG(value);
}
void loop()
{
}
La conversión a través de la clase String es la opción recomendada siempre que no tengamos un motivo de peso para no usarla. Es fácil de usar, es eficiente, y la librería Sting es lo suficientemente ligera para que no penalice su uso.
Como principal desventaja es la ausencia de control de sobre el proceso. Por ejemplo, si la cadena contiene caracteres inválidos el resultado puede ser inesperado (y generalmente incorrecto).
Aunque en la mayoría de los casos esto no supone ningún problema, en caso de tener que lidiar con estas situaciones puede que prefiramos realizar la conversión “a mano” (ver más abajo método Naive)
Conversión con char array
Si por algún motivo no podemos o no queremos emplear la clase String, otra opción es realizar la conversión directamente desde un char array.
Para ello tenemos las funciones atol y atof que convierten un char array, respectivamente, a un número entero o de coma flotante.
El ejemplo para número enteros es el siguiente,
#define DEBUG(a) Serial.println(a);
char* text = "-12345";
void setup()
{
Serial.begin(9600);
long value;
value = atol(text);
DEBUG(value);
}
void loop()
{
}
Que en el caso de números en coma flotante queda así,
#define DEBUG(a) Serial.println(a);
char* text= "-123.45";
void setup()
{
Serial.begin(9600);
long value;
value = atof(text);
DEBUG(value);
}
void loop()
{
}
La eficiencia y comportamiento es similar a los obtenidos usando la clase String ya que, internamente, la clase String emplea las funciones atol y atof para realizar la conversión.
En este caso, tenemos la desventaja de tener que trabajar con char array que, salvo en ciertos casos muy concretos, normalmente será más engorroso que emplear la clase String.
Por tanto, salvo en contadas excepciones, lo normal es que prefiramos las funciones anteriormente vistas en la class String.
Conversión con método Naive
El último caso que vamos a ver es el llamado “método naive”, que no deja de ser una manera sofisticada de denominar a “hacerlo a mano”.
El siguiente ejemplo muestra el proceso de conversión para números enteros, donde procesamos un char array hasta encontrar un carácter que no sea un dígito entre 0 y 9. Previamente hemos comprobado si es un número negativo.
#define DEBUG(a) Serial.println(a);
char *text = "-12345";
void setup() {
Serial.begin(9600);
long value;
value = naiveToInt(text);
DEBUG(value);
}
void loop()
{
}
long naiveToInt(const char *charArray) {
long data = 0;
bool isNegative = false;
if (*charArray == '-')
{
isNegative = true;
++charArray;
}
while (*charArray >= '0' && *charArray <= '9')
{
data = (data * 10) + (*charArray - '0');
++charArray;
}
return isNegative ? -data : data;
}
El caso para un número en coma flotante es algo más complicado ya que, además de detectar el símbolo negativo, debemos detectar el separador decimal (en el ejemplo ’.’ o ’,’). De esta forma la conversión se realiza en dos etapas, una primera de la parte entera antes detectar el separador, y una segunda para la parte entera tras la detección del separador.
#define DEBUG(a) Serial.println(a);
char *text = "-123.45";
void setup() {
Serial.begin(9600);
float value;
value = naiveToFloat(text);
DEBUG(value);
}
void loop()
{
}
float naiveToFloat(const char *charArray)
{
long dataReal = 0;
long dataDecimal = 0;
long dataPow = 1;
bool isNegative = false;
if (*charArray == '-')
{
isNegative = true;
++charArray;
}
while ((*charArray >= '0' && *charArray <= '9'))
{
dataReal = (dataReal * 10) + (*charArray - '0');
++charArray;
}
if (*charArray == '.' || *charArray == ',')
{
++charArray;
while ((*charArray >= '0' && *charArray <= '9'))
{
dataDecimal = (dataDecimal * 10) + (*charArray - '0');
dataPow *= 10;
++charArray;
}
}
float data = (float)dataReal + (float)dataDecimal / dataPow;
return isNegative ? -data : data;
}
El rendimiento del proceso es similar a la función atol y atof y, por extensión, a las funciones de las clases String. Esto es debido a que esta implementación es similar a la empleada internamente por las funciones atol y atof.
La mayor desventaja es, lógicamente, tener que añadir el código en lugar de emplearlo cómodamente a partir de funciones existentes. No obstante, la sencillez de uso es similar.
Sin embargo en este caso tenemos la ventaja de controlar por completo el proceso. Como muestra, en el ejemplo hemos hecho que se admita ’.’ y ’,’ como separador decimal, algo que no podemos hacer con los procesos estándar.
Con sencillez podemos modificar el código para tener otro tipo de comportamiento, como reaccionar de forma diferente a ciertos caracteres, procesar un archivo separado por comas, etc.
Por tanto, emplearemos este método principalmente cuando tengamos condicionantes especiales que impidan usar las funciones estándar, y tengamos que personalizar el proceso de conversión.
Descarga el código
Todo el código de esta entrada está disponible para su descarga en Github.