Newsgroup: it.comp.lang.vo-clipper
Titolo: Piccola correzione e semplificazione

Cari amici, c'era un difettuccio nella proposta precedente (aggiungeva
troppi zeri). Ecco quella aggiornata:
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 )
Spero che sia la volta buona...
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

Spero che sia la volta buona...
Non ho resistito a seguirvi e a fare ancora prove, ma lo "stacco" mi
ha consentito di pensare a due approcci un po' diversi (anche se
ovviamente basati sulle idee che stanno circolando...)
Innanzitutto, per le mie modeste prove la tua soluzione sembra ok.
Propongo, se può interessare, la seguente soluzione che, sempre su
numeri positivi e con 2 decimali, dà gli stessi risultati della
Round2().
Invece di usare la STR(), ho usato la TRANSFORM() "massimizzata"...
FUNCTION ROUNDB( nNUM, nDEC )
	LOCAL cAPP, cNUM, nNEW
	cAPP := TRANSFORM( nNUM, "999999999999999.999999999999999")
	// prendo la parte intera con i decimali richiesti
	nNEW := VAL( LEFT( cAPP, 16 + nDEC ) )
	// estraggo il numero da valutare per arrotondare
	cNUM := SUBSTR( cAPP, 16 + nDEC + 1, 1 )
	// se necessario, incremento
	IF VAL( cNUM ) >= 5
		nNEW += VAL( "0." + REPLICATE( "0", nDEC - 1 ) + "1" )
	ENDIF
	// nel return applico l'idea di Davide: Val( Str( ) )
RETURN VAL( STR( nNEW ) )
Altra soluzione, ma più rognosa, è quella che va a controllare lo
scarto del numero arrotondato dal numero di partenza, per apportare
eventuali correzioni.
Ma, se la provate, i problemi non sono pochi e, anche se nelle mie
prove su numeri positivi e 2 decimali, sta alla pari della Round2(),
non mi convince del tutto...
FUNCTION ROUNDc( nNUM, nDEC )
	LOCAL nNEW, nMAX, nINCR
	nNEW := ROUND( nNUM, nDEC )
	nINCR := 1 / 10 ^ nDEC
	nMAX := 5 / 10 ^ ( nDEC + 1 )
	nMAX := VAL( TRANSFORM( 5 / 10 ^ ( nDEC + 1 ), "9." + ; 	
	REPLICATE( "9", nDEC + 1 ) ) )
	IF nNEW > nNUM
		IF nNEW > nNUM + nMAX
			nNEW -= nINCR
		ENDIF
	ENDIF
	IF nNEW < nNUM
		IF ( nNEW + nMAX - nNUM ) < nINCR/100
			nNEW += nINCR
		ENDIF
	ENDIF
return nNEW
ciao

 non mi convince del tutto...
Vediamo:
? ROUNDC( 1.11499999999999, 2 ) ==> 1.12 ???
Chiaramente io mi baso sul mio punto di vista per cui la sostituta della
ROUND() dovrebbe funzionare anche in casi come questi (dato che abbiamo
visto che è possibile).


? ROUNDC( 1.11499999999999, 2 ) ==> 1.12 ???
In RoundB, ho inserito la gestione con nDEC == 0, mentre in RoundC ho
inserito il Val( Str( ) ) di Davide.
FUNCTION ROUNDB( nNUM, nDEC )
	LOCAL cAPP, cNUM, nNEW
	cAPP := TRANSFORM( nNUM, "999999999999999.999999999999999")
	IF nDEC == 0
		nNEW := VAL( LEFT( cAPP, 15 ) )
		cNUM := SUBSTR( cAPP, 17, 1 )
		IF VAL( cNUM ) >= 5
			nNEW++
		ENDIF
	ELSEIF nDEC < 0
		// -----------
	ELSEIF nDEC > 0
		// prendo la parte intera pi— i decimali richiesti
		nNEW := VAL( LEFT( cAPP, 16 + nDEC ) )
		// estraggo il numero da valutare per arrotondare
		cNUM := SUBSTR( cAPP, 16 + nDEC + 1, 1 )
		IF VAL( cNUM ) >= 5
			nNEW += VAL( "0." + ;
			REPLICATE( "0", nDEC - 1 ) + "1" )
		ENDIF
	ENDIF
RETURN VAL( STR( nNEW ) )
FUNCTION ROUNDc( nNUM, nDEC )
	LOCAL nNEW, nMAX, nINCR
	nNEW := ROUND( nNUM, nDEC )
	nINCR := 1 / 10 ^ nDEC
	nMAX := 5 / 10 ^ ( nDEC + 1 )
	nMAX := VAL( TRANSFORM( 5 / 10 ^ ( nDEC + 1 ), "9." +; 		
	REPLICATE( "9", nDEC + 1 ) ) )
	IF nNEW > nNUM
		IF VAL( STR( nNEW ) ) > VAL( STR( nNUM + nMAX ) )
			nNEW -= nINCR
		ENDIF
	ENDIF
	IF nNEW < nNUM
		IF VAL( STR( nNEW + nMAX - nNUM ) ) < VAL( STR(
nINCR/100 ) )


 mentre in RoundC ho inserito il Val( Str( ) ) di Davide.
? ROUNDC( 8.075, 2 ) ==> 8.07 ???
Sorry...


? ROUNDC( 8.075, 2 ) ==> 8.07 ???
che guaio... se si va a controllare cosa ricevono le due funzioni si
nota che entrambe ricevono 8.0749999999999
da cui l'arrotondamento corretto a 8.07
quindi il vero problema è: 
c'è una soluzione alla diversità tra valore inviato e valore ricevuto?
naturalmente, nelle mie prove, ho testato il valore 8.075, senza
problemi
il che la dice lunga sulla attendibilità delle prove, almeno le mie...
ciao

nota che entrambe ricevono 8.0749999999999
stavo apportando la correzione per questo caso, quando vado ad
incappare nel numero 512.055, la cuii rappresentazione in memoria č 
512.054999999999999900
cioč, mentre 8.075 diventa 8.074 seguito da quindici 9,
512.055 diventa 512.054 seguito da tredici 9 e due ZERO!!
che ci sia una influenza anche della lunghezza della parte intera?
in ogni caso, ho corretto la mia RoundB() in modo da aumentare di 1 la
tredicesima cifra decimale, che diventa cosě il mio personalissimo
"grado di precisione" di un numero.
I risultati non si discostano da quelli di Enrico, che continuano ad
essere molto affidabili. 
FUNCTION ROUNDB( nNUM, nDEC )
	LOCAL cAPP, cNUM, nNEW
	nNUM += .0000000000001
	cAPP := TRANSFORM( nNUM, "999999999999999.999999999999999")
	IF nDEC == 0
		nNEW := VAL( LEFT( cAPP, 15 ) )
		cNUM := SUBSTR( cAPP, 17, 1 )
		IF VAL( cNUM ) >= 5
			nNEW++
		ENDIF
	ELSEIF nDEC < 0
		// --------------------
		nNEW := 0
	ELSEIF nDEC > 0
		// prendo la parte intera con i decimali richiesti
		nNEW := VAL( LEFT( cAPP, 16 + nDEC ) )
		// estraggo il numero da valutare per arrotondare
		cNUM := SUBSTR( cAPP, 16 + nDEC + 1, 1 )
		IF VAL( cNUM ) >= 5
			nNEW += VAL( "0." + REPLICATE( "0", nDEC - 1 )
+ "1" )
		ENDIF
	ENDIF
RETURN VAL( STR( nNEW ) )
ciao

 // nel return applico l'idea di Davide: Val( Str( ) )
Per amor del vero, l'idea non č mia.
Ho semplicemente osservato che nel test di Enrico questa soluzione corregge
il problema nella variabile del ciclo FOR.
Ciao.
Davide.

 il che la dice lunga sulla attendibilità delle prove, almeno le mie...
Perché non usi il mio test? Finora si è comportato bene, almeno per i
casi da esso coperti.


 essere molto affidabili.
Bene, ma continuano ad esserci cose come
? ROUNDB( 1.1149999999999, 2 ) ==> 1.12 ???
Nota che sono 13 cifre decimali. La RoundB() non dovrebbe coprire questo
caso? Inoltre ancora non funzionano i casi con il secondo parametro
negativo. Ah, ho appena trovato un caso un po' grave:
? ROUNDB( -123.456, 1 ) ==> -123.3 ???
:-(


 il problema nella variabile del ciclo FOR.
Grazie. Ma io non ricordo se, a mia volta, l'idea l'avevo presa da te o
da qualcun'altro... ?


 da qualcun'altro... ?
questo è il bello di lavorare in team. :-)
Ciao.
Davide.

 questo è il bello di lavorare in team. :-)
Giusto!


Spero che sia la volta buona...
Facci sapere...
Io intanto ho definitivamente abbandonato la funzione con l'utilizzo
della TRANSFORM() e l'ho convertita all'uso della STR(), introducendo
anche la gestione dei numeri negativi e l'arrotondamento della parte
intera.
Questo l'ha resa molto simile a quella di Davide, anche se, rispetto
alla sua ultima versione conosciuta, ho ottenuto la giusta
formattazione e il giusto allineamento.
Considerato il post di Ciro De Pasquale, mi sembra una buona cosa
avere piů di una soluzione, in quanto nell'uso effettivo dell'euro
potrebbero saltare fuori altri casi di imprecisione.
FUNCTION ROUNDB( nNUM, nDEC )
	LOCAL cAPP, cNUM, nNEW, lNEGATIVO := .F., nPOS
	// il default č 2 decimali
	nDEC := IF( nDEC == NIL, 2, nDEC )
	// se il numero č negativo	
	IF nNUM < 0
		lNEGATIVO := .T.
		nNUM := -nNUM
	ENDIF
	cAPP := STR( nNUM )
	nPOS := AT( ".", cAPP )
	cAPP += IF( nPOS == 0, ".", "" )
	// gestisco a parte il caso dello zero
	IF nNUM == 0
	IF nDEC >= 0
		// artifizio per ottenre l'allineamento dello zero
		RETURN VAL( "1." + REPLICATE( "0", nDEC ) ) - 1
	ELSE
		RETURN 0
	ENDIF
	ENDIF
	IF nDEC == 0
		nNEW := VAL( LEFT( cAPP, nPOS - 1 ) )
		cNUM := SUBSTR( cAPP, nPOS + 1, 1 )
		IF VAL( cNUM ) >= 5
			nNEW++
		ENDIF
	ELSEIF nDEC < 0
		nDEC := -nDEC
		nNEW := VAL( LEFT( cAPP, nPOS - 1 ) ) / 10 ^nDEC
		// faccio il round con una chiamata ricorsiva
		nNEW := ROUNDB( nNEW, nDEC - 1 ) * 10^nDEC
		nNEW := INT( nNEW )
	ELSEIF nDEC > 0
		// prendo la parte intera con i decimali richiesti
		nNEW := VAL( LEFT( cAPP, nPOS + nDEC ) )
		// estraggo il numero da valutare per arrotondare
		cNUM := SUBSTR( cAPP, nPOS + nDEC + 1, 1 )
		IF VAL( cNUM ) >= 5
		nNEW += VAL( "0." + REPLICATE( "0", nDEC - 1 ) + "1" )
		ENDIF
		cAPP := STR( nNEW )
		nPOS := AT( ".", cAPP )
		IF LEN( SUBSTR( cAPP, nPOS + 1 ) ) < nDEC
			cAPP += REPL( "0", nDEC - LEN( SUBSTR( cAPP,
nPOS + 1 ) ) )
		ENDIF
		nNEW := VAL( cAPP )
	ENDIF
	nNEW := IF( lNEGATIVO, -nNEW, nNEW )
RETURN nNEW
ciao

 potrebbero saltare fuori altri casi di imprecisione.
Anch'io la penso così.
Ciao.
Davide.

Ho letto l'ultimo post di davide, ed ho verificato la mia funzione nel
caso RoundB( -999, -2 ).
Ho trovato un errore, non nell'arrotondamento, ma nella mancata
ri-valorizzazione di nPOS in presenza di numeri interi.
Comunque, dato che la mia funzione, aldilà della forma, è
essenzialmente la stessa di quella di Davide, ritiro la mia funzione
per non appesantire inutilmente la ricerca.
Naturalmente, questo non vuol dire che ritiro la mia partecipazione,
per cui, se ce ne fosse bisogno, ci sono.
ciao

 intera.
Bene. Ho fatto qualche test e non ho trovato problemi. La velocità con
il secondo parametro positivo è ancora minore di quella di Davide.
Purtroppo se il secondo parametro è negativo ci mette praticamente il
doppio del tempo rispetto al positivo.


 per cui, se ce ne fosse bisogno, ci sono.
Bene.


 per non appesantire inutilmente la ricerca.
sono daccordo con Enrico.
Penso che sia quasi impossibile scrivere un test tale da coprire tutte le
combinazioni, quindi è sempre meglio avere diverse alternative, qualora
capiti la malaugurata ipotesi che le funzioni finora testate falliscano in
qualche combinazione.
Ciao.
Davide.

doppio del tempo rispetto al positivo.
Sì. Ho ottimizzato il codice per nDEC >= 0 e credo di essere diventato
il più veloce ora, almeno rispetto alle ultime versioni che avete reso
disponibili.
Mi resta da ottimizzare il caso di nDEC < 0, ma ora non posso.
FUNCTION ROUNDB( nNUM, nDEC )
	LOCAL cAPP, cNUM, nNEW, lNEGATIVO := .F., nPOS, nAPP
	// il default è 2 decimali
	nDEC := IF( nDEC == NIL, 2, nDEC )
	IF nNUM < 0
		lNEGATIVO := .T.
		nNUM := -nNUM
	ENDIF
	cAPP := STR( nNUM )
	IF ( nPOS := AT( ".", cAPP ) ) == 0
		cAPP += "."
		nPOS := LEN( cAPP )
	ENDIF
	IF nNUM == 0
		IF nDEC >= 0
		RETURN VAL( "1." + REPLICATE( "0", nDEC ) ) - 1
		ELSE
			RETURN 0
		ENDIF
	ENDIF
	IF nDEC == 0
		nNEW := VAL( LEFT( cAPP, nPOS - 1 ) )
		cNUM := SUBSTR( cAPP, nPOS + 1, 1 )
		IF cNUM >= "5"
			nNEW++
		ENDIF
	ELSEIF nDEC < 0
		nDEC := -nDEC
		nAPP := 10^nDEC
		nNEW := VAL( LEFT( cAPP, nPOS - 1 ) ) / nAPP
		nNEW := ROUNDB( nNEW, nDEC - 1 ) * nAPP
		nNEW := INT( nNEW )
	ELSEIF nDEC > 0
		// estraggo il numero da valutare per arrotondare
		cNUM := SUBSTR( cAPP, nPOS + nDEC + 1, 1 )
		IF cNUM >= "5"
			nNEW := VAL( LEFT( cAPP, nPOS + nDEC ) )
			nNEW += VAL( "0." + REPLICATE( "0", nDEC - 1 )
+ "1" )
		ELSE
			IF LEN( SUBSTR( cAPP, nPOS + 1 ) ) < nDEC
				cAPP += REPL( "0", nDEC - LEN( SUBSTR(
cAPP, nPOS + 1 ) ) )
				nNEW := VAL( cAPP )
			ELSE
				nNEW := VAL( LEFT( cAPP, nPOS + nDEC )
)
			ENDIF
		ENDIF
	ENDIF
	nNEW := IF( lNEGATIVO, -nNEW, nNEW )
RETURN nNEW
//RETURN VAL( STR( nNEW ) )
ciao

Ho terminato di ottimizzare il codice della RoundB(), modificando la
parte in cui il secondo parametro è negativo.
Ora la funzione è, su un numero basso di campioni, poco al di sotto o
uguale come velocità a quella di Enrico, che è straordinariamente
costante.
Su numeri alti, la RoundB() è più veloce.
Se può servire, ho eliminato l'elevazione a potenza, sostituendola con
un ciclo FOR.. NEXT, ed ho ridotto al minimo l'utilizzo di AT() e di
STR().
FUNCTION ROUNDB( nNUM, nDEC )
	LOCAL cAPP, cNUM, nNEW, lNEGATIVO := .F., nPOS, JJ






		cNUM := SUBSTR( cAPP, nPOS - nDEC, 1 )
		nNEW := VAL( LEFT( cAPP, nPOS - nDEC - 1 ) )
		IF cNUM >= "5"
			nNEW++
		ENDIF
		FOR JJ := 1 TO nDEC
			nNEW *= 10
		NEXT
	ELSEIF nDEC > 0
		// estraggo il numero da valutare per arrotondare
		cNUM := SUBSTR( cAPP, nPOS + nDEC + 1, 1 )
		IF cNUM >= "5"
			nNEW := VAL( LEFT( cAPP, nPOS + nDEC ) )
			nNEW += VAL( "0." + REPLICATE( "0", nDEC - 1 )
+ "1" )
		ELSE
			IF LEN( SUBSTR( cAPP, nPOS + 1 ) ) < nDEC
				cAPP += REPL( "0", nDEC - LEN( SUBSTR(
cAPP, nPOS + 1 ) ) )
				nNEW := VAL( cAPP )
			ELSE
				nNEW := VAL( LEFT( cAPP, nPOS + nDEC )
)
			ENDIF
		ENDIF
	ENDIF
	nNEW := IF( lNEGATIVO, -nNEW, nNEW )
RETURN nNEW
ciao

 disponibili.
Questi sono i risultati del mio test:
ROUNDB()          0.38
ROUND2()          0.22
ROUND3()          0.39
A questo punto ci serve assolutamente un test definitivo. Stavo
studiando qualcosa ma per ora non sono arrivato a niente di valido. Non
č facile. Comunque, come linea guida, il test non dovrebbe fare calcoli
di alcun genere. Dovrebbe convertire il numero in stringa e poi lavorare
esclusivamente su di essa.


A questo punto ci serve assolutamente un test definitivo.
Hai ragione. 
A seconda della stessa dimensione del numero, del ciclo di iterazioni,
dei casi considerati, si ottengono risultati diversi.
Il mio test è stato piuttosto semplice e basato su un solo numero
123.55 applicato ai tre casi (secondo parametro positivo, nullo,
negativo).
Ma basta semplicemente usare 150000.55 perchè i risultati non siano
più gli stessi (ed ovviamente a scapito della RoundB()... mentre la
Round2() è, come dicevo, straordinariamente costante, ovvero
efficiente in egual misura in ogni caso).
Ok. Buona caccia al test..
ciao

 Su numeri alti, la RoundB() è più veloce.
Bravo, ottimo risultato! In base alle tue osservazioni ho potuto
verificare che la ROUND() originale rallenta quando si specificano un
maggior numero di decimali (ecco perché anche la mia ha questo tipo di
comportamento, dato che alla fine chiama quella). Pertanto la
conclusione è che la ROUND() originale non mi sembra scritta molto bene
(soprattutto perché poi non funziona...).


 Ok. Buona caccia al test..
Comunque il test che cerco riguarda l'affidabilità, non la velocità.
