Problema matematico in principiante di Arduino

int intVar =0;

intVar = (234*142) / 250

intVar conterra' il numero negativo -129 invece di 132.
Come e' possibile ?
234*142 e' uguale a 33228 quindi superiore ai famigerati 32767, ma il risultato finale di tutta la operazione e' abbondantemente al disotto ; possibile che la variabile a sinistra venga usata come deposito provvisorio durante le operazioni intermedie ? Altra spiegazione ?
Grazie

Non sono un teorico del C, ma direi che viene fatto un cast implicito a int.
Prova a fare un cast esplicito e vedrai che tutto torna.

Il linguaggio C esegue i tuoi ordini in maniera esatta, senza fare sue considerazioni.
Hai dei calcoli con cifre tutte int, quindi la moltiplicazione non ci sta in un int (verranno usati dei registri della MCU a 16 bit).
Perciò l'errore, anzi il comportamento, che non ti aspetti). Fossi che so, in Vb allora il linguaggio "aggiusta" le cose.
Prova anche solo:
a forzare un float (con le virgole): intVar = (234.0142) / 250;
oppure a forzare long: intVar = (234L
142) / 250;
oppure un cast: intVar = ( long(234)*142 ) / 250;

Grazie carissimi.
Avete detto giusto: e' un comportamento che non mi aspetto. Ho esperienza pluriennale di programmazione in altri linguaggi come il Clipper, poi Visual Basic e da giovane Assembly del C64 ma una soluzione cosi ...
Forse devo entrare nella filosofia di questo linguaggio.
PS ho provato a dichiarare la variabile come long pensando che avrei potuto risolvere il problema anche cosi' quindi:
long lngVar=0;
lngVar = (234*142) / 250; // aaarrggh ottengo come risultato il numero negativo !! Solo scrivendolo come

lngVar = (234L*142) / 250; // funziona perfettamente. Insomma sembra che come assegnazione della variabile
// valga piu' cio' che c'e' scritto a destra piuttosto che quello che c'e' scritto a sin. Boh

Se ne era già ampiamente parlato in QUESTO thread ... ::slight_smile:

Guglielmo

Si, beh...

Fa parte di quelle caratteristiche del linguaggio che bisognerebbe sapere
Che poi tutti ce ne dimentichiamo, o prima o dopo

gianfard:
// valga piu' cio' che c'e' scritto a destra piuttosto che quello che c'e' scritto a sin. Boh

Si, il linguaggio C è molto rigido. Prende molto per buono quello che scrivi, senza fare ulteriori analisi/ipotesi.

gpb01:
Se ne era già ampiamente parlato in QUESTO thread ... ::slight_smile:

Guglielmo

Grazie moltissime, visto, ma piu' leggo su questo argomento piu' lo trovo assurdo. Ne prendo atto, ma lo trovo assurdo
Per i principianti come me ho fatto un programmino di raccolta di queste cose assurde:

//PROVA CALCOLI
int intVar=0;
int intVar1=0;
int intVar2=0;
int intVar3=0;
long lngVar=0;
long lngVar1=0;
float fltVar=0;
float fltVar1=0;
//

void setup()
{

}

void loop()
{
intVar = (234*142) / 250; 
// con la calcolatrice da' 33228/250 = 132,912
// che ci si aspetterebbe arrotondato a 132 invece
// il risultato e' -129 perche' forse la prima 
// moltiplicazione supera 32768  
intVar1=(234L*142) / 250; 
// dichiarando LONG 234 (!) il risultato e' esatto 132
intVar2 = (234.0*142) / 250;
// mettendo la virgola dopo il primo intero questo diventa 
// FLOAT e quindi il risultato e' esatto 
 intVar3 = ( long(234)*142 ) / 250; 
  // facendo un cast
  lngVar = (234*142) / 250; 
  // anche usando una variabile dichiarata come LONG in partenza
  // si ottiene un numero negativo
  lngVar1 = (234L*142) / 250;
  // per ottenere un risultato esatto devo impostare come 
  // LONG anche l'intero 234
fltVar = (234*142) / 250;
// niente da fare, sempre -129 
fltVar1 = (234.0*142) / 250;
// risultato esatto con i decimali  132,912... etc etc
  
}

author=nid69ita link=msg=4682056 date=1595320027]
Si, il linguaggio C è molto rigido. Prende molto per buono quello che scrivi, senza fare ulteriori analisi/ipotesi.

Appunto, io dichiaro che una variabile e' Long, quindi tutto quello che viene assegnato ad essa il compilatore dovrebbe dichiararlo automaticamente come Long, anche se, come detto, il risultato finale della operazione (che alla fin della fiera e' quello che finisce dentro la variabile) starebbe in una variabile INT. Cerco di rifletterci.

Non devi rifletterci

Devi studiare il k&R

Non sei tu, non sono io, non siamo noi che decidiamo come sono le regole del C

Non importa quello che pensi tu, penso io, pensiamo noi

Ogni speculazione in questo senso è concettualmente errata, perché porta a credere di sapere, quando non è vero

Ci sono delle regole, le si impara, le si applica

gianfard:
Appunto, io dichiaro che una variabile e' Long, quindi tutto quello che viene assegnato ad essa il compilatore dovrebbe dichiararlo automaticamente come Long, anche se, come detto, il risultato finale della operazione (che alla fin della fiera e' quello che finisce dentro la variabile) starebbe in una variabile INT. Cerco di rifletterci.

@gianfard probabilmente non ho scritto in maniera completa, ti ripeto che il C non fa supposizioni, non va a vedere che tipo di variabile è quella a cui assegni.
Non fa questi ragionamenti. Soprattutto quando ci sono costanti dichiarate senza decimali o senza L o UL (qualificatore long, unsigned long) sono int e con quelle fa i conti. Devi "dimenticare" il Vb ;D

se tu fai:
long longVar=234;
intVar3 = ( longVar *142 ) / 250;
che risultato ti da ??

Standardoil:
Non devi rifletterci

Devi studiare il k&R

Non sei tu, non sono io, non siamo noi che decidiamo come sono le regole del C

Non importa quello che pensi tu, penso io, pensiamo noi

Ogni speculazione in questo senso è concettualmente errata, perché porta a credere di sapere, quando non è vero

Ci sono delle regole, le si impara, le si applica

+1

Concordo al 100% su ogni singola affermazione!

Guglielmo

gianfard:
Cerco di rifletterci.

Come ha detto pure Standardoil, non c'è da riflettere, c'è solo da capire come funzion la cosa. Generalmente se hai operazioni in cui ci sono tipi numerici diversi, usa il formato più "ampio", ad esempio un "long" moltiplicato per un "int" dà un "long".

Quindi, per riepilogare prendendo alcuni dei tuoi esempi, se tu scrivi:

intVar = (234*142) / 250;

il compilatore iniza a valutare il contenuto, e dato che ci sono delle costanti intere lo interpreta così:

intVar = (int)( (int)( (int)234*(int)142 ) / (int)250 );

Ossia calcola per primo il valore tra parentesi come prodotto di interi quindi essendo un risultato necessariamente intero per via della mancanza di cast espliciti, va in overflow e ti sballa come hai già capito.

Quando invece fai:

intVar1=(234L*142) / 250;

che equivale esattamente a:

intVar3 = ( long(234)*142 ) / 250;

l'interpretazione diventa:

intVar = (int)( (long)((long)234*(int)142) / (int)250 );

e quindi in questo caso non "sballa" perché valuta 234*142 come prodotto di long per int cn risultato long (il tipo "più ampio").

Lasciando stare i float (se stai gestendo valori interi non ti conviene passare per i float che avendo solo una determinata precisione non mantengono sempre tutte le cifre), quando poi fai questo:

lngVar = (234*142) / 250;

la situazione non migliora perché viene valutato sempre allo stesso modo del primo, con l'unica diferenza che il valore finale "int" viene convertito in long:

lngVar = (long)( (int)((int)234*(int)142) / (int)250 );

Per cui devi far "capire" al compilatore che deve usare il tipo "long", il che significa che in questo caso la soluzione ideale, sapendo che comunque il risultato deve essere un "int", è:

int intVar1=(234L*142) / 250;

Se invece c'è il rischio che sia un long ti basta assegnarlo ad una variabile long:

long lngVar1=(234L*142) / 250;

E, per finire, se sai che devono essere valori positivi usa sempre "unsigned int" e "unsigned long".

Io invece non sono d'accordo. Chi arriva da linguaggi come il Vb od altri linguaggi ad alto livello, ha difficoltà a capire le regole del C.

Che sia chiaro dico la mia non perché non siete stati chiari.

A me il compilatore mi dice questo:

I../../../../.avrspecs/mkspecs/linux-avr8-gcc -o main.o main.c
main.c: In function 'main':
main.c:279:22: warning: integer overflow in expression of type 'int' results in '-32308' [-Woverflow]
  279 |     int intVar = (234*142) / 250;
      |                      ^

Me lo dice dopo la compilazione, questo vuole dire anche che esegue operazioni sulle costanti numeriche conosciute al tempo della compilazione, questo vuole dire che al tempo della esecuzione non verrà generato codice per eseguire la operazione.

Questo mi basta per capire che un int non è grande abbastanza. Di quando è andato in overflow
di 33228−32768 = 460.

Il C punta verso l'efficienza e quindi usa le minori risorse possibili, se il compilatore fosse più sgamato (ma anche fuori standard) anziché usare il tipo int per 234 e 142 potrebbe usare ulong, tanto non c'è overhead a run-time poiché tutto viene fatto a compile-time.

Altra cosa importante. Perché il C dovrebbe fare deduzioni su ciò che io da programmatore scrivo?
Detto in altri termini, io lo so che c'è overflow e lo sfrutto. Accetto volentieri che il compilatore emetta dei warning e mi da anche la possibilità di disabilitarli.

Come lo potrei sfruttare: voglio generare un pattern binario, mi serve proprio 1000000111001100
vedi qui online.

Ciao.

Mamma mia, ragazzi, mi avete fatto venire il mal di testa :slight_smile:
A parte le battute vi devo ringraziare perche' senza di voi sarei ancora in alto mare, anzi gia' affogato tra i flutti :-).
Invece ho capito tutto : "mai usare le operazioni matematiche programmando Arduino" :-))

A parte le battute e diventando un po' serio, per quanto mi sia possibile, penso che sarebbe interessante vedere come il processore tratti una operazione come quella descritta.
Io quando ero bambino e giocavo con l'assembly del C64 non avevo tutte queste difficolta' di comprensione, perche' vedevo esattamente come il processore trattava i dati : trovavo piu' semplice l'assembly che il basic.

Ora prendendo lo spunto da una esempio fatto da voi

"long longVar=234;
intVar3 = ( longVar *142 ) / 250;
che risultato ti da ?? "

Qui concordo che deve funzionare perche' io "forzo" negli operandi un valore long e quindi deve il valore che ci si aspetterebbe.
Ma se uso solo operandi INT perche' va in overflow ? Ritornando all'assembly io penso che il processore dovrebbe memorizzare i valori provvisori intermedi delle operazioni in zone di memoria a 32 bit ed alla fine il risultato che sara' INT o Byte avra' la lunghezza impostata per il suo tipo e memorizzata in uscita in una variabile opportuna, piu' piccola possibile. Ai fini del risparmio di risorse e' quello che ci interessa, mentre le zone provvisorie possono essere riutilizzate molte volte. Se il processore memorizza i valori intermedi nelle stesse zone di memoria occupate dagli operandi ecco che si crea in patatrac di cui stiamo parlando, e sbaglia il processore :-)).

Altro consiglio:

"Generalmente se hai operazioni in cui ci sono tipi numerici diversi, usa il formato più "ampio", ad esempio un "long" moltiplicato per un "int" dà un "long"."

Questo mi sembra un buon consiglio pratico anche se i puristi potrebbero non essere d'accordo. Questo consiglio sarebbe da dare a tutti i principianti

Altra frase :

"Il C punta verso l'efficienza e quindi usa le minori risorse possibili"

. sono covinto anche io di questo, ma quest filosofia cozza con il fatto che io sia costretto a riservare una zona di memoria come LONG quando ci devo memorizzare un dato INT.
Grazie carissimi stanotte ci riflettero'.

zona di memoria come LONG quando ci devo memorizzare un dato INT.
Grazie carissimi stanotte ci riflettero'.

No, il tipo di dato per intVar può rimanere int se il risultato della operazione richiede più bit viene troncato.

Io quando ero bambino e giocavo con l'assembly del C64 non avevo tutte queste difficolta' di comprensione, perche' vedevo esattamente come il processore trattava i dati : trovavo piu' semplice l'assembly che il basic.

Ok, poi ti posto il codice assembly, ma al posto delle costanti letterali ci devo mettere le variabili.

PS: ricorda che la cpu avr atmega è ad 8-bit, con puntatori lunghi 16-bit.

Ciao.

gianfard:
Ma se uso solo operandi INT perche' va in overflow ?

234*142 da 33228 in caso di variabile 16 bit unsigned, e -32308 in caso di variabile 16 bit signed (che poi diviso per 250 da il famoso -129).

Chi arriva da linguaggi come il Vb od altri linguaggi ad alto livello, ha difficoltà a capire le regole del C.

Io sono uno di quelli ma francamente preferisco queste regole del C perché mi danno la sensazione di controllare meglio le situazioni a discapito di una maggiore concentrazione.

gianfard:
Io quando ero bambino e giocavo con l'assembly del C64 non avevo tutte queste difficolta' di comprensione, perche' vedevo esattamente come il processore trattava i dati : trovavo piu' semplice l'assembly che il basic.

Ma se uso solo operandi INT perche' va in overflow ? Ritornando all'assembly io penso che il processore dovrebbe memorizzare i valori provvisori intermedi delle operazioni in zone di memoria a 32 bit ed alla fine il risultato che sara' ....

Non mi dire che sul C64 avevi registri a 32bit...

gianfard:
e sbaglia il processore

Ma dai... veramente?

gianfard:
Grazie carissimi stanotte ci riflettero'.

Spendi meglio il tuo tempo: studia
che' a riflettere ci hai provato e hai ottenuto risultati che "vabbe' dai, ritiro"

Risultati trascurabili ed errati