(RISOLTO) operazioni con elevato numero di cifre

Ciao a tutti, sto facendo dei calcoli con arduino e mi sono imbattuto in un problema. Sto calcolando dei parametri che hanno cifre significative lontane dalla virgola. Di seguito ho messo uno sketch semplificato giusto per mettere in evidenza il mio problema. Fino al valore danno3 non ho problemi. Tuttavia quando vado a sommare questo valore al parametro danno a cui associo ad esempio 0,99 che ha cifre significative vicino alla virgola il risultato è errato. Non riesco a capire come rimediare a questo problema. Sapreste darmi un consiglio? Vorrei precisare che il dato in input è carico che va da 0 a 1023 e danno4 che va e 0 a 1. N.B. danno+danno3 al massimo può essere 1. Ecco lo sketch:

int carico=50;
double danno4=0.99;
double danno1=0;
double danno2=0;
double danno3=0;


void setup() {

    Serial.begin(9600);
    danno1= (double) carico/1023;
    danno2= (double) danno1*danno1*danno1;
    danno3= (double) danno2/125000;
    danno= (double) (danno4+danno3);
    Serial.print( (double) danno1,9);
    Serial.print(" ; ");
    Serial.print( (double) danno2,9);
    Serial.print(" ; ");
    Serial.print( (double) danno3,14);
    Serial.print(" ; ");
    Serial.print ( (double) danno,14);
    
}

void loop() {
    
}

edit by mod: codice incluso negli appositi tag

Se i dati li devi passare ad un altro software su PC ti conviene mandare i dati grezzi e elaborarli dal PC. L'Arduino UNO con il suo processore a 8bit non è adatto per i calcoli in virgola mobile. Inoltre Arduino UNO non ha i double ma usa sempre i float che sono limitati come precisione a sole sei cifre totali.

No non li devo passare sul pc. Poi li salvo su una memory card per poterli visualizzare. Conosci qualche altra scheda di arduino più potente che mi permette di fare questi calcoli?

Così compila.

int carico = 50;
double danno4 = 0.99;
double danno1 = 0.0;
double danno2 = 0.0;
double danno3 = 0.0;


void setup() {
  Serial.begin(9600);
  danno1 = (double) carico / 1023.0;
  danno2 = (double) danno1 * danno1 * danno1;
  danno3 = (double) danno2 / 125000.0;
  double danno = (double) (danno4 + danno3);
  Serial.print((double) danno1, 9);
  Serial.print(" ; ");
  Serial.print((double) danno2, 9);
  Serial.print(" ; ");
  Serial.print((double) danno3, 14);
  Serial.print(" ; ");
  Serial.println((double) danno, 14);    
}

void loop() {
}

L'Arduino DUE ha un processore a 32bit. Ma non conosco la precisione del calcolo float. Se non è urgente puoi aspettare l'uscita dell'Arduino ZERO, sempre con un processore a 32 bit. Dovrebbe costare meno della DUE.

Ho compilato come mi hai detto ma il problema permane... :(

PaoloP: L'Arduino DUE ha un processore a 32bit. Ma non conosco la precisione del calcolo float ...

Dal reference :

Double precision floating point number. On the Uno and other ATMEGA based boards, this occupies 4 bytes. That is, the double implementation is exactly the same as the float, with no gain in precision. On the Arduino Due, doubles have 8-byte (64 bit) precision.

Guglielmo

Matteo90:
Ho compilato come mi hai detto ma il problema permane… :frowning:

Permane perché Arduino, versioni ad 8 bit, non può fare i calcoli con i double, sono presenti per compatibilità con lo standard del linguaggio però vengono trattati come dei normali float a 32 bit con il limite di solo 6-7 cifre significative complessive.
Se hai una sola cifra per l’intero al massimo puoi avere sei decimali, se hai tre cifre per l’intero al massimo puoi avere quattro cifre per i decimali, inoltre i float/double comportano sempre problemi di arrotondamento, oltre a errori cumulativi, che possono portare a risultati bizzarri come 1/2 che non fa 0.5 ma potrebbe dare, dipende da svariate condizioni, 0.499999 oppure 0.500001

La butto lì, potrebbe anche non fare al caso tuo: Visto che si parla di soli valori positivi, e se usassi una unsigned long? Mi spiego, moltiplichi i tuoi valori per 1.000.000 (un milione), in modo da spostare la virgola il più a destra possibile. In fase di visualizzazione dividi i valori che trovi per un milione. Così a quel punto hai i valori 0,xxxxx che servono a te.

Dato che "danno" non sarà mai maggiore di 1, e che con la unsigned long puoi avere al massimo un valore di 4,294,967,295, dovresti starci dentro alla grande, anche moltiplicando per 10 milioni i tuoi valori.

o usare la libreria BigDecimal

lesto: o usare la libreria BigDecimal

Meglio ancora usare un processore a 32 bit, in questo caso la DUE potrebbe essere la soluzione ideale visto che può usare i double a 64 bit.

Diverse soluzioni, le rielenco in ordine di rapporto costo/prestazioni (per costo intendo non solo il prezzo di acquisto ma anche il sovraccarico in termini di prestazioni):

  • una FPU esterna dedicata, come la uM-FPU64: gli spedisci i dati con l'operazione da svolgere e lei ti rende il risultato; hai però da acquistare un chip che costa qualche decina d'euro ed aumenti la complessità del progetto;

  • Arduino DUE: è indubbio che numeri in virgola mobile a 64 bit risolvono tutti i problemi, però la scheda costicchia e se serve solo per fare 2 calcoli, non conviene;

  • bigNumber: la libreria può sulla carta lavorare con un numero teoricamente infinito di cifre, ovvio che nell'ambiente limitato dell'Atmega328 queste cifre non sono poi tantissime, tenuto conto anche dell'occupazione di risorse, per cui se il programma diventa complesso si riducono anche le cifre gestibili. Però può essere d'aiuto nel caso tu voglia avere una decina di cifre decimali;

  • trattare i numeri in virgola mobile come interi. Addirittura, oltre agli unsigned long il compilatore supporta (via software) anche gli unsigned long long a 64 bit, devi mettere in conto un pò di Flash in più per la gestione di questi numeri.

Ringrazio tutti per i consigli che mi avete dato. Ho provato la libreria BigNumber ma ho riscontrato un piccolo problema: non effettua operazioni matematiche con numeri che gli vengono assegnati come input leggendone solo la parte intera. Ossia se io gli dico di sommarmi 1.8+4.1 il risultato è 5 e non 5.9. Se invece questi numeri (1.8 e 4.1) derivano da altri calcoli effettuati prima dal codice non da problemi e il risultato è 5.9. Non riesco a capire il motivo.. è un limite della libreria oppure c'è una soluzione a questo problema?

La libreria overloada gli operatori quindi dovresti poter fare tutti i calcoli. Forse la chiami male. Posta il codice

Praticamente non viene letta la variabile d4. Ho notato inoltre che se d4 avesse qualche numero prima della virgola legge solo la parte intera. Quindi in questo caso lo legge come 0. Ecco il codice:

#include "BigNumber.h"

BigNumber carico=10, d1, d2, d3, d5, d4=0.0000097919744;


void printBignum (BigNumber n){
  char * s = n.toString ();
  Serial.println (s);
  free (s);
}  // end of printBignum


void setup() {

    Serial.begin(9600);
    BigNumber::begin ();  // initialize library
    BigNumber:: setScale (20);
    d1 = BigNumber (carico) / BigNumber (1023);
    d2 = BigNumber (d1) * BigNumber (d1) * BigNumber (d1);
    d3 = BigNumber (d2) /  BigNumber (125)  / BigNumber (1000); 
    d5 = BigNumber (d3) + BigNumber (d4);
    
    printBignum (d1);
    Serial.println ( ";" );
    printBignum (d2);
    Serial.println ( ";" );
    printBignum (d3);  
    Serial.println ( ";" );  
    printBignum (d5);    

}

void loop() {
    
}

edit: incluso codice negli appositi tag

non sono sicuro che la BigNumber abbia un costruttore che accetta i float, ed in ogni caso il tuo float verrebbe troncato prima di essere passato alla libreria. Dallo come stringa per non perdere precizione!

BigNumber carico=10, d1, d2, d3, d5, d4="0.0000097919744";

sono a lavoro, quindi dimmi se tutto funziona come previsto

edit: in oltre tu assegni il valore alla variabile PRIMA di settare la scala (ovvero il numero di cifre dopo la virgola), quindi probabilmente la libreria di default tronca. metti il begin/setscale (mi pare siano equivalenti) prima

BigNumber::begin (20);
BigNumber carico=10, d1, d2, d3, d5, d4="0.0000097919744";

La Libreria è questa? --> Big Number

dovrebbe

Funziona!!!!!!! L ho messo dopo il BigNumber::begin (20); dandolo come stringa e ha funzionato. Grazie mille lesto!!! Un'ultima domanda: Mettere BigNumber::begin (20); oppure BigNumber::begin (); seguito da BigNumber:: setScale (20); ho visto che è la stessa cosa. Mi sbaglio oppure c'è qualche differenza?

si è quella li la libreria.

Matteo90: Funziona!!!!!!! L ho messo dopo il BigNumber::begin (20); dandolo come stringa e ha funzionato. Grazie mille lesto!!! Un'ultima domanda: Mettere BigNumber::begin (20); oppure BigNumber::begin (); seguito da BigNumber:: setScale (20); ho visto che è la stessa cosa. Mi sbaglio oppure c'è qualche differenza?

Probabilmente è scritta per accettare il parametro di configurazione direttamente nel begin. L'altro metodo puoi usarlo se vuoi cambiare scala durante l'esecuzione dello sketch.