Dubbi con aritmetica e float

Ciao a tutti. Vi scrivo per chiedervi dei chiarimenti in merito all'aritmetica con Arduino.
Ho scritto uno Sketch che converte le coordinate equatoriali di un corpo celeste in coordinate altazimutali e viceversa.
Dovendo lavorare con i numeri reali utilizzo delle variabili float.
Una riga del codice è la seguente:

delta_target = (DECtarget / 3600) * pigreco / 180;

dove pigreco è una const float pari a 3,141593

ho notato che i calcoli eseguiti nell’ordine sopra riportato comportano delle perdite di cifre significative, con conseguente imprecisione nella conversione.

Riadattando l’ordine degli operatori come sotto indicato

delta_target = (DECtarget * pigreco) / 648000;

ovvero dividendo per 3600*180 = 648000 solo dopo aver effettuato tutte le moltiplicazioni, l’incertezza dovuta alla perdita di cifre significative si riduce drasticamente, tanto da essere sufficiente ai miei scopi.

Questo fatto dovrebbe dipendere da come vengono gestiti i dati nelle float, ma non mi è del tutto chiaro.

Qualcuno potrebbe darmi delle spiegazioni in merito?
Grazie e buon pomeriggio a tutti

Ezio

>zioetzi: essendo il tuo primo post, nel rispetto del regolamento (… punto 13, primo capoverso), ti chiedo cortesemente di presentarti QUI (spiegando bene quali conoscenze hai di elettronica e di programmazione ... possibilmente evitando di scrivere solo una riga di saluto) e di leggere con MOLTA attenzione il su citato REGOLAMENTO ... Grazie.

Guglielmo

P.S.: Qui una serie di link utili, NON necessariamente inerenti alla tua domanda:
- serie di schede by xxxPighi per i collegamenti elettronici vari: ABC - Arduino Basic Connections
- pinout delle varie schede by xxxPighi: Pinout
- link generali utili: Link Utili

:o a dire il vero mi ero presentato nel 2013...

http://forum.arduino.cc/index.php?topic=176345.0

Mancavo dal forum da un po' di tempo. Cosa è successo al mio account?
Sono ripartito da zero messaggi...

Non so dirti cosa è accaduto dal 2013 ad oggi (... manutenzione forum ? Mah) ... comunque, nell'apposito thread una tua presentazione non c'è e quindi ... dedica 5 minuti a farla :), così ci racconti bene quali conoscenze hai di elettronica e di programmazione e ci aiuti a "calibrare" le risposte :wink:

Grazie,

Guglielmo

zioetzi:
ho notato che i calcoli eseguiti nell’ordine sopra riportato comportano delle perdite di cifre significative, con conseguente imprecisione nella conversione.

Se è quello che sospetto (cioè che parte dei calcoli venga fatta con numeri interi) così si dovrebbe risolvere:

delta_target = ((float)DECtarget / 3600) * pigreco / 180;

(sempre che DECtarget sia un intero ovviamente)

Basta anche forzare le costanti a float, 3600.0 invece di 3600

Ho forzato le costanti intere a float e tutto funziona. :smiley:
Grazie mille

Ezio

zioetzi:
Questo fatto dovrebbe dipendere da come vengono gestiti i dati nelle float, ma non mi è del tutto chiaro.

I float sono valori rappresentati tramite un esponenziale, in pratica hai una mantissa moltiplicata per una potenza di due, il problema è che con 32 bit rimangono solo 24 bit per la mantissa, 1 è usato per il segno e 7 per l'esponente, pertanto questo porta ad un grosso limite nelle cifre significative disponibili, tipicamente tra 6 e 7.
Per dirla in modo semplice se con un float vuoi rappresentare 123.123454678 in realtà questo diventa 123.1234 nella migliore delle ipotesi, tutte le altre cifre vnno perse, anzi è altamente probabile che diventa una cosa del tipo 123.1233999, come valore equivalente, perché si tratta comunque di una potenza.
Qui trovi un'ottima spiegazione su come sono rappresentati i float in formato IEEE 754, è lo standard usato da tutti i compilatori C, anzi da quasi tutti compilatori anche per altri linguaggi.
Dato che sulle MCU a 8 bit al massimo i compilatori supportano i float, 32 bit, i double, 64 bit, anche se riconosciuti vengono comunque trattati come float, è sempre una cosa intelligente evitare di usarli se non strettamente necessario, p.e. nel caso dei calcoli trigonometrici dove sono obbligatori, e lavorare il più possibile con gli interi, se servono i decimali basta premoltiplicare tutti i valori per 10-100-1000, etc.
Rammento che il compilatore per gli AVR supporta gli interi a 64 bit, pertanto è possibile rappresentare valori molto grandi anche con la premoltiplicazione per emulare i decimali.
Fare calcoli solo con gli interi ha anche il vantaggio che richiede notevolmente meno tempo cpu rispetto ai calcoli con i float.

astrobeed:
Rammento che il compilatore per gli AVR supporta gli interi a 64 bit, pertanto è possibile rappresentare valori molto grandi anche con la premoltiplicazione per emulare i decimali.

int64_t e uint64_t quindi ? sarebbero i long long ?
Al link il tipo fa test nel IDE 1.6.10 su Atmega32U4 e funziona
https://forum.arduino.cc/index.php?topic=415836.msg2863862#msg2863862

Si gli int64_t e uint64_t sono i long long, confermo che sono supportati al 100% perché li uso nel codice per il sensore di pressione del rov, dove è presente un ADC delta sigma da 24 bit e i vari calcoli matematici per la conversione da valore raw a pressione reale vanno fatti forzatamente a 64 bit se si vuole ottenere la risoluzione reale a 18 bit.

Grazie, mi è tutto più chiaro.
Le float infatti le uso solo dove è strettamente necessario. Intervengono solo nella conversione tra le coordinate equatoriali/altazimutali e viceversa perché ci sono funzioni goniometriche, poi lavoro con le long.
Ciao e tutti,
Ezio

@astro, non mi picchiare (anche se lo meriterei)

Ho un messo 123.52 in un long long quindi 12352
Poi ho messo 9.56 in un long long quindi 956
Moltiplicazione: 12352 * 956 => 11808512 123.52 * 9.56 => 1180.8512
Dopo la moltiplicazione tra i due long long se mi interessano solo 2 cifre decimali, devo ammazzare 2 cifre ?
Mi sono un pò perso. :confused:

Devi arrotondare, elimini la cifra decimale e modifichi la precedente:

  • se la cifra eliminata è minore di 5, la lasci inalterata
  • se la cifra eliminata è maggiore di 5, la aumenti di 1
  • se la cifra eliminata è 5, allora arrotondi alla cifra pari (xxx.335 diventa xxx.34, xxx.325 diventa xxx.32)

nid69ita:
Dopo la moltiplicazione tra i due long long se mi interessano solo 2 cifre decimali, devo ammazzare 2 cifre ?

Beh si, 100A * 100B = 10000AB
Quindi il risultato va diviso per 100

Ci sono anche altri modi usando le potenze di due... che poi è sempre lo stesso
((10A)<<14) * ((10B)<<14) = 100268435456AB
e poi scorri tutto a destra di 28 bit:
(a
b) >> 28

Grazie per le risposte.
Non sò se utile, ma stò creando una classe Decimal per contenere numeri long long.

class Decimal : public Printable
{ private:
    int64_t  value;
    uint8_t  decimals; 
    uint32_t numdec;     // decimals=3 numdec=1000
...
Decimal operator+(Decimal d)
    { Decimal newd;
      newd.decimals=d.decimals;
      newd.value = value + d.value ;
      return newd;
    }

Una classe anche printable e con operatori overloaded.
La difficoltà è su operatore *

Decimal operator*(Decimal d)   
    { Decimal newd;
      newd.decimals=d.decimals;
      newd.value = value * d.value ;        // errato      
                                            // 123.45 * 9.52 => 1175.244        123.45 * 9.5 => 1172.775
                                            //  12345 * 952  => 11752440        12345 * 950  => 11727750
      return newd;
    }

Nella mia idea per ogni oggetto Decimal decidi anche quanti decimali (max 10) e quindi potrei moltiplicare 2 Decimal con numero decimali desiderati diversi.
Forse è meglio scegliere un numero di decimali fisso, esempio 10 con una bella #define e quindi decidere quanti decimali stampare solo in fase di "output" del dato ?
Comunque mi sono impantanato con la operator*