Operaciones con float

Buenas, mi nombre es Jose y soy un nuevo tertuliano de foro. :smiley:

El problema que tengo es que estoy realizando una serie de operaciones con numeros float y creo que algo no esta funcionando del todo bien. He ledio que las comparaciones con datos float pueden no ser del todo exactas, que tampoco entiendo muy bien porque, en mi caso hago una multiplicacion de una variable float de este tipo xx.xx por 100 y moviendolo a una nueva variable int. El resultado no es siempre el que tendria que ser, las primeras veces que hago la operacion sale ok, pero segun la repito cada vez va acumulando un pequeño error, por lo que entiendo que algun punto, supongo que en decimales de la variable que no veo, va acumulando un error que tarde o temprano llega a xx.xx. He probado otras cosas pero siempre termino llegando al error en la multiplicacion, no se si alguien me podra ayudar a resolver esto.

Pongo una imagen para que lo veais mas claro

Saludos

Hola,
Bienvenido al foro.
Si pones el código lo podremos ver mejor.

Buenas,
Si claro pego el codigo tal cual lo imprimo en la imagen que he puesto antes

EEPROM.write(14, unidades1); //Escribimos byte unidades1 en eeprom
Serial.println(unidades1);
delay(1000);

EEPROM.write(18, decimales1); //Escribimos byte unidades1 en eeprom
Serial.println(decimales1);
delay(1000);

Serial.println(consigna1);
delay(1000);

consigna1_int= consigna1*100.00; //El fallo esta aqui, en esta multiplicacion es donde se produce el redondeo, probado *100 y *100.00
Serial.println(consigna1_int);
delay(1000);

consigna1_2=consigna1_int & 255; //Separamos la variable (consigna1_int) en dos bytes, consigna1_1 es el byte alto y consigna1_2 el byte bajo
Serial.println(consigna1_2);
delay(1000);

consigna1_1=(consigna1_int >> 8 ) & 255;
Serial.println(consigna1_1);
delay(1000);

EEPROM.write(22, consigna1_1); //Guardamos consigna
EEPROM.write(24, consigna1_2); //

consigna1 = ((consigna1_1 << 8) | (consigna1_2)); //Y volvemos a montar
Serial.println(consigna1);
delay(1000);

consigna1=(consigna1/100); //Dividimos por 100 y volvemos a float
Serial.println(consigna1);
delay(1000);

Hola, arduka.
No he estudiado muy a fondo tu sketch, pero te doy un par de principios que creo que hay que tener en cuenta cuando usemos float:

  • Todas las operaciones las haremos sobre los float, ya que si usamos un int convertido, nos arrastrará un error cada vez más grande; es decir, si en un momento hemos de convertir a int para mostrarlo, siempre que sea posible continuaremos utilizando el valor anterior del float, en lugar de introducir el int convertido, porque iremos acumulando cada vez más error.
  • La conversión float a int, trunca directamente los decimales, por lo que sería conveniente, después de multiplicar por 100.0, aún dentro del float, sumarle 0.5, para que al truncar redondee al alza si el primer decimal es mayor o igual a 5.

Espero te pueda servir de ayuda.
Saludos

gracias por la respuesta noter, despues de leer lo que has escrito, confirmas las ideas que tenia, aunque sigo sin entender muy bien el caso, vamos por partes y me explico:
1- yo creo la variable float pj con 12.30
2- multiplico x 100.00= 1230
3- divido x 100=12.30
Esto mismo en resumen es lo que tengo, por eso no entiendo que no funcione bien. Lo entenderia si pj el valor inicial 12.30, viera de una operacion entre dos float, pero no es el caso, es una variable que inicialmente yo fuerzo a un valor y que la comparo mas tarde con otros valores, pero en ningun caso ni divido ni multiplico, claro esta, excluyendo *100.00 y /100.00.

Despues de pensarlo un poco, la realidad es que no necesito el valor int, solo era un paso para mas tarde llegar a byte y llegar a la eeprom. Aprovechando que curro a intervenido tambien, entiendo que este codigo que tu colgaste curro, solucionaria el problema, no?

#include <EEPROM.h>

union Float_Byte
{
 float datoF;
 byte  datoB[4];
} unionFB;

void setup()
{
   Serial.begin(9600);
}


void loop()
{
   unionFB.datoF = -12.13 ;

   Serial.print("\nFloat 1: ") ;
   Serial.println(unionFB.datoF);
   Serial.print("\nbyte1: ");  Serial.print(unionFB.datoB[0], DEC) ; // 123
   Serial.print("\nbyte2: ");  Serial.print(unionFB.datoB[1], DEC) ; // 20
   Serial.print("\nbyte3: ");  Serial.print(unionFB.datoB[2], DEC) ; // 66
   Serial.print("\nbyte4: ");  Serial.print(unionFB.datoB[3], DEC) ; // 193
   
   // guardar un float en la EEPROM en las posiciones 0-3
   EEPROM.write(0, unionFB.datoB[0]);
   EEPROM.write(1, unionFB.datoB[1]);
   EEPROM.write(2, unionFB.datoB[2]);
   EEPROM.write(3, unionFB.datoB[3]);
     
   // reconstuir el float leyendo la EEPROM
   unionFB.datoF = 0.0 ;
   unionFB.datoB[0] =  EEPROM.read(0);
   unionFB.datoB[1] =  EEPROM.read(1);
   unionFB.datoB[2] =  EEPROM.read(2);
   unionFB.datoB[3] =  EEPROM.read(3);

   Serial.print("\n\nFloat 2: ");
   Serial.println(unionFB.datoF);
  
   delay(5555); // para no desperdiciar ciclos
}

Saludos y gracias nuevamente

Buenas

Efectivamente el codigo que colgo curro en su dia, ha solucionado el problema

#include <EEPROM.h>

union Float_Byte
{
 float datoF;
 byte  datoB[4];
} unionFB;

void setup()
{
   Serial.begin(9600);
}


void loop()
{
   unionFB.datoF = -12.13 ;

   Serial.print("\nFloat 1: ") ;
   Serial.println(unionFB.datoF);
   Serial.print("\nbyte1: ");  Serial.print(unionFB.datoB[0], DEC) ; // 123
   Serial.print("\nbyte2: ");  Serial.print(unionFB.datoB[1], DEC) ; // 20
   Serial.print("\nbyte3: ");  Serial.print(unionFB.datoB[2], DEC) ; // 66
   Serial.print("\nbyte4: ");  Serial.print(unionFB.datoB[3], DEC) ; // 193
   
   // guardar un float en la EEPROM en las posiciones 0-3
   EEPROM.write(0, unionFB.datoB[0]);
   EEPROM.write(1, unionFB.datoB[1]);
   EEPROM.write(2, unionFB.datoB[2]);
   EEPROM.write(3, unionFB.datoB[3]);
     
   // reconstuir el float leyendo la EEPROM
   unionFB.datoF = 0.0 ;
   unionFB.datoB[0] =  EEPROM.read(0);
   unionFB.datoB[1] =  EEPROM.read(1);
   unionFB.datoB[2] =  EEPROM.read(2);
   unionFB.datoB[3] =  EEPROM.read(3);

   Serial.print("\n\nFloat 2: ");
   Serial.println(unionFB.datoF);
  
   delay(5555); // para no desperdiciar ciclos
}

Aunque para comprender mejor como se procesan los float en arduino, haber si alguien me puede aclarar, el porque del mal funcionamiento de esto

1- yo creo la variable float pj con 12.30
2- multiplico x 100.00= 1230
3- divido x 100=12.30
Esto mismo en resumen es lo que tengo, por eso no entiendo que no funcione bien. Lo entenderia si pj el valor inicial 12.30, viera de una operacion entre dos float, pero no es el caso, es una variable que inicialmente yo fuerzo a un valor y que la comparo mas tarde con otros valores, pero en ningun caso ni divido ni multiplico, claro esta, excluyendo *100.00 y /100.00.

Se trata de aprender, no de apagar fuegos....

Saludos!!!!

Para entender eso, tendrías que mirar cómo se almacenan los números en coma flotante en memoria. Busca en Google sobre el tema si tienes curiosidad, pero en resumen el problema es que muchos decimales redondos en decimal son periódicos en binario. Por ejemplo, intenta obtener 0,1 (1/10) sumando fracciones potencia de 2 (1/2, 1/4, 1/8, 1/16...) y verás que acabas en un ciclo sin salida. Por ello también las comparaciones en punto flotante son peligrosas, porque por muy lógico que a nosotros nos parezca que 0,1+0,9==1.0, en binario ya no es exactamente así, al igual que aunque sabemos que 1/3 + 2/3=1, pero si hacemos los cálculos en lugar de razonar, obtendremos 0,99999...(con el número de decimales que tengamos la paciencia de sacar).

Buenas,

Gracias noter por la respuesta, aunque creo que me cuesta coger un poco tu explicacion y por google no encuentro nada que me lo aclare.... Si sabes de algun enlace que puedas colgar para aclararme un poco, te lo agradeceria

Saludos y gracias

podria ser desbordamiento de variable o algo asi se llama, es decir
por ejemplo si tienes un variable de tipo int y un uint8_t

int a= 300; uint8_t b ;
b=a;

como el el tipo uint8_t usa solo un byte , es decir 0-255, y el tipo int usa dos bytes de range of -32,768 to 32,767 (minimum value of -2^15 and a maximum value of (2^15) - 1).

es claro si pongo el 300 al uint8_t, no calza por lo que se desvorda y te coloca por ejmplo 20, o algo asi.

tambien debes tener en cuenta que si pones un float en un int siempre hay trucamiento, no aprace la parte decimal

Esencialmente lo que ocurre es que muchos números decimales exactos en base 10 son periódicos en base 2, osea que tienen infinitos decimales, con lo que operaciones como la que sugieres de dividir por diez (por ejemplo 0,1 es periódico en binario) pierdes precisión. Si encima no redondeas, sino que truncas, la pérdida de precisión es aún mayor.
Echa un vistazo por ejemplo a esta página, a ver si lo entiendes mejor:

Por cierto, y volviendo al problema inicial que se te presentaba, para un asunto similar yo opté por utilizar enteros directamente (es decir, multiplicar el decimal por 100 y trabajarlo como entero, y sencillamente a la hora de representar la variable resultado (entera), imprimir resultado/100, luego imprimir la coma y luego imprimir resultado%100.

Nuevamente noter, muchas gracias

La pagina que has puesto es precisamente lo que necesitaba, una explicacion y un ejemplo. Lo que ocurre es que vengo del mundo plc´s donde este problema simplemente no lo notas (porque ya se encargan los fabricantes de que no te enteres), lo maximo que tienes que hacer es cambiar el orden de los bits y/o bytes de los float, en una comunicacion entre diferentes fabricantes. Lo que esta claro, es que tendre que cambiar mi programa para trabajar solo con enteros....

Ahora me surge una duda, internamente con cuantos decimales trabajan las variables float, porque en serial o lcd por defecto imprime 2 ( a menos que le digas lo contrario) pero por las pruebas que he hecho la variable tiene mas decimales, pj una variable que en pantalla es 16.20, si escribo esto 16.20 =>16.20, no siempre se cumple aunque este visualizando en pantalla 16.20, por lo que por defecto la variable float debe tener mas de 2 decimales.

Saludos

En el mismo artículo que has leído lo explica. Un número float se almacena en forma de coma flotante, compuesto por 24 bits de mantisa y 8 de exponente. La "precisión total" es, por tanto de 24 bits "representativos". Si hablamos de un número con parte entera y parte fraccionaria, se van a dedicar a la parte fraccionaria todos los bits que no sean necesarios para la parte entera. Para que hagas una idea en sistema decimal, sólo usando tres cifras "representativas" y desplazando la coma, el número 123 podría "representar" 123000000 (con una precisión de millón arriba-millón abajo) o en otra circunstancia 0,000000123 (milmillonésima arriba-milmillonésima abajo). También podría representar 1,23 o 12,3 (aquí vemos que cuanto más grande la parte entera, menos exacta es la parte decimal). Como verás, es un sistema muy bien pensado para aprovechar muy bien esos 32 bits, pero las operaciones con ellos son complejas. Para nosotros creo que es suficiente conocer hasta aquí y entender que los float pueden ser muy precisos, pero no necesariamente exactos a un decimal.

Buenas

Creo que no me explique del todo bien, jejej

Esta claro que el numero teorico de decimales de una variable float depende de la parte entera, pero la duda que tengo es, en los calculos internos del arduino este supongo que usa todos los decimales disponibles en la variable float para las operaciones, si en vez de utilizar para las operaciones todos los decimales disponibles, podemos usar solo los decimales que nosotros digamos? Como para imprimir podemos decirle que utilice los decimales que desemos, podemos hacer esto mismo para las operaciones? Si no recuerdo mal, hace tiempo lei en algun sitio que en c habia una forma de decirle la cantidad de decimales que querias, que tuviera la variable float, pero no lo enuentro y no se si esto es cierto o simplemente lo lei mal o lo recuerdo mal.

Saludos y gracias

Pues no me suena a mí que se pueda limitar el número de decimales con los que trabaje internamente la aritmética float. Yo supongo que siempre trabaja con todo el número, combinando mantisas y exponentes tratando de mantener la máxima precisión. Otra cosa es que a la hora de representar el número haya funciones que te permiten especificar la precisión, pero sólo es eso, la forma de salida, no la forma de almacenar u operar con floats. Por ejemplo, en sprintf("%5.2f",x) imprimimos el float x usando 5 dígitos, la coma y 2 decimales. Si tu preocupación es por comparar floats o trabajar con un número determinado de decimales, debes recurrir al redondeo o te llevarás sorpresas.