Newsgroup: it.comp.lang.vo-clipper
Titolo: Sarà la volta buona?

Cari amici, intanto volevo dirvi che ho capito la causa dell'errore
evidenziato da Diego, quello che capita dopo un certo numero di
decimali. Il problema, proprio come nel caso della INT(), dipende da
qualcosa di più interno. Infatti se proviamo a stampare
? 1.114999999999999
otteniamo il numero esatto ma se aggiungiamo un nove
? 1.1149999999999999
allora otteniamo
1.1150000000000000
Questo, secondo me, significa soltanto che abbiamo raggiunto il limite
di precisione supportato da Clipper e pertanto non dobbiamo
preoccuparcene più di tanto.
Poi vi propongo questa correzione alla mia soluzione:
FUNCTION ROUND2( nValue, nDecimals )
    LOCAL cValue := STR( nValue )
    IF AT( ".", cValue ) = 0; cValue += "."; ENDIF
    IF LEN( cValue ) - AT( ".", cValue ) < nDecimals
        cValue += REPLICATE( "0", nDecimals )
    ENDIF
    RETURN ROUND( VAL( cValue + "1" ), nDecimals )
Il rallentamento è trascurabile. Vi prego di provare anche questa.
EMG
EMAG Software Homepage:     
http://www.emagsoftware.it
The EMG's ZX-Spectrum Page: 
http://www.emagsoftware.it/spectrum
The Best of Spectrum Games: 
http://www.emagsoftware.it/tbosg

 Il rallentamento è trascurabile. Vi prego di provare anche questa.
Ok !, faccio girare il programma di test usato per la precedente funzione
anche su questa.
Ciao.
Davide.

 anche su questa.
No, non serve.
? round( 512.045, 2 )     // 512.04 ok
? round2( 512.045, 2 )    // 512.05 no
? round( 512.0451, 2 )    // 512.05 no
In particolare, la terza riga mi fa pensare che l'approccio basato sull'EPS
non può funzionare.
Dubito che si riesca a correggere una funzione che ha un errore usando alla
fine la stessa funzione che ha l'errore.
Ricominciamo a pensare.......
Ciao.
Davide.

 Ricominciamo a pensare.......
Perché non ti rilassi un attimo e rileggi quello che hai scritto? :-)



Intendevo dire che la RETURN della Round2() utilizza di nuovo Round (pur
aggiungendo l'EPS).
Come fai ad essere matematicamente sicuro che il valore modificato che gli
passi non risulti nuovamente errato a causa della round() stessa ?.
So che non ti piace, comunque se vuoi testarla (e mi faresti un piacere) ho
apportato alcune modifiche alla round2 con le stringhe. Ora non ha piů il
limite delle 14 cifre intere e delle 10 decimali.
Ho corretto anche il problema dell'allineamento dei decimali con la round()
normale (anche se non al 100%) , comunque io non utilizzo mai "?" per
stampare un numero, ma sempre Transform()
Ciao.
Davide.
Function Round2(nNum,nDec)
Local cNum,cInt,cDec,cUlt,nDec1:=0
If nDec<=0
  nNum:=nNum*10^nDec             // Divido il numero per traslare la virgola
  nDec1:=-nDec                   // Ricordo num.decimali
  nDec:=0                        // Numero decimali = 0 per round
Endif
cNum:=Str(nNum)
IF AT(".",cNum)=0; cNum += ".0"; ENDIF   // Metto almeno un decimale per
cUlt
cInt:=left(cNum,AT(".",cNum)-1)          // Parte Intera
cDec:=SubStr(cNum,AT(".",cNum)+1,nDec)   // Parte Decimale
cUlt:=SubStr(cNum,AT(".",cNum)+1+nDec,1) // Un decimale in pi- per le
verifiche
If cUlt>="5"                             // Se S >= 5 ..
  cDec:=lTrim(Str(Val(cDec)+1))          // .. aumento di 1 la parte
decimale
  Do Case
    Case Len(cDec)>nDec                  //    Se S aumentato troppo (es.
99->100) ..
      cDec:="0"                          //    .. i decimali vanno a zero ..
      cInt:=lTrim(Str(Val(cInt)+1))      //    .. aumento di 1 la parte
intera
    Case Len(cDec)<nDec                  //    Se S diminuito (es. 00->1) ..
      cDec:=Right(Replicate("0",nDec)+cDec,nDec) //  .. fillo di 0 a sx
  EndCase
Endif
If cDec<>Replicate("0",Len(cDec))
  cInt += "."+cDec               // Aggiungo decimali se ci sono (altrimenti
ottengo output non formattato)
Endif
Return Val( cInt + Replicate("0",nDec1) )

 So che non ti piace, comunque se vuoi testarla (e mi faresti un piacere) ho
No, in realtà è anche più veloce di quella di Diego. Le tre soluzioni
stanno circa ad un rapporto di 1:2:3, cioè la tua ci mette il doppio
della mia e quella di Diego il triplo. Credo che la lentezza sia dovuta
all'elevamento a potenza. Comunque ho fatto qualche test e (non mi
odiare):
? ROUND3( 123.5, 0 ) ==> 124 (ma allineato al margine sinistro)



Lo so, è per questo che ho detto che non risolve l'allineamento al 100%.
Il manuale di Clipper non riporta nulla sull'allineamento di VAL(), ma per
quanto riguarda STR() dice che nel caso si usi una Espressione/Costante
ritorna un minimo di 10 cifre + i decimali, mentre nel caso si faccia
STR(VAL()) ritorna un minimo di 3 cifre.
Da questo deduco che tutti i numeri interi < 1000 (una volta arrotondati)
hanno il problema dell'allineamento.
Ci si può lavorare, se vuoi, ma ancora non comprendo perchè questo ti crei
dei problemi.
Comunque, se restiamo al puro valore numerico ritornato dalle 2 funzioni
round che sembrano funzionare sempre (la mia con le stringhe e la tua
ultima) i risultati dei test che ho fatto girare sono questi:
1) La mia, con il tuo programma di test che gira da 0 a 1000000 da lo 0%
degli errori.
2) Il tuo programma di test modificato in modo che compari il risultato
della mia con quello della tua (da 0 a 350000 step 0.001) da gli stessi
identici risultati.
3) Lo stesso test modificato in modo che compari la mia round con la round
fatta usando INT() sta girando ancora, ed è arrivato a 81000 senza rilevare
errori.
Tutti i test sono stati eseguiti facendo round con 2 decimali.
In queste condizioni sembra che sia la mia che la tua funzionino entrambe
bene.
Ciao.
Davide.

 dei problemi.
Hai pensato alle stampe? Se tu stampi un numero ad una certa posizione
perché questo era preceduto da un certo numero di spazi, cosa succede
alla tua stampa se questi spazi vengono improvvisamente a mancare?


bene.
Per risparmiare a Enrico l'ennesimo "sorry", te lo dico io.
Mi dà subito problemi con 8.075 e con 512.055
ciao

 Mi dà subito problemi con 8.075 e con 512.055
:-)))



Con quanti decimali ?. Con 2 funzionano entrambe.
Forse ti sei perso qualche modifica e non stai testando le ultime versioni.
Te le riporto qui per semplicità, perchè sul NG ne sono girate un bel po.
Ciao.
Davide.
FUNCTION ROUND2( nValue, nDecimals )
    LOCAL cValue := STR( nValue )
    IF AT( ".", cValue ) = 0; cValue += "."; ENDIF
    cValue += REPLICATE( "0", nDecimals - LEN( cValue ) + AT( ".",
cValue ) )
    RETURN ROUND( VAL( cValue + "1" ), nDecimals )
Function Round3(nNum,nDec)


 alla tua stampa se questi spazi vengono improvvisamente a mancare?
Personalmente, non mi sono mai fidato delle spaziature automatiche date da
funzioni tipo "?" ed ho sempre usato Transform() per allineare i miei
numeri.
Comunque la tua obiezione è sensata. Proverò a vedere come si può sistemare.
Ciao.
Davide.

 Te le riporto qui per semplicità, perchè sul NG ne sono girate un bel po.
Ecco un problemino residuo:
? ROUND3( 123.5, 0 ) ==> 124 (ma allineato a sinistra)
Non ho trovato niente altro. Resta la lentezza del doppio rispetto alla
mia proposta.


 Comunque la tua obiezione è sensata. Proverò a vedere come si può sistemare.
Ok.



In realtà c'erano anche altri casi che non funzionavano, tipo:
? ROUND3( 123.55, 5 ) ==> 123.55 invece di 123.55000 (solito allineamento)
? ROUND3( -999.99, -2 ) ==> -800 invece di -1000 (piccola svista, ma fatale)
Ora li ho corretti e la funzione è compatibile 100% con la round() originale
(a parte che questa funziona :-) )
Comunque per sicurezza sto facendo girare un test che confronta l'output a
video delle due funzioni. Più tardi vi posto i risultati.
Non so che metodo usi per testare la velocità, ma visto che l'ho anche
ottimizzata un pochino, puoi ritestarla per piacere ?
Per vedere se il problema velocità sta nell'elevamento a potenza, puoi
testare separatamente i casi di round() con un numero di decimali positivo
(la funzione non fa ^) e negativo ?
Ciao.
Davide.
Function Round3(nNum,nDec)
Local cNum,cInt,cDec,nDec1:=0
If nDec<0
  nNum:=nNum*10^nDec             // Divido il numero per traslare la virgola
  nDec1:=-nDec                   // Ricordo num.decimali
  nDec:=0                        // Numero decimali = 0 per round
Endif
cNum:=Str(nNum)
IF AT(".",cNum)=0; cNum += ".0"; ENDIF     // Metto almeno un decimale
cInt:=left(cNum,AT(".",cNum)-1)            // Parte Intera
cDec:=SubStr(cNum,AT(".",cNum)+1,nDec)     // Parte Decimale
If SubStr(cNum,AT(".",cNum)+1+nDec,1)>="5" // Decimale successivo >= "5" ..
  cDec:=lTrim(Str(Val(cDec)+1))            // .. aumento di 1 la parte
decimale
  If Len(cDec)>nDec                        //    Se S aumentato troppo (es.
99->100) ..
      cDec:="0"                            //    .. i decimali vanno a zero
..
      cInt:=Str(Val(cInt)+IF(nNum<0,-1,1)) //    .. aumento di 1 la parte
intera
  ElseIf Len(cDec)<nDec                    //    Se S diminuito (es. 00->1)
..
      cDec:=PADL(cDec,nDec,"0")            //    .. fillo di 0 a sx
  Endif
Endif
If nDec>0
  cInt += "."+PADR(cDec,nDec,"0")          // x allineamento
Else
  cInt += Replicate("0",nDec1)             // x arrot. con decimali negativi
Endif
Return Val( cInt )

 ? ROUND3( Val("123"), 0 ) ==> 123 (ma di nuovo allineato a sinistra)
Prova questa per piacere.
Ciao.
Davide.
Function Round3(nNum,nDec)
Local cNum,cInt,cDec,nDec1:=0
If nDec<0
  nNum:=nNum*10^nDec             // Divido il numero per traslare la virgola
  nDec1:=-nDec                   // Ricordo num.decimali
  nDec:=0                        // Numero decimali = 0 per round
Endif
cNum:=Str(nNum)
IF LEN(cNum)<10;cNum:=PADL(cNum,10); ENDIF // Almeno 10 car. x allineamento
*********
IF AT(".",cNum)=0; cNum += ".0"; ENDIF     // Metto almeno un decimale
cInt:=left(cNum,AT(".",cNum)-1)            // Parte Intera
cDec:=SubStr(cNum,AT(".",cNum)+1,nDec)     // Parte Decimale
If SubStr(cNum,AT(".",cNum)+1+nDec,1)>="5" // Decimale successivo >= "5" ..
  cDec:=lTrim(Str(Val(cDec)+1))            // .. aumento di 1 la parte
decimale
  If Len(cDec)>nDec                        //    Se S aumentato troppo (es.
99->100) ..
      cDec:="0"                            //    .. i decimali vanno a zero
..
      cInt:=Str(Val(cInt)+IF(nNum<0,-1,1)) //    .. aumento di 1 la parte
intera
  ElseIf Len(cDec)<nDec                    //    Se S diminuito (es. 00->1)
..


 (la funzione non fa ^) e negativo ?
Finora l'avevo sempre testata con il secondo parametro positivo. In
effetti quando è negativo diventa un pochino più lenta.


 Prova questa per piacere.
Sembra vada bene. Ma, come ho detto, a questo punto penso sia necessario
ideare un test come si deve.


 ideare un test come si deve.
Il test sulla compatibilità dell'allineamento è finito senza riportare
errori.
Ovviamente ho comparato la mia funzione con la tua e non con la round() di
Clipper altrimenti si sarebbe fermato continuamente, però la tua è
sicuramente compatibile con quella.
Questo secondo me significa che funzionano entrambe bene. Che ne dici ?
Ciao.
Davide.
STATIC FUNCTION TEST( )
     LOCAL nErr := 0
     LOCAL nTot := 0
     LOCAL i,j,cr2,cr3
     FOR j = -1 TO 4                     // decimali
     FOR i = -10000 TO 10000 STEP 0.001
         i = VAL( STR( i ) )
         ? i,j
         ? ROUND2( i, j )
         ? ROUND3( i, j )
         cr2:=SAVESCREEN(ROW()-1,1,ROW()-1,50)
         cr3:=SAVESCREEN(ROW()  ,1,ROW()  ,50)
         IF cr2 <> cr3
           ? "Errore"
           ACCEPT "" TO TASTO
           nErr++
         ENDIF
         nTot++
     NEXT i
     NEXT j
     ? "ROUND()    ", nErr, nTot, LTRIM( STR( nErr / nTot * 100 ) ) + "%"
     RETURN NIL

 Questo secondo me significa che funzionano entrambe bene. Che ne dici ?
Non so. Il test che riporti sotto, ad esempio (e se vedo bene), non
avrebbe evidenziato il problema con il numero 1.1149999999999, dico
bene? Non sarebbe il caso, da una parte, di estendere il test a tutte le
possibilità e, dall'altra, di limitarlo ai casi in cui la ROUND()
effettivamente può avere problemi (quelli attorno allo zero o al
cinque)? Che ne pensi?


 cinque)? Che ne pensi?
Penso che si dovrebbe testare almeno lo 0, il 9, il 4 ed il 5, cioè il 40%
dei casi totali. (raddoppio della velocità del test)
Per testare tuttavia tutte le combinazioni incapperemmo in un numero
infinito di casi. Cosa secondo me impossibile.
Penso che la questione del test meriti un thread a parte. Provo ora ad
iniziarne uno.
Ciao.
Davide.

 cinque)? Che ne pensi?
Penso che si dovrebbe testare almeno lo 0, il 9, il 4 ed il 5, cioè il 40%


 iniziarne uno.
Ok.
