Newsgroup: it.comp.lang.c
Titolo: Da Double a Stringa

Ciao a tutti!
Premetto che so molto poco del linguaggio C: mi è stato posto un
quesito da un professore, realizzare una subroutine per un DSP che
ricevesse in ingresso un numero in formato double e fornisse una
sequenza contenente le cifre del numero in codice ASCII (quindi in
formato stringa). Credo che ci sia già una funzione standard che fa
questo, dovrebbe essere sprintf(), ma da come mi è stata posta la
domanda dal professore (che mi ha chiesto di essere più bravo di lui),
la risposta non dovrebbe essere così semplice.
Qualcuno mi può dare una mano in proposito?
Grazie.


e' abbastanza semplice trovare i sorgenti della implementazione della 
[s]printf ( GCC , SDCC , il vecchio borland C )
possono essere una base di partenza e di studio
P.S. sarebbe consigliabile che te li studiassi per bene altrimenti e' 
facile accorgersi che non e' farina del tuo sacco

FabrizioIng wrote:

Non e' difficile. Siccome non ti ha chiesto un formato
specifico, la cosa piu' semplice e' adottare un
formato scientifico con un numero di cifre significative
a richiesta. Diciamo qualcosa tipo
+4.1245668E+03
-6.5532235E-07
la cosa si realizza facilmente con i seguenti passi:
1) determini e memorizzi il segno del numero, e poi
lo rendi positivo se era negativo.
2) se il numero e' strettamente minore di 1 lo moltiplichi
ripetutamente per 10 fino a che diventa maggiore di 1
Il numero di volte che hai dovuto moltiplicarlo di fornisce
l'esponente (in negativo)
cioe', se era 0.00345555555 lo devi moltiplicare 3 volte
e infatti il numero corrisponde a 3.45555555 * 10^-3 = 3.455555E-3
3) analogamente se il numero e' maggiore o uguale a 10, lo
dividi ripetutamente per 10 fino a portarlo sotto a 10 e
questo ti fornisce l'esponente positivo.
4) arrivato qui hai un numero double x compreso tra 1.0 e 9.99999999999.
(suppongo di stampare una versione con troncamento, se vuoi
quella con arrotondamento devi solo stare un po' piu'
attento, perche' 9.9999999.. potrebbe "scattare" a 10.00000 che
va trattato a parte)
per ottenere le cifre ti basta ripetere un ciclo
in cui prendi la parte intera del numero,
(cosi' ottieni la cifra piu' significativa) poi la togli
dal numero e moltiplichi il rimanente per 10 per
fare avanzare le cifre.
Ripeti in questo modo fino ad ottenere il numero
di cifre desiderate. Con una if sull'indice,
fai in modo da aggiungere un '.'
dopo la prima cifra, per separare i decimali.
Qualcosa tipo (stampo, invece di accumulare in una stringa):
double x=7.234580123;
int i,d;
char c;
// qui andrebbe stampato (messo nella stringa) il segno
// originario del numero poi...
for (i=1; i<=6; i++)
{
   d = (int) x; // parte intera per troncamento
   c = '0'+ d; // conversione in carattere
   printf("%c",c);
   if (i==1) printf(".");  // dopo il primo metto il punto
   x = (x-d)*10; // avanzo di una cifra
}
printf("\n");
// e poi si stampa (accoda) l'esponente
ciao,
g.

Grazie tante, mi hai dato l'input giusto per iniziare. In realtà mi è
stato chiesto di mettere il numero nella stringa così come si trova,
senza alcuna notazione. A questo punto mi conviene trattare la parte
intera in un ciclo che seleziona una cifra alla volta fino ad arrivare
a zero (dividendo ogni volta per 10), dopodichè inserisco il punto
nella stringa. A questo punto moltiplico ogni volta per 10 per
selezionare tutte le cifre decimali, fino alla fine del numero.
Grazie ancora. Fabrizio.


Se vai avanti fino a che il numero "finisce" rischi di stampare
anche una caterva di cifre prive di significato.
Il motivo risiede tra le altre cose nel fatto che la maggior
parte dei numeri decimali, per esempio 0.3 oppure 4.78,
non possono essere rappresentati "esattamente" in binario.
In binario 0.1 e' un numero periodico, quindi qualunque "troncamento"
della sua rapprsentazione ad un numero finito di bit non
rappresentera' mai esattattamente il numero 0.1.
Per esempio, il numero 1.1 in realta' quando e' dentro un double,
e' probabilmente rappresentato con un numero binario che se
convertito in decimale corrisponde a:
1.100000000000000088817841970012523233890533447265625
quindi valuta bene dove e quando fermarti quando stampi
le cifre.
Analogamente, se il tuo numero double fosse un numero
molto grande, ad esempio dell'ordine di 10^40, allora
buona parte delle cifre prima della virgola, e tutte
quelle dopo la virgola, sarebbero chiaramente prive di
senso. In generale un numero double ha una precisione
(se ricordo bene) compresa tra le 15 e le 16 cifre decimali.
Per questo motivo si usa la rappresentazione scientifica,
per mettere in evidenza le cifre significative di
un numero:
scrivere 4.123456789433E+40 e' molto piu' sensato di
scrivere  41234567894329997658445411834261296644096.32355
soprattutto se le cifre dopo la 16esima sono totalmente
prive di senso.
ciao,
g.

 ha scritto nel messaggio
news:dvrmbr$olf$1...@news.cineca.it...

attenzione però se devi fare delle moltiplicazioni/divisioni
con questi numeri allora devi considerare tutte le cifre possibili.
41234567894329997658445411834261*0.3=
12370370368298999297533623550278

 
In che senso ?
Se la precisione dei double e' di 16 cifre decimali (circa)
le cifre dopo la 16a sono semplicemente prive di significato.
Prova a spiegare  *esattamente* cosa intendevi.
Tra l'altro le operazioni critiche, dal punto di vista
della perdita di precisione, sono somme e sottrazioni, non
moltiplicazioni e divisioni.
ciao,
g.

moltiplicazioni e divisioni.
Uh?

Andrea Laforgia wrote:

La precisione di un numero corrisponde grosso
modo a quante cifre significative hai, quindi
e' tanto maggiore quanto minore e' l'errore relativo.
Se sottrai due numeri che sono vicini tra
di loro (o sommi due numeri di segno opposto
che in valore assoluto sono vicini) puoi
avere una perdita di cifre significative
nel risultato.
Per esempio, se hai due numeri
1234567.8882322223555454 e 1234566.124544545454545545
e li rappresento come numeri di macchina poniamo che
ciascuno venga rappresentato con 10 cifre significative (lo so che
i double sono piu' precisi, e' per fare un esempio):
1234567.888 e 1234566.122
quindi posso ritenere che l'errore relativo commesso
nella rappresentazione sia  dell'ordine di 10^(-10).
Se li vado a sottrarre interviene il fenomeno della
cancellazione: lavorando in precisione 10 cifre ottengo
1.764, che ha solo 4 cifre significative. Quindi potrebbe
essere affetto da un errore relativo dell'ordine di 10^-4,
maggiore di circa un milione di volte di quello dei suoi
addendi.
Se invece faccio una moltiplicazione, l'errore relativo
dal quale puo' essere affetto il risultato e' grosso
modo dello stesso ordine di quello degli operandi.
Questo si puo' vedere anche in C, facendo delle operazioni tipo:
double x=1.0, y=2.0e30, z;
una operazione di questo tipo mi da un risultato
completamente errato (dovrebbe dare 0 al posto di 1)
z=(x+y)-y;
(E' una cosa che per esempio puo' capitare nel calcolo delle
radici di una equazione di secondo grado con la formula
classica quando una radice e' molto grande e una molto piccola)
mentre una operazione del tipo
z=(x/y)*y;
e' sostanzialmente innocua e dovrebbe fornire un risultato
quasi esatto.
Scusate se sono un po' confuso, ma sono le 01:22 e l'esame
di calcolo numerico l'ho dato circa 20 anni fa...
ciao,
g.

 ha scritto nel messaggio
news:4421eb99$0$29731$4faf...@reader2.news.tin.it...

guarda io non sono un esperto di calcolo ma se io sommo due numeri:
12345.123+12345.678=24690,801
poi se trascuro i numeri dopo la virgola ho:
12345+12345=24690
errore = 0.801
ora se faccio lo stesso moltiplicando:
12345.123*12345.678=152408913,428394
12345*12345=152399025
errore=152408913,428394 -152399025=9888,428394
scusa ma non credo di averti capito, come fai a dire che
le somme/sottrazioni sono più critiche?
ivan

 le somme/sottrazioni sono più critiche?
Per il motivo  che nella stragrande maggioranza dei
casi pratici (e anche nelle operazioni in floating point del C)
ha rilevanza l'errore relativo e non l'errore assoluto (quello
che consideri nel tuo esempio.)
Inoltre ho parlato di somma di due nuneri discordi o di differenza di
due numeri concordi di segno con valori assoluti molto vicini.
Nel tuo caso il numero 12345.123 come numero di macchina 12345
e' affetto da un errore relativo  0.123/12345 = circa 0.00001.
Se vai a fare la DIFFERENZA tra i due numeri che hai detto
tu come numeri di macchina ottieni addirittura un errore
relativo infinito, perche' ottieni 0 al posto di 0.801, cioe'
ottieni un valore che non ha neanche una cifra significativa esatta.
Al contrario, l'errore relativo del prodotto e'
(152408913,428394 -152399025)/152399025 = 0.00006
che e' maggiore di quello dei due operandi, ma non e' infinito,
e infatti se rappresenti i numeri in notazione scientifica
sono entrambi uguali a 1.524e8, cioe' hai almeno 4 cifre
significative esatte nel risultato.
In generale la rappresentazione in virgola mobile (floating point)
insomma quella usata per rappresentare i numeri con i double del C
e' una rappresentazione che cerca di minimizzare gli errori
relativi. E quindi quella che dobbiamo valutare e' l'amplificazione
dell'errore relativo durante l'esecuzione delle operazioni.
L'enfasi sull'errore relativo e quindi sulla rappresentazione in
virgola mobile ha senso dal punto di vista pratico, perche'
generalmente mi fa comodo poter rappresentare numeri da molto
piccoli a molto grandi e mi interessa conoscere un numero
ragionevole di cifre significative in entrambi i casi, indipendentement
da dove cade la virgola nei numeri.
Al contrario, una rappresentazione in virgola fissa tipicamente
o assegna molte posizioni prima della virgola, tipo xxxxxx.xxx
e allora potro' rappresentare bene numeri grandi tipo 123456,789,
ma quelli piccoli avranno al massimo 3 cifre significative,
oppure privilegio le posizioni dopo la virgola, tipo xxx.xxxxxxx e
allora posso rappresentare numeri piccoli con buona precisione,
ma non posso rappresentare affatto numeri maggiori di 999.
La rappresentazione in virgola mobile, quindi mantissa+esponente
e' un buon compromesso perche' mi permette sia di rappresentare
i numeri vicini allo 0 con buona precisione sia dal punto
di vista dell'errore relativo che assoluto e quelli molto
minori o molto maggiori comunque con un certo numero garantito
di cifre significative che non dipende dalla magnitudine del numero
e quindi con un errore relativo (o se preferisci un errore "in
percentuale") basso.
Ci sono naturalmente dei settori (limitati)
nei quali quello che mi interessa e' l'errore assoluto.
Per esempio nei calcoli sui grandi numeri interi che intervengono
ad esempio in crittografia o in generale in teoria dei numeri:
in quel caso della virgola mobile mi faccio bene poco, visto che voglio
rappresentare un numero intero
non con una precisione fissa e prefissata (come avviene nei numeri
floating point) ma con una precisione tale da consentirmi di avere
errore assoluto uguale a zero, ovvero, in altre parole, di rappresentare
il numero in modo esatto. In quel caso si usano (quando i long long non
bastano piu') delle rappresentazioni in precisione arbitraria, che
mi rappresentano il numero con tutti i bit che servono per rappresentarlo.
Queste rappresentazioni in precisione arbitraria non sono di cosi'
comune utilizzo e quindi non fanno parte del C standard, ma vengono
realizzate da apposite librerie, come le GMP della GNU.
In generale i problemi relativi all'errore e alla sua propagazione
non sono difficili ma richiedono un po' di background, che deve
partire da una comprensione dettagliata di come vengono rappresentati
i numeri di macchina. Le prime 50 pagine di un qualsiasi testo
di calcolo numerico dovrebbero essere abbastanza.
Questo documento e' molto interessante:
http://cr.yp.to/2005-590/goldberg.pdf
si intitola "What Every Computer Scientist Should Know About Floating
Point Arithmetic" e nelle prime pagine spiega con esempi
l'errore relativo e la rappresentazione floating point etc.etc.
ciao,
g.

Ho capito quello che intendi, ed effettivamente il professore che mi ha
richiesto questa subroutine non è stato dettagliato a riguardo. Cmq,
non credo che debba scrivere il numero double in una notazione
scientifica particolare, perchè il numero in questione deve essere
passato cifra per cifra ad un Processore Digitale che lo legge così
come era scritto all'origine. Quindi credo che sia opportuno, come dici
tu, troncare i decimali diciamo intorno la decima cifra dopo la virgola
(chiederò lumi in merito al prof), ma non posso fare nulla se il
numero è eccessivamente grande.
Cmq grazie lo stesso. Fabrizio.