Newsgroup: it.comp.lang.c
Titolo: Espressioni condizionali: cosa mi sfugge?

B. sera alla comunita' ... che non frequento da anni! (e spero di non
sbagliare anche su questo! ... ;) 
Sarei grato a chi volesse aiutarmi a trovare l'errore in questa parte di
un piccolo programma di prova in linguaggio C (che ho ripreso in esame
dopo alcuni anni di abbandono completo). 
La compilazione del programmma (con gcc v. 3.3.5, su GNU/Linux Debian
Sarge) non da' alcun errore ma, di fatto, nel caso particolare
delle espressioni condizionali che qui riporto, ottengo un risultato
che mi lascia perplesso; ecco che succede eseguendo il codice: 
------------------ $ ./prova_eq ------------
Inserire valore dei coefficienti 'a' e 'b', del termine noto, 'c': 
N.B.: 'Enter' o 'Spazio' o 'Tab' dopo ogni input.('E/e' per uscire.)
[inserisco -2 4 0]
delta = 16.000000
divpos= -0.00
divn = 2.00
1.0La prima radice e': x = 2.00
1.1La prima radice e': x = -0.00
1.2La prima radice e': x = -0.00
--------------------------
Dall'esame del codice sotto riportato si vede che viene 'saltata' la
seconda condizione e non riesco a trovarne il motivo, stante le
scarsissime conoscenza che ho del linguaggio. 
Riporto (per ovvi motii di spazio) soltanto la parte relativa al
problema che mi assilla:
.............
     else if (delta >0) {
        [.... seguono alcune definizioni di variabili ...]
        /* conditional expressions con 'condit ? expr_iftrue, expr_iffalse' */
        // ((-b+rdelta) == 0) ? printf("1.0La prima radice e': x = %.2f\n", 0):
1^ ->  if (delta >0 && (-b+rdelta) == 0) {
          printf("1.0La prima radice e': x = %.2f\n",0);
          printf("1.1La prima radice e': x = %.2f\n",(-b+rdelta)/(2*a));
          printf("1.2La prima radice e': x = %.2f\n",divp);
       }
 
       // ((-b-rdelta) == 0) ? printf("2.0La seconda radice e': x = %.2f\n", 0):
2^ ->  else if (delta >0 && (-b-rdelta) == 0)  {
          printf("2.0La prima radice e': x = %.2f\n",0);
          printf("2.1La seconda radice e': x = %.2f\n",(-b-rdelta)/(2*a));
          printf("2.2La seconda radice e': x = %.2f\n",divn);
          }
       }	
     else  {			// ossia delta e' negativo
   printf("L'equazione non ammette soluzioni reali poiche' delta e' negativo!");
     }
   }   
----------------
N.B. Naturalmente alcune righe sono ridondanti (le ho usate per
'vedere' cosa succedeva, visto che con le istruzioni originarie (cond ?
,iftrue,iffalse) dava dei valori strani.
Inutile dire che ho provato alcune varianti degli 'if' ma il risultato
e' sempre lo stesso. Forse sarebbe piu' giusto usare uno 'switch (var) {
case ..}' ma un primo tentativo e' fallito miseramente, perche' non ho
degli interi da passare come (var) (se ho capito bene come funziona
switch).
Saluti,
	Ennio.
-- 
[Perche' usare Win$ozz (dico io) se ..."anche uno sciocco sa farlo.    \\?//
 Fa' qualche cosa di cui non sei capace!"   (diceva Henry Miller) ]    (°|°)
 Ennio. (Please change . for .dot. and @ for .at. in my Reply-To)       )=(

 dopo alcuni anni di abbandono completo). 
Sarebbe meglio che tu postassi il codice completo, perche'
cosi' non capisco neanche se il codice calcola correttamente
quello che vuoi calcolare.
Comunque un grave errore di programmazione (non so se
e' quello che causa il malfunzionamento, ma potrebbe essere)
lo vedo:
fai un test di uguaglianza su numeri float o double.
In particolare vedo che fai un test del tipo
(-b + rdelta)==0
Questa cosa e' assolutamente da evitare.
Il motivo e' che quando il calcolatore rappresenta
i numeri "con la virgola", allora commette quasi
sempre degli errori. Questo e' dovuto al fatto
che la rappresentazione e' binaria, e che in rappresentazione
binaria anche dei numeri "innocenti" in base 10 come
2.7 diventano dei numeri binari periodici e quindi, venendo
rappresentati con un numero inevitabilmente finito di bit,
non sono rappresentati "esattamente". Ad esempio, 2.7
nella memoria del calcolatore potrebbe essere qualcosa tipo:
2.700000000000000033535 oppure qualcosa tipo
2.699999999999999966323 (solo per darti l'idea).
Naturalmente i numeri che possono essere espressi come
somme di potenze positive o negative di 2, ad esepio 3.125 =
2^1+2^0+2^(-3) saranno invece rapprsentati esattamente
(entro certi limiti).
Quindi capisci bene che gia' in partenza i numeri float o
double non sono esatti e che quando vai a farci delle operazioni
gli errori di rappresentazione tipicamente possono anche
amplificarsi.
Quindi se ad esempio il valore double x e' ottenuto come risultato
di una serie di calcoli, e' piuttosto improbabile che possa
valere esattamente 0. Anche se teoricamente fosse 0,
come numero di macchina sara' piu' probabilmente
qualcosa di molto vicino a 0, tipo 0.00000000000000014482.
Quando vai a testare l'uguaglianza x==0 questo ti dice che
x e' diverso da 0 come invece ti aspetti.
Il bello di questi errori e' che le cose ti possono
anche andare bene 100 volte e poi per un certo
valore l'approssimazione va nell'altro senso e ottieni
il risultato sbagliato. Oppure puoi facilmente costruire
un programma che a seconda delle opzioni di ottmizzazione
fornisce risultati diversi (perche' tratta diversamente
le operazioni in floating point).
Riassumendo: a meno che uno non sappia molto bene
cosa sta facendo, il test di uguaglianza tra numeri
in floating point e' da evitare.
In generale, per vedere se due numeri floating point
sono "uguali", tipo x e y, invece di x==y
devi fare una cosa tipo
fabs(x-y)<Eps
dove fabs e' una funzione definita in math.h che calcola
banalmente il valore assoluto ed Eps e' una costante
piccola che andra' dimensionata in base al tipo di calcoli
che fai e in base alla precisione di macchina.
In altre parole per te due numeri saranno uguali se
la loro differenza, in valore assoluto, non eccede Eps.
ciao,
v.

  [...]
Hmm ... cominciavo a sospettare che potesse dipendere dal tipo di
rappresentazione numerica usato, ma non mi era finora capitato (o
non me ne ricordo) di leggere nulla di cosi' preciso in proposito.
Intanto ti ringrazio molto della cortese dettagliata spiegazione,
ripromettendomi di metterti al corrente degli sviluppi e, se necessario,
di postare il codice completo (molto banale tentativo di trovare le radici
di una equazione di 2° grado, finalizzato principalmente
all'apprendimento di almeno le funzioni piu' elementari del C.
Grazie di nuovo e a presto :-)


  Ennio-(Sr) wrote:
[big cut]
Dopo vari ed infruttuosi tentativi, fatti inglobando la soluzione da te
suggerita, accetto l'invito di inviare il codice completo,
considerato che solo chi abbia dimestichezza con il linguaggio puo'
scovare l'errore. Nella mia ignoranza, sono portato a concludere
che il compilatore non riesca a distinguere la piccola
differenza tra le due condizioni e che, quindi, salti la seconda,
ritenendo di averla gia` valutata.
Ma saro` naturalmente lieto di essere smentito, altrimenti vorrebbe dire
che il gcc e` bacato.
Resto in trepida attesa.
Saluti,
	Ennio.
###############
N.B. Ho preferito non cancellare le righe di commento per assicurare che
la verifica si faccia esattamente sulla stessa base.
------------------------- pr_eq2.c --------------------
#include <stdio.h>
#include <math.h> 
#include <printf.h>
#ifndef FALSE
#define FALSE	0
#endif
#ifndef TRUE
#define TRUE	1
#endif
#define MAXCHAR 	64 /* size if input buffer for fgets() */
void	report (double);
// int ff_divpow();
// long stdiv[50];
long w,z;
/* -------------------------------------------- */
int main ( double argc, char **argv ) {
/* E' molto importante definire correttamente i tipi di numeri: */	
double	x,y,a,b,c; 	    /* numbers to be examined */
double delta,rdelta,quot;
long pird;
float divp, divn, vd, dird;
char inbuff [MAXCHAR];	    /* text input buffer */
float Eps; ///////////
Eps=0.05; /// > conflicting type of Eps ????????????
for (;;) {
   inizio:
   printf("\n\nInserire valore dei coefficienti 'a' e 'b', del termine noto, 'c': \n");
   printf("N.B.: 'Enter' o 'Spazio' o 'Tab' dopo ogni input.");
   printf ("('E/e' per uscire.)\n\n");
   scanf("%lf\t%lf\t%lf",&a,&b,&c);
   if (a==0) {
      printf("Il coefficiente di xý non puo' essere nullo");
      goto inizio; 	// break; return; return(0) escono dal programma
   }
   delta = (b*b)-(4*a*c);		// calcola il valore di delta
   printf("delta = %f\n", delta);	// lo stampa a schermo
   rdelta = sqrt(delta);		// calcola la radice di delta
   pird = floor(rdelta);	// calcola la parte intera della radice
   dird = rdelta-pird;		// differenza tra radice e sua parte intera
   if (dird == 0) { 		// vuol dire che la radice e' un intero
      if (delta <0) {
         printf("L'equazione non ammette radici reali!");
      }
/* da rivedere (tolta la funzione ff_divpow) */
      if (delta == 0) {
         if (b !=0 ) {	   
//         w=b,printf("L'equazione ha 'radice doppia:' x = %.2f/%.2f\n",ff_divpow(w),ff_divpow(2*a)            ); 
	 }
         else  {
	    printf("L'equazione e' indeterminata (radice di zero!) \n");
         }
      }
 
      else if (delta >0) {
         rdelta = sqrt(delta);
	 printf("val. di -b+pird = %.40f\n", -b+pird);
	 printf("val. di -b-pird = %.40f\n", -b-pird);
         float divp=(-b+pird)/(2*a);
	 float divn=(-b-pird)/(2*a);
         printf("divpos= %.2f\n",divp);	
         printf("divn = %.2f\n",divn);
         /* conditional expressions con 'condit ? expr_iftrue, expr_iffalse' */
         // ((-b+pird) == 0) ? printf("1.0La prima radice e': x = %.2f\n", 0):
//       printf("val ass fabs = %.40f\n", fabs((-b+pird)));
         if (delta >0 && fabs((-b+pird) - 0) <Eps) {
//	       printf("1.0La prima radice e': x = %.2f\n",0);
//	       printf("1.1La prima radice e': x = %.2f\n",(-b+rdelta)/(2*a));
//	       printf("1.2La prima radice e': x = %.2f\n",divp);
printf("sono nella prima");	       
//             printf("val ass fabs = %.40f\n", fabs((-b-pird)));
	    }
//Sostituisco a else if il semplice if:	 
         // ((-b-pird) == 0) ? printf("2.0La seconda radice e': x = %.2f\n", 0):
         if (delta >0 && fabs((-b-pird) - 0) <Eps)  {
//	       printf("2.0La prima radice e': x = %.2f\n",0);
//	       printf("2.1La seconda radice e': x = %.2f\n",(-b-rdelta)/(2*a));
//	       printf("2.2La seconda radice e': x = %.2f\n",divn);
printf("sono nella seconda");	       
	    }
         }	
      else  {			// ossia delta e' negativo
         printf("L'equazione non ammette soluzioni reali poiche' delta e' negativo!");
      }
   }   
 
fflush (stdout);
fgets (inbuff, sizeof (inbuff), stdin);
if (*inbuff == 'E' || *inbuff == 'e') 
   break;
}  // fine del for
}  // fine della main
/* -------------------------------------------- */


 che il gcc e` bacato.
Con una probabilitita' 1000000000:1 e' il tuo programma ad essere bacato.
non prendere le seguenti considerazioni come offese personali....
Il tuo programma contiene diverse cose sospette, per esempio
il fatto che usi sia i float che i double (l'uso dei float
e' da sconsigliare), il fatto che ridefinisci
delle variabili, come float divp e divn,
e che non dovrebbe neanche compilare visto
che all'inizio c'e'
una inutile e incomprensibile
#include<printf.h>
che su tutti i compilatori che conosco dovrebbe dare errore.
La funzione printf e' definita gia' dentro
stdio.h
Contiene sicuramente anche degli errori dal punto di
vista matematico, come quando ad un certo punto scrivi
"L'equazione e' indeterminata (radice di zero!) \n"
guarda che la radice di 0 e' determinatissima e vale 0....
Poi sinceramente non capisco tutto l'ambaradan che
fai con la parte intera. Cosa c'incastrano le parti
intere con la soluzione di una banalissima equazione
di secondo grado? Non riuscendo a capire cosa vorresti
fare non posso neanche provare a trovare l'errore...
Poi il valore di Eps messo a 0.05 e' veramente troppo
grande...
Poi il numero degli if else che metti e' eccessivo
e rende la struttura del programma inutilmente
intricata.
Quel goto che usi e' inutile, visto che ti trovi
all'interno di un ciclo basta che fai un "continue"
per ritornare all'inizio del ciclo.
In fondo al main ci dovresti mettere un return 0;
Ti scrivo come dovrebbe essere il ciclo interno del tuo
programma
(le variabili da dichiarare sono solo:
double a,b,c,x1,x2,D,r;
double epsilon = 1.0e-9;
)
/// suppongo di aver gia' letto a b e c
printf("a=%.5f b=%.5f c=%.5f\n",a,b,c);
if (fabs(a)<epsilon) {printf("a troppo piccolo\n"); continue;}
D = b*b-4*a*c;
printf("D = %.10f\n",D);
if (D < -epsilon)
{
   printf("Delta negativo: non ci sono soluzioni reali\n");
}
else
if (D > epsilon)
{
   printf("Delta positivo: due soluzioni distinte\n");
   r=sqrt(D);
   x1 = (-b+r)/(2*a);  x2=(-b-r)/(2*a);
   printf("x1 = %.5f\nx2 = %.5f\n",x1,x2);
}
else
/* in questo caso D e' compreso tra -epsilon e +epsilon
    quindi e' molto piccolo e assumiamo che sia uguale a 0 */
{
   printf("Delta uguale a 0: due soluzioni coincidenti\n");
   x1 = -b/(2*a);
   printf("x1 = x2 = %.5f\n",x1);
}
fine,
ciao,
v.

  [...] 
Per il resto provero' a fare tesoro dei tuoi suggerimenti.
Mi scuso degli errori grossolani, imputabili alla voglia di fare, in
fretta, senza la base di un corso di studi regolari. Ho finora solo
sbirciato sui tutor on line di Steve Summit e sulla FAQ.
Ti ringrazio di nuovo della cortese collaborazione.


 che fai e in base alla precisione di macchina.
In <float.h> sono definite le costanti FLT_EPSILON e DBL_EPSILON che
servono proprio a questo scopo.
Luca
-- 
Home: 
http://kronoz.cjb.net
Porc i' mond che cio' sott i piedi!
V. Catozzo

 servono proprio a questo scopo.
Queste costanti sono comode, ma c'e' il rischio che, per esempio a causa
dell'amplificazione dell'errore dovuto ad una lunga serie di
operazioni, queste costanti siano troppo piccole per l'uso che
uno vuol farne. Quindi un po' di analisi dell'errore
e' meglio farla, se si e' nel dubbio.
ciao,
v.

  [ ... ]
[Giusto per la cronaca, fermo restando quanto scritto nel mio
precedente messaggio]
In effetti c'era un errore logico nel contenuto di quei due 'if' che mi
ostinavo a pensare che il programma dovesse riconoscere, nonche` una
errata rappresentazione per il valore '0' [usavo %f anziche' %d]; e la cosa,
tutto sommato, mi fa piacere, perche' vuol dire che le varie condizioni,
per quanto numerose possano essere, funzionano perfettamente bene quando
e` corretta la logica sottostante ... :-)
Grazie di nuovo,
	Ennio.
P.S. Qualcuno puo' suggerirmi una buona lettura sul 'C' oltre alle FAQ e
al tutor di Steve Summit? In modo particolare mi interessano esempi che
prevedano il salvataggio di dati in un database: vorrei compilare in C
dei vecchi programmi fatti mooooolti anni fa in Clipper.
Please: solo proposte incoraggianti ... ;)
