Newsgroup: it.comp.lang.c
Titolo: valori anomali

salve,
il codice che segua ha due anomalie:
1)giorni fa scrissi un post in cui affermavo che, per visualizzare
correttamente gli interi
grandi, bastava convertirli in "double", e stamparli con "%e" nella
printf().
Nessuno mi ha smentito.
Oggi però mi sono accorto che ciò non è corretto.
Il codice che segua conferma ciò. In esso ottengo i valori corretti
impiegando "%d" e non "%e".
Perchè?
Considerato che io non riesco ad impiegare il debugger nel mio computer,
perchè mi si inchioda il
pc, per me visualizzare i valori corretti con le printf è utile per testare
il codice C.
Come posso allora capire a priori quando un intero vadi stampato con "%d" o
con "%e"?
2)
Il codice ha un'altra anomalia.
I valori assegnati alle variabili devono creare d_varianza<0. (fate i
calcoli con la
calcolatrice).
Eppure viene stampato: "d_varianza è positiva".
Come si spiega?
****************************************************************************
***
 #include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <stdint.h>
/*
se eseguo questo programma, allora ottengo:
con %d d_varianza=-1.7*10^9 (OK)
con %e d_varianza=2.6*10^9 (KO)
Come mai?
*/
int main ()
{
int64_t d_varianza=646000000; //d_varianza=6.46*10^8
int16_t somma16;
somma16=1814;
d_varianza=d_varianza-somma16*1310720;
printf("con d vale: d_varianza=%d", d_varianza); getchar();
printf("con e vale: d_varianza=%e", (double)d_varianza); getchar();
/*
Il problema non è insito nel calcolo di somma16*1310720. Infatti si ottiene:
con %d somma16*1310720=-1.7*10^9 (OK)
con %e somma16*1310720=-1.7*10^9 (OK)
*/
printf("con d vale: somma16*1310720 vale: =%d", somma16*1310720); getchar();
printf("con e vale: somma16*1310720 vale: =%e", (double) (somma16*1310720));
getchar();
if(d_varianza<0)
{
printf("d_varianza è negativa"); getchar();
}
else
{
printf("d_varianza è positiva"); getchar();
}
}
****************************************************************************
*
grazie,
adriano

 con "%e"?
Cosa usare nella printf() dipende dal tipo della variabile, non dal suo 
valore. "%e" stampa un double in formato esponenziale, "%d" un int (con 
segno, quindi) e "%u" un unsigned int.
Nel tuo esempio d_varianza è un int64_t che, molto probabilmente, è 
definito con un long long int.
Se fosse cosi' (verfica sul tuo compilatore) dovresti usare "%lld".
Se usi gcc c'e' un flag che abilita dei warning specifici sui formati 
-Wformat (che e' tra quelli attivati con -Wall). Molto utile.
R.D


Impiegando nel mio codice iniziale, nella printf: %lld, ottengo gli stessi
esiti: di %ld e di %d.
Mistero! Pare che, nel mio computer, impiegare: %ld, %d, %lld non faccia mai
la differenza.
passiamo ora all'altro problema citato nel mio post iniziale.
Rimane il problema che il C esegue dei calcoli dall'esito anomalo, argomento
su cui nessuno mi ha
risposto ancora (e ciò indipendentemente da quale tipologia di  printf venga
impiegata).
Per capirci qualcosa in più allora ho scritto il codice che segua
(in cui non c'è la problematica di come il valore venga passato alla
printf):
- nei primi 4 metodi eseguo gli stessi
semplici calcoli matematici, codificandoli in C in 4 maniere diverse.
Ottengo gli esiti corretti,
eccetto che con il 2° metodo. Perchè? Capita lo stesso anche con i vostri
compilatori (io impiego
 il dev c++) ?
-nel 5° metodo eseguo un calcolo diverso ma analogo, per fare vedere che i
problemi nascono solo
per certi valori delle variabili.
Ecco il codice C:
****************************************************************************
************
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <stdint.h>
int main ()
{
int64_t appoggio, d_varianza, mol64;
int16_t somma16;
/*
1° METODO DI SVOLGERE  I CALCOLI
Esso fornisce l'esito corretto: negativo.
*/
printf("con il 1° metodo si ottiene:\n"); getchar();
somma16=1800;
mol64=1300000;
d_varianza=-somma16*mol64;
if(d_varianza<0)
{
printf("d_varianza è negativa\n"); getchar();
}
else
{
printf("d_varianza è positiva\n"); getchar();
}
////////////////////////////////////////////////////////////////////////////
///
/*
2° METODO DI SVOLGERE  I CALCOLI
Esso fornisce l'esito errato: positivo.
E' ovvio che si dovrebbe pervenire a: "d_varianza
è negativa", visto che si sottrae a 0, il valore >0. Invece non è così.
perchè?
*/
printf("con il 2° metodo si ottiene:\n"); getchar();
d_varianza=0;
somma16=1800;
d_varianza=d_varianza-somma16*1300000;
if(d_varianza<0)
{
printf("d_varianza è negativa\n"); getchar();
}
else
{
printf("d_varianza è positiva\n"); getchar();
}
////////////////////////////////////////////////////////////////////////////
///
/*
3° METODO DI SVOLGERE  I CALCOLI
Esso fornisce l'esito corretto: negativo.
Nel 3° metodo vengono svolti gli stessi passaggi matematici del 2° metodo,
ma ora
si impiega la variabile: appoggio.
*/
printf("con il 3° metodo si ottiene:\n"); getchar();
d_varianza=0;
somma16=1800;
mol64=1300000;
appoggio=mol64*somma16;
d_varianza=d_varianza-appoggio;
if(d_varianza<0)
{
printf("d_varianza è negativa\n"); getchar();
}
else
{
printf("d_varianza è positiva\n"); getchar();
}
////////////////////////////////////////////////////////////////////////////
///
/*
4° METODO DI SVOLGERE  I CALCOLI
Esso fornisce l'esito corretto: negativo.
Nel 4° metodo vengono svolti gli stessi passaggi matematici del 2° metodo,
ma ora,
le variabili vengono inizializzate partendo dai valori posti in notzione
esponenziale.
*/
printf("con il 4° metodo si ottiene:\n"); getchar();
d_varianza=0;
somma16=(int16_t)1.8e3;
appoggio=somma16*(int64_t)1.3e6; //appoggio=2e9
d_varianza=d_varianza-appoggio;
if(d_varianza<0)
{
printf("d_varianza è negativa\n"); getchar();
}
else
{
printf("d_varianza è positiva\n"); getchar();
}
////////////////////////////////////////////////////////////////////////////
///
/*
5° METODO DI SVOLGERE  I CALCOLI
Esso fornisce l'esito corretto: negativo.
Nel 5° metodo vengono svolti gli stessi passaggi matematici del 2° metodo,
ma ora,
anzichè moltiplicare per 1300000 (1.3e6), si moltiplica per 130000 (1.3e5).
*/
printf("con il 5° metodo si ottiene:\n"); getchar();
d_varianza=0;
somma16=1800;
d_varianza=d_varianza-somma16*130000;
if(d_varianza<0)
{
printf("d_varianza è negativa\n"); getchar();
}
else
{
printf("d_varianza è positiva\n"); getchar();
}
}
****************************************************************************
************
grazie a chiunque saprà darmi la spiegazione di questa anomalia.
ciao,
adriano

 grazie a chiunque saprà darmi la spiegazione di questa anomalia.
in pratica sei stato vittima di una conversione implicita ad int.
RemoD

 salve,
Ma non ce l'ha un manuale il tuo compilatore?
Li trovi la risposta a tutto quello che chiedi.

 d_varianza=d_varianza-somma16*1300000;
somma16 è uno short, 1300000 un long int.
Il risultato di somma16*1300000 è un long int che vale -1954967296
perche' in 31 bit 2340000000 non ci sta e sei andato in overflow. Quindi
d_varianza vale 1954967296.
///////////////////////////////////////////////
Nella espressione:
d_varianza=d_varianza-somma16*1300000;
1300000 viene memorizzata in long int (32 bit)
somma16 viene promossa ad long int (32 bit)
somma16*1300000 viene memorizzata in long int
Solo all'atto dell'assegnamento "=", la variabile interna:
appoggio=somma16*1300000, viene
promossa da long int a long long int, e cioè da 32 a 64 bit nel mio
computer.
Stando a ciò che dice Schildt, ogni variabile della espressione dovrebbe
essere promossa a long
long int (64 bit), perchè questo è il tipo dell'operando di estensione
maggiore che compaia nella
espressione.
Invece sia Remo, che l'esito dei calcoli ottenuti eseguendo il codice,
confermano che ogni
calcolo intermedio avvenga a 32 bit (long int), e non a 64 bit (long long
int).
Inoltre Schildt afferma:  "tutti i char e gli short int vengano convertiti
automaticamente in int.
Questo processo si chiama promozione intera".
Per verificare se ciò sia corretto, ho scritto il neocodice che segua, in
cui verifico se le cose
stiano davvero così, e cioè se, in ogni caso, anche quando non ce ne sia il
bisogno, i tipi
uint8_t ed uint16_t vengano promossi ad uint32_t, prima di essere impiegati
nelle espressioni
matematiche (nel mio computer un intero è a 32 bit).
Ebbene, ottengo qualche anomalia.
****************************************************************************
*******************
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <stdint.h>
int main ()
{
uint64_t appoggio, d_varianza, mol64, t64;
uint32_t z32;
uint16_t y16, somma16 ;
uint8_t x8, u8;
printf("n byte tipo char=%u\n", sizeof(char)); getchar();
printf("n byte tipo short int=%u\n", sizeof(short int)); getchar();
printf("n byte tipo int=%u\n", sizeof(int)); getchar();
printf("n byte tipo long int=%u\n", sizeof(long int)); getchar();
printf("n byte tipo long long int=%u\n", sizeof(long long int)); getchar();
/*
1° ESEMPIO
Esso fornisce l'esito corretto.
Infatti:
- x8 viene promosso da char ad int(32 bit)
- 1 viene inserito in un intero(32 bit) anche se basterebbero 8 bit
- x8+1=255+1=256 è contenibile in 32 bit (non c'è l'overflow)
- y16=256 assegna il valore 256 memorizzato a 32 bit, alla variabile y16
promossa ad
  int
- dei 32 bit di y16 promossa ad int, si troncano i 16 bit più significativi
al fine
  di rimanere con soli 16 bit. Ciò non crea la perdita di bit significativi
per
  questo caso.
Pertanto l'esito è corretto.
*/
printf("con il 1° metodo si ottiene:\n"); getchar();
x8=255;
y16=x8+1;
printf("y16=%u\n", y16); getchar();
////////////////////////////////////////////////////////////////////////////
///
/*
2° ESEMPIO
Esso fornisce l'esito corretto.
Infatti:
- x8 viene promosso ad int(32 bit)
- y16 iene promosso ad int(32 bit)
- y16+x8=255+65535=65790 è contenibile in 32 bit (non c'è l'overflow).
Ora nella variabile  z32 promossa ad int( in questo caso è già int perchè
nel mio
computer 1 int occupa 32 bit) viene messo il valore contenuto in 32 bit:
65790.
Eliminando la promozione ad int, la variabile z32 rimane identica.
Pertanto l'esito è corretto.
*/
printf("con il 2° metodo si ottiene:\n"); getchar();
x8=255;
y16=65535;
z32=y16+x8;
printf("z32=%u\n", z32); getchar();
////////////////////////////////////////////////////////////////////////////
///
/*
3° ESEMPIO
Esso fornisce l'esito errato. Però le mie previsioni forniscono il valore
diverso
da quello calcolato dal computer.
Infatti ipotizziamo che il compilatore decida di eseguire t64=z32+y16+x8,
calcolando
prima x8+x16=appoggio32 e poi appoggio32+z32 (appoggio32 è un variabile
interna di
tipo int impiegata dal compilatore che non compare esplicita nel mio codice
C).
Nel calcolare: x8+y16 avviene che:
- x8 viene promosso ad int(32 bit)
- y16 viene promosso ad int(32 bit)
- y16+x8=255+65535=65790 è contenibile in 32 bit (non c'è l'overflow).
Pertanto si rimane con: appoggio32=65790.
Nel calcolare: z32+appoggio32 avviene che:
65790+(uint32_t)pow(2,32)-1=65790+4294967296-1=4295033085. viene memorizzato
in 32 bit.
-> per i valori assegnati c'è l'overflow.
pertanto si rimane con il valore: 65790-1=65789. Questo valore viene
promosso a 64
bit solo all'atto dell'operazione dell'assegnazione(quando è troppo tardi e
l'overflow
è già capitato).
Pertanto in t64 viene posto il valore errato: 65789.
Invece il computer fornisce il valore : 65788.
Perchè?
*/
printf("con il 3° metodo si ottiene:\n"); getchar();
x8=255;
y16=65535;
z32=(uint32_t)pow(2,32)-1;
t64=z32+y16+x8;
printf("t64=%u\n", t64); getchar();
////////////////////////////////////////////////////////////////////////////
///
/*
4° ESEMPIO
Esso fornisce l'esito corretto.
Infatti:
- x8 viene viene promosso ad int(32 bit)
- u8 viene promosso ad int(32 bit)
- x8+u8=255+1=256 è contenibile in 32 bit  (non c'è l'overflow).
- dei 32 bit di y16 promossa ad int, si troncano i 16 bit più significativi
al fine
  di rimanere con soli 16 bit. Ciò non crea la perdita di bit significativi
per
  questo caso.
Pertanto l'esito è corretto.
*/
printf("con il 4° metodo si ottiene:\n"); getchar();
x8=255;
u8=1;
y16=x8+u8;
printf("y16=%u\n", y16); getchar();
////////////////////////////////////////////////////////////////////////////
///
/*
5° ESEMPIO
Il 5° e 6° esempio eseguono lo stesso calcolo, però le operazioni
associative
vengono svolte in ordine diverso.
L'ordine impiegato in questo esempio fornisce l'esito corretto.
Infatti prima si esegue: (t64+z32).
- z32 quando viene promosso ad uint64_t (64 bit).
- (t64+z32)=4294967295+1=4294967296=2^32 che è contenibile in 64 bit ( non
c'è l'overflow).
Si rimane con il valore: 2^32 contenuto nella variabile interna appoggio64 a
64 bit.
- quando viene eseguita l'operazione: appoggio64+y16, y16 viene promossa ad
uint64_t, e si ricava: 2^32+65535=2^32+2^16-1=4294967296+65536-1=4295032831
che è
contenibile in 64 bit (non c'è l'overflow).
- quando viene eseguita l'operazione di assegnamento, si ottiene:
4295032831.
La printf mi stampa: 65535  sia se impiego %llu, che %lu che %u, mentre mi
stampa
l'esito corretto (ma troncato nelle cifre significtive)se impiego: %e.
Pertanto l'esito è corretto (se impiego %e).
*/
printf("con il 5° metodo si ottiene:\n"); getchar();
t64=1;
y16=65535;
z32=(uint32_t)pow(2,32)-1;
//z32=4294967295;
t64=(t64+z32)+y16;
printf("t64=%LLu\n", t64); getchar();
printf("t64=%e\n", (double)t64); getchar();
////////////////////////////////////////////////////////////////////////////
///
/*
6° ESEMPIO
Il 5° e 6° esempio eseguono lo stesso calcolo, però le operazioni
associative
vengono svolte in ordine diverso.
L'ordine impiegato in questo esempio fornisce l'esito non corretto.
Infatti:
- y16 viene viene promosso ad int(32 bit)
- z32 quando viene promosso ad int(32 bit) rimane  32 bit.
- (z32+y16)=65535+4294967295=4295032830 che non è contenibile in 32 bit
(c'è l'overflow).
Si rimane con il valore: 65534 contenuto nella variabile interna appoggio32
a 32 bit.
-quando viene eseguita l'operazione t64+appoggio32, appoggio32 viene
promossa ad
 uint64_t, e si ricava: t64= 1+65534=65535 che è contenibile in 64 bit  (non
c'è
 l'overflow).
Pertanto si ricava: t64=65535 (si ottiene questo valore ponendo nella printf
sia
%e che %u).
Pertanto l'esito è non corretto.
*/
printf("con il 6° metodo si ottiene:\n"); getchar();
t64=1;
y16=65535;
//z32=4294967295;
z32=(uint32_t)pow(2,32)-1;
t64=t64+(z32+y16);
printf("t64=%u\n", t64); getchar();
printf("t64=%e\n", (double)t64); getchar();
////////////////////////////////////////////////////////////////////////////
///
/*
7° ESEMPIO
Il 7° esempio è analogo al 8°, ma si somma "1" anzichè "2".
Esso fornisce l'esito non corretto, però diverso da quello che io mi
aspetterei
che fornisca il computer.
Ecco il mio ragionamento:
- z32 quando viene promosso ad int(32 bit) rimane a 32 bit.
- il valore 1 viene inserito in una costante del tipo int (a 32 bit)
- quando si calcola: z32+1=z32=4294967295+1=4294967296=2^32, l'esito non  è
contenibile
 nei 32 bit (c'è l'overflow).
Pertanto nei 32 bit a disposizione si rimane con il valore: 0.
-quando viene eseguita l'istruzione di assegnamento, il valore 0 a 32 bit,
viene
 promosso a 64 bit.
Pertanto in t64 viene posto il valore 0.
pertanto si dovrebbe perviene all'esito errato=0.
Invece, mandandolo in esecuzione, ottengo l'esito errato: 4294967295=2^32-1.
(l'esito corretto in matematica sarebbe: 2^32=4294967296)
Perchè?
*/
printf("con il 7° metodo si ottiene:\n"); getchar();
//z32=4294967295;
z32=(uint32_t)pow(2,32)-1;
t64=z32+1;
printf("t64=%u\n", t64); getchar();
printf("t64=%e\n", (double)t64); getchar();
////////////////////////////////////////////////////////////////////////////
///
/*
8° ESEMPIO
L'8° esempio è analogo al 7°, ma si somma "2" anzichè "1".
Esso fornisce l'esito non corretto, però diverso da quello che io mi
aspetterei
che fornisca il computer.
Ecco il mio ragionamento:
- z32 quando viene promosso ad int(32 bit) rimane a 32 bit.
- il valore 2 viene inserito in una costante del tipo int (a 32 bit)
- quando si calcola: z32+1=z32=4294967295+2=4294967297=2^32+1, l'esito non è
contenibile
 nei 32 bit (c'è l'overflow).
Pertanto nei 32 bit a disposizione si rimane con il valore: 1.
-quando viene eseguita l'istruzione di assegnamento, il valore 1 a 32 bit,
viene
 promosso a 64 bit.
Pertanto in t64 viene posto il valore 1.
pertanto si dovrebbe perviene all'esito errato=1.
Invece, mandandolo in esecuzione, ottengo l'esito errato: 0.
(l'esito corretto in matematica sarebbe: 2^32=4294967297)
Perchè?
*/
printf("con il 8° metodo si ottiene:\n"); getchar();
//z32=4294967295;
z32=(uint32_t)pow(2,32)-1;
t64=z32+2;
printf("t64=%u\n", t64); getchar();
printf("t64=%e\n", (double)t64); getchar();
////////////////////////////////////////////////////////////////////////////
///
}
****************************************************************************
*******************
Ora io chiedo:
1)le spiegazioni di ciò che avvenga, inserite nei commenti, sono corrette?
2)a che serve coniare i tipi di dati: int8_t, int16_t se tanto ogni volta
che essi compaiano in
 una espressione vengano automaticamente convertiti in int (la promozione
intera)?
3)è corretto affermare che ogni valore di un espressione viene gestito come
minimo come int
 (mai come int8_t o come int16_t), e  solo se serva come long int o come
long long int.
 Cioè il compilatore converte ogni costante, variabile, valore noto, che
compaia in un espressione,
 di default,  in int, a meno che esso non basti per tenere memorizzato uno
di questi ingressi.
4)l'esito di un espressione, dipende dall'ordine con cui vengono eseguite le
operazioni dotate
della proprioetà associativa (+, *) inserite nell'espressione. Lo confermano
gli esempi n° 5 e 6
del codice.
5)perchè nel 3° e 7° esempio il mio ragionamento sbaglia a prevedere il
valore fornito dal
 computer?
6)se io eseguo (su un microcontrollore ad 8 bit) il ciclo che segue
(impiegando il tipo
 uint8_t per il  contatore "i"):
uint8_t i, v[80];
for(i=0;i<80, i++)
{
v[i]=i+1;
}
allora mi sembra che non ci sia alcuna maggiore efficienza rispetto
all'impiego del ciclo
sostitutivo (impiegando il tipo uint per il  contatore "i"):
uint8_t v[80];
uint i;
for(i=0;i<80, i++)
{
v[i]=i+1;
}
visto che nel 1° caso, sia i che v[i] vengono promossi ad interi durante il
calcolo
dell'espressione inserita nel ciclo (così dice Schildt nella frase citata
all'inizio).
E' corretto ciò?
7)io mi aspetto di ottenere t64=0 nel 6° esempio. Invece lo ottengo nel 7°
esempio.
Perchè?
grazie,
adriano

 allora mi sembra che non ci sia alcuna maggiore efficienza [...]
Qualunque considerazione di questo tipo dipende dal tuo compilatore e 
dall'architettura. Non penso sia corretto cercare "regole generali".
Remo.D

    Purtroppo non ho il libro che citi.
È un bene. Lo Schildt è piuttosto noto per essere zeppo di inesattezze e
imprecisioni. 
-- 
-riko