Operazioni matematiche.

Una delle funzioni principali del microcontrollore è quella di eseguire calcoli, sia con i numeri direttamente che con i valori ​​delle variabili. Iniziamo a tuffarci nel mondo della matematica con le azioni più semplici:

  • + addizione
  • - sottrazione
  • * moltiplicazione
  • / divisione
  • % resto della divisione
  • = assegnamento
Considera un semplice esempio:
int a = 5;
int b = 7;
int c = a + b;
int d = a * b;
Anche questo è possibile.
d = d / a;
c = c * d;
Per quanto riguarda le ultime due righe dell'esempio, quando la variabile è coinvolta nel calcolo del proprio valore: esistono anche operatori composti che abbreviano la notazione:

  • += addizione composta: a += 5 equivale a a = a + 5
  • -= sottrazione composta: a -= 5 equivale a a = a - 5
  • *= moltiplicazione composta: a *= 5 equivale a a = a * 5
  • /= divisione composta: a /= 5 equivale a a = a / 5
  • %= aggiungi resto: a %= 5 equivale a a = a + a % 5
Usandoli puoi accorciare le ultime due righe dell'esempio precedente:
d /= a equivale a d=d/a
c *= d equivale a c=c*d
Molto spesso nella programmazione addizione o sottrazione si usano le unità, per le quali c'è anche una breve notazione:

  • Incremento ++: a++ equivale a a = a + 1
  • -- decremento: a-- equivale a a = a - 1
L'ordine in cui viene scritto un incremento è molto importante: un post-incremento var++ restituisce il valore di variabile prima dell'esecuzione dell'incremento. L'operatore di pre-incremento ++var restituisce il valore di una variabile già modificata. Esempio:
byte a, b;
a = 5;
b = a++;
a otterrà il valore 6
b otterrà il valore 5
a = 10;
b = ++a;
a otterrà il valore 6
b otterrà il valore 6
E auspicabile inizializzare le variabili, altrimenti potrebbero avere un valore casuale e si otterranno risultati imprevedibili nelle operazioni matematiche. Se una variabile al momento dell'avvio del programma, o dopo una chiamata di funzione (variabile locale) deve avere il valore 0.
byte a;        // dichiarare senza inizializzazione 
byte b = 0; // dichiarare e assegnare 0
a++;           // il risultato è imprevedibile 
b++;           // risultato 1 

L'ordine di valutazione delle espressioni segue le consuete regole matematiche: prima si eseguono le operazioni tra parentesi, poi la moltiplicazione e la divisione, infine l'addizione e la sottrazione.

Velocità di calcolo - I calcoli eseguiti richiedono del tempo al processore, dipende dal tipo di azione e dal tipo di dati con cui l'azione viene eseguita. Devi capire che non tutte le azioni in tutti i casi trascorrono tutto il tempo che verrà descritto più avanti: il compilatore cerca di ottimizzare i calcoli il più possibile, poiché puoi provare a cercare in Internet. I calcoli ottimizzati richiedono un tempo trascurabile rispetto a quelli non ottimizzati. È difficile dire se un singolo calcolo nel codice verrà ottimizzato, quindi dovresti sempre prepararti al peggio e sapere come farlo al meglio. Vale a dire:

  • Arduino (su AVR) non ha il supporto hardware per i calcoli in virgola mobile (float) e questi calcoli vengono eseguiti utilizzando strumenti separati e richiedono molto più tempo rispetto ai tipi interi.
  • Più "massiccio" è il tipo di dati, più lunghi vengono eseguiti i calcoli, ad es. le azioni con variabili a 1 byte vengono eseguite più velocemente rispetto a quelle a 4 byte.
  • La divisione (e trovare il resto di una divisione) viene eseguita da strumenti separati (come le operazioni float), quindi questa operazione richiede più tempo di addizione, sottrazione, moltiplicazione. 
Tempo per calcoli non ottimizzati per il compilatore di diversi tipi di dati, il tempo è specificato in microsecondi (µs) per una frequenza di clock di 16 MHz. L'operazione di divisione corrisponde anche all'operazione di resto di divisione, %:

  • int8_t: (+ -) - 0.44µs, (*) - 0.625µs,  (/ %) - 14.25µs
  • uint8_t: (+ -) - 0.44µs, (*) - 0.625µs, (/ %) - 5.38µs
  • int16_t: (+ -) - 0.89µs, (*) - 1.375µs, (/ %) - 14.25µs
  • uint16_t: (+ -) - 0.89µs, (*) - 1.375µs, (/ %) - 13.12µs
  • int32_t: (+ -) - 1.75µs, (*) - 6.06µs, (/ %) - 38.3µs
  • uint32_t: (+ -) - 1.75µs, (*) - 6.06µs,  (/ %) - 37.5µs
  • float: (+ -) - 8.125µs, (*) - 10µs, (/ %) - 31.5µs
Queste informazioni sono fornite solo a scopo di riferimento e non è necessario preoccuparsi della velocità dei calcoli, la maggior parte di essi sarà ottimizzata. Quando possono esserci problemi con la mancanza di velocità di calcolo? Ad esempio: quando Arduino ha calcolato la legge del moto di un paio di dozzine di punti lungo un piano inclinato utilizzando un modello fisico dettagliato. Lì si nota come i microsecondi dei calcoli si siano trasformati in millisecondi. Per progetti semplici senza migliaia di calcoli, onestamente, non preoccuparti.

Divisione intera - con la divisione intera, il risultato non viene arrotondato secondo regole "matematiche": la parte frazionaria viene semplicemente tagliata: sia 9/10 che 1/10 daranno 0. Quando si utilizza float, risulterà automaticamente 0,9 e 0,1. Per arrotondare secondo le solite regole matematiche, puoi usare la funzione round(), ma è piuttosto pesante, ed è così che funziona con i float. Molti problemi possono essere risolti usando la matematica intera, che è molto ottimale in termini di velocità di esecuzione del codice. A volte hai bisogno di una divisione intera arrotondata per eccesso, puoi implementarla in questo modo: (x + y - 1) / y. Gli esempi precedenti di divisione per 10 daranno il risultato 1.

Overflow variabile - Dato che abbiamo iniziato a parlare di azioni che aumentano o diminuiscono il valore di una variabile, vale la pena pensare a cosa accadrà alla variabile se il suo valore esce dall'intervallo? Qui tutto è molto semplice: quando si trabocca verso l'alto, il valore massimo della variabile viene tagliato dal nuovo valore grande e rimane solo il resto. Vediamo un esempio:
tipo di dati byte, min. valore 0, max. valore 255
byte val = 255;
val++; - qui il valore della variabile val diventa 0
val -= 10; - e poi diventerà da zero 246
val = 525; - overflow verso l'alto il resto e 13
val = -20; - overflow verso il basso, val diventa 236

Caratteristiche del calcolo di grandi numeri - Per l'addizione e la sottrazione, la cella predefinita è long (4 byte), ma per la moltiplicazione e la divisione viene utilizzato int (2 byte), che può portare a risultati imprevedibili. Se, moltiplicando i numeri, il risultato supera 32768, verrà calcolato in modo errato. Per risolvere la situazione, è necessario scrivere (tipo di dati) prima della moltiplicazione, il che costringerà l'MK ad allocare memoria aggiuntiva per il calcolo (ad esempio (long) 35 * 1000). Ci sono anche modificatori che fanno più o meno la stessa cosa.

  • U - conversione in formato int senza segno (da 0 a 65'535). Esempio: 36000u
  • L - conversione in formato long (-2 147 483 648... 2 147 483 647). Esempio: 325646L
  • UL - conversione in formato unsigned long (da 0 a 4 294 967 295). Esempio: 361341ul
Come funziona:
long val;

  • val = 2000000000 + 6000000; - calcola correttamente (perché addizione)
  • val = 25 * 1000; - calcola correttamente (moltiplicazione, inferiore a 32768)
  • val = 35 * 1000; - calcola in modo errato (moltiplicazione, maggiore di 32'768)
  • val = (long)35 * 1000; - calcola correttamente (alloca memoria (long))
  • val = 35 * 1000L; - calcola correttamente (modificatore L)
  • val = 35 * 1000u; - calcola correttamente (modificatore u)
  • val = 70 * 1000u; - calcola in modo errato (modificatore u, risultato > 65535)
  • val = 1000 + 35 * 10 * 100; - calcola in modo errato (più di 32768 in moltiplicazione)
  • val = 1000 + 35 * 10 * 100L; - calcola correttamente (modificatore L)
  • val = (long)35 * 1000 + 35 * 1000; - calcola in modo errato, la seconda moltiplicazione rovina tutto.
  • val = (long)35 * 1000 + (long)35 * 1000; - calcola correttamente (alloca memoria (long))
  • val = 35 * 1000L + 35 * 1000L; - calcola correttamente (modificatore L)
Peculiarity float - Arduino supporta i numeri in virgola mobile (decimali). Questo tipo di dati non ha supporto hardware, ma è implementato nel software, quindi i calcoli con esso richiedono molte volte più tempo rispetto a un tipo intero. Oltre ai calcoli lenti, il supporto per lavorare con i float occupa memoria, perché è implementato come una "biblioteca". L'uso di float math (* / + -) aggiunge circa 1000 byte alla memoria flash, una volta, basta collegare lo strumento per eseguire l'azione. Arduino supporta tre tipi di input in virgola mobile:

  • Decimale: 20.5 = 20.5
  • Scientifico: 2.34E5 = 2.34*10^5 o 234000
  • Ingegneria: 67e-12 = 67*10^-12 o 0.000000000067
Esiste una tale funzionalità con i calcoli: se non ci sono numeri float nell'espressione, i calcoli avranno un risultato intero (la parte frazionaria viene tagliata). Per ottenere il risultato corretto, è necessario scrivere una trasformazione (float) prima dell'azione, utilizzare numeri float o variabili float. C'è anche un modificatore f che può essere applicato solo alle cifre float. Non ha senso, ma una voce del genere può essere trovata:

  • float val; - inoltre assegneremo 100/3, ci aspettiamo il risultato 33.3333
  • val = 100 / 3; - calcola in modo errato (risultato 33.0)
  • int val1 = 100; - variabile intera
  • val = val1 / 3;- calcola in modo errato (risultato 33.0)
  • float val2 = 100; - variabile float
  • val = val2 / 3; - calcola correttamente (c'è una variabile float)
  • val = (float )100 / 3; - calcola correttamente (specificare (float))
  • val = 100.0 / 3; - calcola correttamente (c'è un numero float)
  • val = 100 / 3.0f; - calcola correttamente (c'è un numero float e modificatore f)
Quando si assegna un float a un tipo di dati intero, la parte frazionaria viene troncata. Se vuoi arrotondare matematico, devi usarlo separatamente:
valore int;
val = 3,25;                 // val diventa 3
val = 3,92;                 // val diventa 3
val = round(3,25);   // val diventa 3
val = round(3,92);   // val diventa 4
Il prossimo punto importante: a causa delle peculiarità del modello stesso dei "numeri in virgola mobile", i calcoli vengono talvolta eseguiti con un piccolo errore. Vedi (i valori vengono emessi tramite la porta seriale):

  • float val1 = 1.1 - 1.0;  // val1 == 0.100000023 !!!
  • float val2 = 1.5 - 1.0;  // val2 == 0.500000000
Sembrerebbe che val1 dovrebbe diventare esattamente 0.1 dopo la sottrazione. Fare molta attenzione quando si confrontano i numeri float, specialmente con gli operatori <=: il risultato potrebbe essere errato e illogico.
Crea il tuo sito web gratis! Questo sito è stato creato con Webnode. Crea il tuo sito gratuito oggi stesso! Inizia