View Full Version : [C] Imprecisioni in lettura file e serie
salvodel
04-07-2008, 15:11
Salve a tutti, avrei un problema un po strano che non so risolvere. Ho scritto un programmino che legge da file dei numeri. Quando visualizzo questi numeri però ci sono delle discrepanze. Ad esempio:
23.5 diventa 23.499998
1.2 diventa 1.2000001
mentre altri numeri rimangono corretti. Se poi mi creo un vettore che contiene una serie anch'essa ad un certo punto sballa nel senso che se faccio
t[i]=t[i-1]+0.2tutto ok fino ad un certo punto poi ottengo numeri di questo tipo:
383
383,200012
383,399994
383,609985
383,809998
384,01001
384,209991
384,410004
Se li leggo da un file che contiene la seria numerica corretta ottengo:383,004974
383,204987
383,404999
383,605011
383,805023
384,005035
384,205048
384,40506
Il passo bene o male è sempre 0.2 ma sono numeri sballati. Perché?:confused:
Grazie a tutti per i suggerimenti.
PS
Se volete ho il listato del programma. Un ultima cosa, le variabili sono di tipo float.
wizard1993
04-07-2008, 15:25
il problema è del c stesso, esso scrive in memoria i numeri virgola mobile non come tali, ma come il numero che più vi si avvicina
il problema è del c stesso, esso scrive in memoria i numeri virgola mobile non come tali, ma come il numero che più vi si avvicina
non solo del c, ma proprio del formato a virgola mobile.
Il problema è che i numeri sono rappresentati in modo approssimato, qualche volta esatto.
banryu79
04-07-2008, 17:50
Se usi sempre tipi di dato float per storare i tuoi numeri prova a fere così:
t[i]=t[i-1]+0.2f
DanieleC88
04-07-2008, 18:34
Non è un problema di rappresentazione, credo, 23.5 ha bisogno di una sola cifra binaria dopo la virgola per essere rappresentato senza troncamenti. Come li leggi i numeri? Dev'essere lì l'errore, direi. :boh:
EDIT: però 0.2 non ha bisogno di una sola cifra... è vero, conviene usare dei double. :D
salvodel
05-07-2008, 10:55
Cerco di rispondere a tutte le considerazioni che mi avete proposto:
Con i double la situazione non cambia. Ho fatto un replace veloce ed il risultato è lo stesso.
Anche aggiungendo la f a 0.2 la situazione non cambia(banryu79 me l'avevi gia proposta per un altro problema ed era la prima cosa che ho fatto)
I numeri li leggo con fscang("%f",&bla)
Ora ricontrollo i vecchi programmi...fino ad ora non mi ero accorto di questo problema, sopratutto sulla serie che rappresenta il tempo:confused: .Mi crea un po di problemi avere istanti temporali un po sfalsati:muro: .
Grazie
uomoserio
05-07-2008, 20:13
Scusatemi, io non sono un esperto di programmazione e non voglio passare per tale, ma leggendo le vostre risposte al problema di Salvodel si potrebbe dedurre che i PC non servono a niente e si potrebbero anche buttare nel cassonetto...
Se davvero, come ha affermato qualcuno, questo problema fosse insormontabile ed intrinseco nella rappresentazione stessa dei numeri in virgola mobile, allora non si spiegherebbe l'esistenza stessa del computer, che nasce guardacaso proprio per compiere "calcoli complessi".
Finché si suppone che sia un limite del "C" allora uno potrebbe dire "bene, cambio linguaggio"....ma se si afferma che il problema è del formato stesso in virgola mobile.....allora non si potrebbe andare oltre il 2+2.
Come nascerebbero, allora, programmi di calcolo, programmi 3d, programmi astronomici, ecc ecc....?? Come sarebbero stati creati 3d Studio, Autocad, e altri software che hanno precisioni nell'ordine dei 4-5 decimali?
Insomma, che senso avrebbe usare un computer per fare dei calcoli se davvero fosse "naturale" che sbagli delle cifre anche ad un solo decimale??
Mi sembra troppo assurda come risposta, o forse sono io che non ho capito nulla di come gira il mondo.... :muro:
mmmh tutti i vostri dubbi sono di facile risoluzione conoscendo come un calcolatore rappresenta i dati al suo interno.
Per i numeri Naturali non si pone il problema in quanto basta semplicemente convertire da binario a decimale e viceversa. Per i numeri interi positivi e negativi si hanno diverse tecniche di rappresentazione ma possono sempre rappresentare il numero in modo esatto.
Con i numeri float e double la cosa è diversa.
Un numero a virgola mobile nel calcolatore viene memorizzato come n = M x 2^e.
Non serve essere matematici per trarre conclusioni quali: solo pochi numeri possono essere rappresentati esattamente, alcuni numeri non sono nemmeno rappresentabili, si hanno problemi di assorbimento (es: 2^15 +1 = 2^15 ) ecc...
Se davvero, come ha affermato qualcuno, questo problema fosse insormontabile ed intrinseco nella rappresentazione stessa dei numeri in virgola mobile, allora non si spiegherebbe l'esistenza stessa del computer, che nasce guardacaso proprio per compiere "calcoli complessi".
Finché si suppone che sia un limite del "C" allora uno potrebbe dire "bene, cambio linguaggio"....ma se si afferma che il problema è del formato stesso in virgola mobile.....allora non si potrebbe andare oltre il 2+2.
Come nascerebbero, allora, programmi di calcolo, programmi 3d, programmi astronomici, ecc ecc....?? Come sarebbero stati creati 3d Studio, Autocad, e altri software che hanno precisioni nell'ordine dei 4-5 decimali?
approssimazione... lo stesso zero in virgola mobile non è detto sia zero, per questo si parla di "zero macchina", in pratica, per il calcolatore, un valore è nullo quando è inferiore al più piccolo valore assoluto rappresentabile.
DanieleC88
05-07-2008, 21:01
Scusatemi, io non sono un esperto di programmazione e non voglio passare per tale, ma leggendo le vostre risposte al problema di Salvodel si potrebbe dedurre che i PC non servono a niente e si potrebbero anche buttare nel cassonetto...
È un problema di base numerica, ci sono alcuni numeri che non sono precisamente rappresentabili, ma solo approssimativamente (perché in base 2 sono periodici).
uomoserio
06-07-2008, 01:26
È un problema di base numerica, ci sono alcuni numeri che non sono precisamente rappresentabili, ma solo approssimativamente (perché in base 2 sono periodici).
Mmmhhh......quindi secondo quanto detto, i vari programmi di calcolo ad ultra precisione lavorano per approssimazione? :eek:
Software di calcoli ingegneristici, fisici, quantistici,....tutte approssimazioni?
Nei programmi di progettazione ad altissima precisione (meccanica, fisica, chimica) dove un millesimo di millimetro di differenza può significare il fallimento di un progetto lavorano tutti per "approssimazione"? :eek:
Scusate, io non sono né un fisico né un matematico, ma per il buon senso mi risulta difficile crederlo..... :muro:
Inoltre, nei miei primi apprendimenti di programmazione, le poche volte che ho sperimentato calcoli con variabili float e double in C e C++ i valori sono sempre stati corretti, mai sbagliato un solo decimale...:mbe:
Il problema lamentato da Salvodel sembra più un difetto di calcolo che una cosa "normale"....
Voglio dire, si parla di un 23.5 che diventa 23.499998 e non il contrario, quindi sarebbe un'approssimazione inversa.....quando mai la si è vista? Posso capire se 23.499998 me lo approssima a 23.5 per la difficoltà di rappresentare troppi decimali, ma il contrario proprio non l'ho mai visto....e non credo abbia senso.....cioè, il calcolatore non riesce a rappresentare correttamente 23.5? Allora anche la semplice "calcolatrice di windows" in base a che principio funziona? Miracoli? :rolleyes:
Scusate la mia incredulità, nelle mie considerazioni non vuole esserci né polemica né presunzione, voglio solo cercare di capire perchè se davvero esiste questo problema tutto il concetto di "computer" cade e va a farsi friggere....ma mi sembrano cose dell'altro mondo :mc:
DanieleC88
06-07-2008, 02:23
Alcune volte sì. Non è una cosa che capita con tutti i numeri, ma alcuni numeri decimali non è possibile (in base 2) rappresentarli con esattezza. Il sistema numerico in base 2 è un sistema posizionale come anche quello in base 10; ciò significa che ogni cifra di un numero ha un suo "peso" che è dato dalla posizione. Ad esempio, il numero 123 è uguale a (1·100) + (2·10) + (3·1), dove tutti i moltiplicatori sono potenze di 10. I numeri decimali seguono lo stesso principio: 0,53 = (5 / 10) + (3 / 100), dove i moltiplicatori sono le potenze negative di 10.
Un numero in base 2 ha semplicemente una base diversa, il procedimento invece è identico: 7 in base 10 = 111 in base 2 = (1·4) + (1·2) + (1·1). Invece: 0,25 in base 10 = 0,01 in base 2 = (0 / 2) + (1 / 4).
L'algoritmo per convertire un numero decimale dalla base 10 alla base 2 è semplice: lo moltiplichi per due; del risultato, la parte a sinistra della virgola è il "bit" della conversione, la parte a destra della virgola va presa da sola e vi va riapplicato il procedimento (finché non ottieni 0).
Prendiamo 0,2 in base 10 e convertiamolo in base 2:
0,2 · 2 = 0,4 | primo bit: 0
0,4 · 2 = 0,8 | secondo bit: 0
0,8 · 2 = 1,6 | terzo bit: 1
0,6 · 2 = 1,2 | quarto bit: 1
0,2 · 2 = ......
Come vedi si è ritornati a 0,2, e il procedimento ripeterà periodicamente quei 4 bit, dando vita ad una serie infinita di cifre dopo la virgola.
Per sicurezza ho chiesto anche a Qalculate!, che mi traduce 0,2 in base 10 in questa stringa binaria:
≈ 0.0011001100110011001100110011
Segnalandomi, come vedi, l'approssimazione (è una sequenza troncata).
Il problema coi float è che usano la precisione singola su 32 bit (1 di segno, 8 di esponente, e solo 23 di mantissa), quindi, approssimazione dopo approssimazione, con centinaia e centinaia di somme ottieni risultati imprecisi. Con la precisione doppia già puoi avere risultati più affidabili.
Poi, ovvio che software complessi che devono garantire una buona dose di precisione facciano fronte a questi problemi in diversi modi... Insomma, non è tutto da gettare alle ortiche, tranquillo. ;)
Ci fai vedere un po' di codice ? Secondo me si tratta di una somma di errori di rappresentazione. Compreso il codice che stampa in numeri per verifica ;)
Prima di tutto devi usare sempre i double e mai i float.
Il problema di rappresentazione dei numeri in virgola mobile è presto detto: i numeri reali hanno cardinalità maggiore dei numeri rappresentabili su un floating point a qualsiasi precisione. E' chiaro, di conseguenza, che non tutti i numeri reali sono rappresentabili su un numero limitato di bit.
Nei calcoli di alto livello si usano per questo precisioni maggiori dei 64-80 bit dei double. Spesso si usano floating point a 128 bit. I nostri processori permettono già di usare questo livello di precisione, ma solamente tramite istruzioni SSE, che sono appunto adatte a processare dati vettorialmente.
Poi c'è anche tutta una teoria dietro per limitare la propagazione di errori di rappresentazione.
!k-0t1c!
06-07-2008, 18:11
Qualora fosse ancora necessario...
Mi sono registrato oggi e ho visto questo thread.
La spegazione fornita sopra sull'inesattezza della rappresentazione binaria di un numero decimale è esatta, e alla luce di essa è opportuno decidere qual'è il livello di precisione realmente necessario.
La libreria che si trova al link seguente fornisce la possibilità di calcolo in virgola mobile con più precisione di quanta non ne serva normalmente. Se si desiderassero numeri esatti, ad ogni modo, e questi non fossero parte di algoritmi particolarmente cpu-intensive, consiglio l'uso di un elevata precisione combinata combinata ad arrotondamenti, in maniera tale da arrivare a risultati "esatti". Nel caso di applicazioni che devono fare calcoli di denaro è molto utile al fine di non perdere o aggiungere nei calcoli qualcosa qua e la, specie perché se la quantità di operazioni diventa ingente anche le somme perse o aggiunte possono diventare non trascurabili.
http://www.apfloat.org/apfloat/
salvodel
07-07-2008, 09:54
Ci fai vedere un po' di codice ? Secondo me si tratta di una somma di errori di rappresentazione. Compreso il codice che stampa in numeri per verifica ;)
Spero vivamente che ci sia un errore di precisione. Non avevo mai notato questo problema nei programmi precedenti. Prima plottavo solo i punti calcolati e poi in matlab mi generavo i tempi per tutti gli output. Qui ho provato a salvare i tempi per ogni file e quando ho visualizzato i dati mi sono accorto di questo sfasamento....spero quindi che ci sia un bel:D errore.
Grazie a tutti.
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
int salva(float time[],float time_c[], int max)
{
FILE *fdati;
char *OUTPUT;
int t;
OUTPUT=(char *)malloc(15*sizeof(char));
printf("Salvataggio - ");
sprintf(OUTPUT, "%s.dat", "tempi");
if((fdati=fopen(OUTPUT,"w"))==NULL)
{
printf("\nImpossibile scrivere il file\n");
return 0;
}
else
{
for(t=0;t<max;t++)
fprintf(fdati,"%d\t%f\t%f\n",t,time[t], time_c[t]);
}
printf("O.K.\n");
return 1;
}
int main()
{
float *time, *time_c, *WIND;
float tMAX, WIND_medio;
int count, t;
FILE *fdati;
tMAX=-1;WIND_medio=-1;
time =(float *)malloc(1*sizeof(float *));
WIND =(float *)malloc(1*sizeof(float *));
count=0;
if((fdati=fopen("dati.dat","r"))==NULL)
printf("Impossibile aprire il file");
else
{
while(fscanf(fdati,"%f",&time[count])>0)
{
count++;
time = (float *) realloc (time, (count+1)*sizeof(float));
}
}
time_c =(float *)malloc(count*sizeof(float *));
time_c[0]=0.0f;
for(t=1;t<count;t++)
time_c[t]=time_c[t-1]+0.2f;
salva(time,time_c,count);
return 1;
}
Allora, il primo problema è intrinseco al fatto che vai a scrivere i dati con printf. I floating point, se dopo devono essere recuperati e rielaborati devono essere SEMPRE salvati in binario. Altrimenti già stampando su file con %f limiti la precisione del float stesso non andando a scrivere gran parte delle cifre dopo la virgola. In alternativa, nonostante non sia perfettamente equivalente, stampa con %.20f.
Il secondo problama: non si usano MAI i float, ma SEMPRE i double. I float hanno una precisione adatta ai programmini delle scuole superiori. I float sono solamente 32 bit (se non mi ricordo male 23 di mantissa, 8 di esponente e 1 di segno).
Solo la scrittura e la lettura binaria ti garantisce di andare a scrivere un numero e poi andare a riprendere esattamente quello che hai scritto. Infatti c'è già un grandissima approssimazione intrinseca alla scrittura in testo con decimali limitati, poi quando vai a riprendere questi numeri leggendo il testo c'è una ulteriore approssimazione dovuta alla conversione da testo a floating point. Ecco qui il doppio errore di approssimazione.
salvodel
07-07-2008, 12:58
Allora, il primo problema è intrinseco al fatto che vai a scrivere i dati con printf. I floating point, se dopo devono essere recuperati e rielaborati devono essere SEMPRE salvati in binario. Altrimenti già stampando su file con %f limiti la precisione del float stesso non andando a scrivere gran parte delle cifre dopo la virgola. In alternativa, nonostante non sia perfettamente equivalente, stampa con %.20f.
Il secondo problama: non si usano MAI i float, ma SEMPRE i double. I float hanno una precisione adatta ai programmini delle scuole superiori. I float sono solamente 32 bit (se non mi ricordo male 23 di mantissa, 8 di esponente e 1 di segno).
Solo la scrittura e la lettura binaria ti garantisce di andare a scrivere un numero e poi andare a riprendere esattamente quello che hai scritto. Infatti c'è già un grandissima approssimazione intrinseca alla scrittura in testo con decimali limitati, poi quando vai a riprendere questi numeri leggendo il testo c'è una ulteriore approssimazione dovuta alla conversione da testo a floating point. Ecco qui il doppio errore di approssimazione.
Da quello che mi dici quindi posso fare ben poco. Avevo utilizzato i float invece dei double per una storia secondaria ma anche utilizzando i double il risultato non cambia molto.
Cmq c'è un erroraccio nel listato(c'era un * di troppo essendo in principio una function). Ora l'ho corretto ed ottengo il seguente risultato:
OK
0 0 0
1 0.2 0.2
2 0.4 0.4
3 0.6 0.6
4 0.8 0.8
5 1 1
Cosi e cosi
159 31.8 31.8
160 32 32
161 32.2 32.2001
162 32.4 32.4001
163 32.6 32.6001
164 32.8 32.8001
165 33 33.0001
166 33.2 33.2001
:muro:
446 89.2 89.1998
447 89.4 89.3998
448 89.6 89.5998
449 89.8 89.7998
450 90 89.9998
451 90.2 90.1998
452 90.4 90.3998
453 90.6 90.5998
454 90.8 90.7998
455 91 90.9998
456 91.2 91.1998
457 91.4 91.3997
458 91.6 91.5997
459 91.8 91.7997
460 92 91.9997
461 92.2 92.1997
462 92.4 92.3997
463 92.6 92.5997
464 92.8 92.7997
465 93 92.9997
466 93.2 93.1997
467 93.4 93.3997
468 93.6 93.5997
469 93.8 93.7997
470 94 93.9997
471 94.2 94.1997
472 94.4 94.3997
473 94.6 94.5997
474 94.8 94.7997
475 95 94.9997
476 95.2 95.1997
477 95.4 95.3997
478 95.6 95.5997
479 95.8 95.7997
480 96 95.9997
481 96.2 96.1997
482 96.4 96.3997
483 96.6 96.5997
484 96.8 96.7997
485 97 96.9997
486 97.2 97.1997
487 97.4 97.3997
488 97.6 97.5997
489 97.8 97.7997
490 98 97.9996
491 98.2 98.1996
492 98.4 98.3996
Per il file binario non avendocelo in quel modo non posso farci niente, giusto? Cmq ora lo legge bene ma non riesco a creare una serie correttamente.
Grazie
Sono proprio le somme che propagano ancora di più l'errore di rappresentazione ;)
Prova a sostituire questo
for(t=1;t<count;t++)
time_c[t]=time_c[t-1]+0.2f;
con questo
for(t=1;t<count;t++)
time_c[t]=time_c[0]+0.2f * t;
salvodel
07-07-2008, 14:18
Sono proprio le somme che propagano ancora di più l'errore di rappresentazione ;)
Prova a sostituire questo
for(t=1;t<count;t++)
time_c[t]=time_c[t-1]+0.2f;
con questo
for(t=1;t<count;t++)
time_c[t]=time_c[0]+0.2f * t;
La soluzione del prodotto risolve decisamente la situazione. Leggendo e scrivendo i double con %lf la situazione migliora. Di questo me ne avevi gia parlato del fatto che la printf non fa distinzione mentre la scanf si. Io spesso ho utilizzato %g.
Cmq questo è quello che ottengo con %lf e facendo il prodottocontatore | serie letta | serie generata
4264 852.830000 852.800000
4265 853.030000 853.000000
4266 853.230000 853.200000
4267 853.430000 853.400000
4268 853.630000 853.600000
4269 853.830000 853.800000
4270 854.030000 854.000000
4271 854.230000 854.200000
4272 854.430000 854.400000
Questo è quello che ottengo con %lf e facendo la sommatoria
contatore | serie letta | serie generata
4264 852.830000 852.800013
4265 853.030000 853.000013
4266 853.230000 853.200013
4267 853.430000 853.400013
4268 853.630000 853.600013
4269 853.830000 853.800013
4270 854.030000 854.000013
4271 854.230000 854.200013
4272 854.430000 854.400013
Grazie:ave:
PS cionci quando togli la scritta Lontano dal forum per qualche mese? :D
vBulletin® v3.6.4, Copyright ©2000-2026, Jelsoft Enterprises Ltd.