Newsgroup: it.comp.lang.vo-clipper
Titolo: Problemi di arrotondamento

Cari amici, allora come risolviamo i seguenti errori di arrotondamento
gentilmente offerti da Clipper (almeno la 5.2e, ditemi se fa la stessa
cosa con altre versioni):
? TRANSFORM( 8.235, "9.99" )     => 8.23 ???
? STR( 8.235, 5, 2 )             => 8.23 ???
? ROUND( 1.265, 2 )              => 1.26 ???
EMG
PS. VO come si comporta?
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

Leggermente diverso con Clipper 5.3b
Almeno la funzione ROUND mi rassicura:
*********** PRG
LOCAL nPippo := 8.235
? TRANSFORM( nPippo, "9.99" )
? STR( nPippo, 5, 2 )
? ROUND( nPippo, 2 )
? " -------------- "
? " Esempio di EMG "
? TRANSFORM( 8.235, "9.99" )
? STR( 8.235, 5, 2 )
? ROUND( 1.265, 2 )
*********** FINE PRG
************ OUTPUT
8.23
 8.23
         8.24
 --------------
 Esempio di EMG
8.23
 8.23
         1.27
************ FINE OUTPUT


 Almeno la funzione ROUND mi rassicura:
Allora non ci siamo proprio. Qualche proposta su come risolvere
radicalmente la cosa? Io finora non ho trovato alcuna soluzione valida,
invasiva o non invasiva che sia.
EMG


In VO da questi risultati :
   ? Transform( 8.235, "9.99" )  => 8.24
   ? Str( 8.235, 5, 2 )  => 8.23
   ? Round( 1.265, 2 ) => 1.27
Ciao
Enio


Uhm... questo mi porterebbe a pensare che il risultato della funzione
Str() sia corretto? Cioč la funzione Str() tronca, non arrotonda? Perň
sulla Norton Guide del Clipper c'č scritto:
   If <nLength> is less than the number of decimal digits
   required for the decimal portion of the returned string, CA-Clipper
   rounds the number to the available number of decimal places.
Quindi mi sembra che il risultato della Str() sia errato anche in VO.
Sembrano corretti gli altri due, almeno per quei valori. Perň bisogna
tenere presente che anche in Clipper con molti valori il risultato č
corretto.
EMG


? ROUND( 1.265, 2 )              => 1.26 ???
Mi e' venuto freddo leggendo questo !!! Se non fosse scritto da EMG
non ci crederei. Adesso sono stanchissimo, ma domattino provo anche io
.... e comincio a pregare.
Cavolo, se e' cosi' sono nella palta ! Sigh.
Ciao.Alberto.
-------------------------------------------------------------
Alberto Rubinelli           Mail : 
alb...@retrocomputing.net
A2 SISTEMI                  Http : 
www.retrocomputing.net
Via Costantino Perazzi 22   Tel 0321 640149
28100 NOVARA (NO) - ITALY   Fax 0321 391769
ICQ : 49872318              Odigo : 5269083

 Cavolo, se e' cosi' sono nella palta ! Sigh.
Lascia perdere la "palta" :-) e concentrati sul problema. Hai letto la
mia proposta di soluzione parziale?
EMG


mia proposta di soluzione parziale?
Certo, niente da dire sulla funzionalita' dell'idea  .... ma mi sembra
a dir poco assurdo che clipper abbia di questi bug .... mi chiedo come
ho fatto a non farci caso fino ad adesso. Certo, il 90% delle volte si
lavora con arrotondamento all'intero nella contabilita' in lire ... ma
nelle quantita' a volte utilizzavo proprio le picture indicate :((
E poi clipper lo usano mica solo in italia ... come hanno fatto fino
ad adesso tedeschi, americani, e tutti quelli che usano le due cifre
decimali ?


 decimali ?
Infatti. Penso che le risposte possano essere solo due: o mi sto
sbagliando io oppure il problema, in realtà, si manifesta in situazioni
talmente particolari da poterne trascurare l'impatto nei programmi
reali. Per l'appunto ho deciso, per il momento, di accantonarlo...


 ha scritto nel
messaggio news:3BDD6E88...@emagsoftware.it...


 sino alla soluzione della cosa..
Per me va bene. Vogliamo riprendere il discorso dalla soluzione parziale
che avevo proposto?
EMG


 ha scritto nel
messaggio news:3BE13A0E...@emagsoftware.it...


? ROUND( 1.265, 2 )              => 1.26 ???
il problema proposto riguarda, a mio avviso, solo la funzione ROUND
perchè non vedo errore nel comportamento della Transform e della Str,
che non sono chiamate a fare alcun arrotondamento.
Ora, io ho provato a fare la ROUND del valore 8.235 ed il risultato è
stato sempre 8.24 sia usando SET DECIMALS che non usandolo, sia usando
SET FIXED che non usandolo.
Con il valore 8.234, ottengo invece 8.23
ciao

 m_x=int(p_num*100)/100
non mi è molto chiaro il senso di questo test, 
e non capisco perchè un numero con 3 cifre decimali debba essere
conservato in un campo con 5 cifre decimali.
Forse mi è sfuggito qualcosa ed il problema è relativo solo ai valori
presi dai campi di un dbf? e con quali caratterisitiche?
ciao.

 ha scritto nel messaggio
news:9to2utsmvr56klntn...@4ax.com...


cosa stai usando, Diego ?
hai ragione, scusa.
Sto usando Clipper 5.2e
ciao

 ha scritto nel messaggio
news:9rrkkj$mja$1...@fe2.cs.interbusiness.it...


 ha scritto nel messaggio
news:kip2utgk8bipa2rgk...@4ax.com...


più di due decimali...
avevo sentito qualcosa del genere, ma mi pareva che riguardasse solo
la fase di "convivenza" con le lire...
Dal primo gennaio 2002, ha senso avere più di 2 decimali nei campi di
un dbf?
a me pare di no, ma se qualcuno ne sa di più....
e comunque, hai verificato se la Round() ti dà risultati corretti come
succede a me?
ciao

 ha scritto nel messaggio
news:9v23utgj81r4rqb3k...@4ax.com...


prova a fare il round(1.265,2) dovrebbe darti 1.27 e non 1.26 come temo darà
Hai ragione, il problema si verifica con il valore 1.265, e non con il
valore 8.235 che usavo io
Comunque, ho trovato una possibile soluzione.
Innanzitutto, va notato che se mettiamo in una variabile il risultato
di 5.5 * 0.23 (ancora 1.265) e ne facciamo il Round, otteniamo
correttamente 1.27 !!!
Vi è quindi un problema nella valorizzazione di una variabile in
memoria quando ci sono cifre decimali, da cui discende un
arrotondamento non sempre preciso.
Per cui, se ora assegno ad una variabile il valore 1.265 e gli sommo
una costante (nel mio caso ho provato 7), e se poi decremento la
variabile sempre del valore della costante, e a quel punto faccio la
Round(), il risultato è corretto, cioè 1.27!!!
Può essere un primo passo verso una soluzione forse semplice, anche se
non pulitissima (aggiungere una costante, e poi detrarre la costante
prima di fare una Round() ) ???
ciao

   num=num+0.001
Questo č il primo errore. Cosě facendo tu introduci degli errori
supplementari che non hanno niente a che vedere con quelli di
arrotondamento delle funzioni che stiamo testando (ROUND(), STR() e
TRANSFORM()). Mi spiego meglio: i numeri da te generati, anche se
sembrano essere 0.002, 0.003, 0.004, etc. in realtŕ sono dei numeri
molto vicini ma non uguali a quelli. Questo succede proprio a causa
dell'approssimazione dei numeri floating-point che non permette di
rappresentare il numero 0.001 con una precisione sufficiente al nostro
test. Faccio un esempio:
FUNCTION MAIN()
    LOCAL i, n := 0
    FOR i = 1 TO 100000
        n += 0.001
        IF i / 1000 != n
            EXIT
        ENDIF
    NEXT
    ? i, n, n = 0.009
    RETURN NIL
Questo codice a me (Clipper 5.2e) si ferma dopo sole 9 iterazioni.
Questo perché l'errore di approssimazione della costante 0.001,
sommandosi, provoca, dopo 9 iterazioni, lo "sballamento" del valore di
n.
Per il resto, non ho capito molto bene come funziona il tuo, forse
eccessivamente, complesso test. Ne propongo uno di gran lunga piů
semplice (e piů veloce) che, spero, dia indicazioni piů valide:
FUNCTION MAIN()
    TEST( 1 )    // ROUND()
    TEST( 2 )    // STR()
    TEST( 3 )    // TRANSFORM()
    RETURN NIL
STATIC FUNCTION TEST( nTipo )
    LOCAL nErr := 0
    LOCAL nTot := 0
    LOCAL i
    FOR i = 0.005 TO 99.995 STEP 0.01
        i = VAL( STR( i ) )
        DO CASE
            CASE nTipo = 1 .AND. ROUND( i, 2 ) = INT( i * 100 ) / 100
                nErr++
            CASE nTipo = 2 .AND. VAL( STR( i, 5, 2 ) ) = INT( i * 100 )
/ 100
                nErr++
            CASE nTipo = 3 .AND. VAL( TRANSFORM( i, "9.99" ) ) = INT( i
* 100 ) / 100
                nErr++
        ENDCASE
        nTot++
    NEXT
    DO CASE
        CASE nTipo = 1
            ? "ROUND()    ", nErr, nTot, LTRIM( STR( nErr / nTot * 100 )
) + "%"
        CASE nTipo = 2
            ? "STR()      ", nErr, nTot, LTRIM( STR( nErr / nTot * 100 )
) + "%"
        CASE nTipo = 3
            ? "TRANSFORM()", nErr, nTot, LTRIM( STR( nErr / nTot * 100 )
) + "%"
    ENDCASE
    RETURN NIL
Questo testa tutti i casi in cui il numero finisce con 5 millesimi e
confronta tale valore (che, grazie all'accoppiata VAL( STR() ), produce
senza l'accumulazione degli errori che ho evidenziato sopra) arrotondato
ai centesimi con lo stesso valore troncato.
Ebbene, il risultato mi sembra molto piů verosimile: un 5 o 6 per cento
di errore sul totale. Interessante č il fatto che STR() e TRANSFORM()
sembrano produrre lo stesso identico numero di errori. Ho anche ripetuto
la prova con l'arrotondamento ai decimi e i risultati sono simili (un
7%, ma la ROUND(), in questo caso, non produce alcun errore). Ho anche
provato ad utilizzare un indice intero per il FOR e a calcolare il
numero frazionario in base ad esso:
FOR j = 5 TO 99995 STEP 10
    i = j / 1000
Che ne pensate di tutto ciň?


 che 'sembrerebbe' non avere più il problema
Scusa, ma a che serve tutto quel popò di roba? Cos'hanno che non vanno
quelle, semplicissime, da me proposte (che aggiungono semplicemente
quell'epsilon)?


 SET FIXED che non usandolo.
E' normale. Come ho fatto vedere nel mio test, l'errore accade solo in
una piccola percentuale di casi.
EMG


 a me pare di no, ma se qualcuno ne sa di più....
Si, può averne almeno in due casi. Il primo riguarda una serie di norme
imposte (non mi pronuncio sulla loro validità) in determinati ambienti e
situazioni. Il secondo quando si sta trattando, ad esempio, materiale
dal costo unitario inferiore al centesimo di Euro (ma qualcuno mi ha
detto che questa cosa dovrebbe andare a sparire, però intanto...).


 prima di fare una Round() ) ???
Ripropongo la mia soluzione (che forse vi era sfuggita):
#define EPS 0.000000000000001
#define TRANSFORM( xValue, cPicture ) Transform( If( ValType( xValue ) =
"N", xValue + EPS, xValue ), cPicture )
#define STR( nValue, nDigits, nDecimals ) Str( nValue + EPS, nDigits,
nDecimals )
#define ROUND( nValue, nDecimals ) Round( nValue + EPS, nDecimals )
FUNCTION MAIN()
    ? TRANSFORM( 8.235, "9.99" )
    ? TRANSFORM( "peppe", "@!" )
    ? STR( 8.235, 5, 2 )
    ? ROUND( 1.265, 2 )
    ? TRANSFORM( 8.234999999, "9.99" )
    ? TRANSFORM( "peppe", "@!" )
    ? STR( 8.234999999, 5, 2 )
    ? ROUND( 1.264999999, 2 )
    RETURN NIL


rounds the number to the available number of decimal places.
No, traduci male tu.
La sintassi è questa:
STR(<nNumber>, [<nLength>], [<nDecimals>]) --> cNumber
dove
<nLength> is the length of the character string to return, including
decimal digits, decimal point, and sign.
<nDecimals> is the number of decimal places to return.
Per cui, la nota che riporti avverte che nel caso si chieda una
lunghezza della stringa minore del numero di decimali richiesti,
Clipper provvederà a fare qualcosa sulla parte decimale (che, di
fatto, a me risulta tradursi nella visualizzazione di asterischi).
Quindi se chiedi str( 1.265, 5, 2 ) ottieni 1.27, ma se chiedi
str(1.265,2,3) ti restituisce asterischi.
ciao

 str(1.265,2,3) ti restituisce asterischi.
Ok, ma quello che volevo evidenziare (e correggimi se sbaglio) è che la
STR() (come anche la TRANSFORM()) nella maggioranza dei casi arrotonda
il numero e quindi immagino che sia questo il comportamento corretto.
Prova, ad esempio con
? STR( 12.345, 6, 2 ) ==> 12.35 OK!


il numero e quindi immagino che sia questo il comportamento corretto.
Se è per questo, io ritengo che la funzione ROUND() sia la funzione
specifica per gli arrotondamenti, mentre immagino che STR() e
TRANSFORM() non facciano che applicare, al loro interno, un SET FIXED
ON e un SET DECIMALS TO (n).
Infatti se, avendo la variabile A = 1.265, imposto SET FIXED ON e SET
DECIMALS TO 2, ottengo che il "? A" mi stampa 1.27, ovvero esattamente
quello che è il risultato della STR() e della TRANSFORM() (e che,
purtroppo, non è il risultato della ROUND()).
Ma, a parte queste osservazioni che tutto sommato lasciano il tempo
che trovano, il problema di questo thread è se il comportamento
anomalo di ROUND() da te evidenziato (e,se si vuole, anche di STR() e
di TRANSFORM()), pur nella sua marginalità, può avere sensibili (e
reali) effetti negativi nei nostri programmi.
Occorrerebbe sapere se, al di là del caso scolastico A = 1.265, nella
pratica si verifica mai un caso del genere.
Perchè, se non si verifica, sarebbe pressochè inutile cercare una
soluzione, ed infatti mi sembra che tu stesso per ora ti sei orientato
in questo modo.
Quindi, personalmente attendo che, dal contributo di Andrea o di
altri, emerga qualcosa di più specifico e riscontrato su casi concreti
per valutare le eventuali soluzioni da chiunque prospettate, che
andrebbero adeguatamente testate.
ciao

 ha scritto nel messaggio
news:f365utolnlglcnj7g...@4ax.com...

 ha scritto nel
messaggio news:3BE00476...@emagsoftware.it...

Penso che non ti stia sbagliando. Me ne sono accorto anch'io di recente
purtroppo.
Tra l'altro ora con l'euro il problema è molto più tangibile.
A questo punto ho preso una decisione drastica :
? Transform(Round(1.236,2), "@EZ 9.99")
almeno così dovrei essere sicuro che l'arrotondamento sia coerente.
per troncare userei l'apposita funzione in posto di Round().
Ciao,
    Fabrizio Malaguti

 ha scritto nel
messaggio news:8MAE7.6042$zl4.1...@news2.tin.it...

Opps. la funzione incriminata è proprio la Round() ?
Aiuto!!!
Ciao,
    Fabrizio Malaguti.

 andrebbero adeguatamente testate.
Bene, abbiamo detto la stessa cosa. :-)


 almeno cosě dovrei essere sicuro che l'arrotondamento sia coerente.
Sempre che la Round() ti assista (in Clipper non ti assiste...).


 Aiuto!!!
Appunto. Hai seguito il resto del thread? Prova le soluzioni proposte e
facci sapere.


Sembrano corretti gli altri due, almeno per quei valori. 
Non vorrei sembrare un guastafeste.
C'e' addirittura una norma UNI che riguarda l'arrotondamento; e
probabilmente deve esserci anche qualche norma ISO. 
La norma UNI 4729 precisa che viene maggiorata di 1 la cifra che ne
precede una maggiore di 5 (ad es. 1,26 viene arrotondata a 1.264;
1.266 viene arrotondata a 1,27). Quando pero' l'ultima cifra da
indicare segue un 5 esatto, si segue la "regola del numero pari": si
arrotonda al pari più vicino maggiorando di 1 la cifra che precede il
5 solo se quella cifra e' dispari (1,265 si arrotonda a 1,26 perche' 6
e' pari; 1,275 si arrotonda a 1,28 perche' 7 e' dispari). Pertanto 
    ? Round( 1.265, 2 ) => 1.27
e' errato.
Ciao,
Alex (5630)

 probabilmente deve esserci anche qualche norma ISO.
Questo che dici è molto interessante (anche se non riesco a capire il
motivo di questa differenza tra pari e dispari) ma non c'entra niente
con il problema che stiamo trattando che invece riguarda un caso di
difformità dalle specifiche dichiarate. Infatti dalle Norton Guide del
Clipper riguardanti la funzione ROUND() troviamo
Digits between five to nine, inclusive, are rounded up.
Digits below five are rounded down.
Dato che invece la funzione si comporta, in alcuni casi, in modo
diverso, questo comportamento si deve chiaramente intendere come un bug.


e' pari; 1,275 si arrotonda a 1,28 perche' 7 e' dispari).
La tua indicazione è senz'altro importante.
Tuttavia, purtroppo, non risolve il problema.
Per accorgersene basta fare questa prova:
? Round( 8.235, 2 ) => 8.24
? Round( 8.245, 2 ) => 8.24
? Round( 8.255, 2 ) => 8.26
? Round( 8.265, 2 ) => 8.27
l'ultimo caso, secondo la norma da te citata, dovrebbe dare 8.26
ed invece dà 8.27
quindi, il comportamento della Round() rimane ancora oscuro...
ciao.

difformità dalle specifiche dichiarate. 
Ero intervenuto per dire che le specifiche del linguaggio dovrebbero
prevedere l'osservanza degli standard internazionali; le norme
sull'arrotondamento, in questo caso.
Ciao
Alex (5630)

 sull'arrotondamento, in questo caso.
Se fossero giuste, sarei d'accordo con te. Ma, a parte il fatto che non
credo lo siano, purtroppo il Clipper, a quanto pare, non le segue.


 Non ne vedo il motivo. 
I motivi che spinsero l'UNI a emanare quella norma te li diro'
martedi', quando potro' consultare in biblioteca la norma 4729.
Suppongo dipenda dal fatto che il decimale 5 sia equidistante e
dall'intero inferiore e da quello superiore. Un numero col decimale 5
dovrebbe percio' essere approssimato a random meta' delle volte
all'intero inferiore e meta' delle volte a quello superiore. Poiche'
e' scomodo lanciare ogni volta una moneta per fare la decisione, si
osserva la cifra che precede il 5, la cui occorrenza rispetto alla
cifra seguente e' casuale. Naturalmente e' la "regola del pari" e'
arbitraria, si poteva scegliere anche quella "del dispari". Ma gli
enti tipo l'UNI servono per uniformare proprio le regole arbitrarie
come questa.
La regola e' presente tuttavia anche nei testi di matematica,
soprattutto di aritmetica. Mi limito a trascriverti quanto viene in un
testo che ho a portata di mano: "Statistica" di Murray R.Spiegel, Etas
libri, 2.a edizione:
   [...] Nell'arrotondare 72,465 ai primi due decimali, tuttavia, ci
   troviamo di fronte ad una difficolta', poiche' 72,465 e'
   equidistante rispetto a 72,46 e 72,47. E' invalsa la pratica in
   tali casi di arrotondare alla cifra pari che precede il 5. Cosi'
   72,465 viene arrotondato a 72,46; 183,575 viene arrotondato a
   183,58; 116500000 viene arrotondato a 116000000 (arrotondamento al
   milione piu' prossimo). Questa tecnica e' particolarmente utile per
   minimizzare _gli errori cumulativi dell'arrotondamento_ quando si
   compie un gran numero di operazioni 
   [Spiegel, op.cit., § Arrotondamento dei dati, pag. 3]
Riporto ora l'esercizio 4 di pag. 7 dello stesso testo per mostrare in
cosa consistano quegli "errori cumulativi dell'arrotondamento":
   Esercizio 4. Sommare i numeri 4,35; 8,65; 2;95; 12,45; 6,65; 7,55;
   9,75 (a) direttamente (b) arrotondando al primo decimale secondo la
   convenzione del "numero intero pari", (c) arrotondando cosi' da
   aumentare il numero semplice prima del 5.
   Soluzione:
           (a)       (b)     (c)
           4,35      4,4     4,4   
           8,65      8,6     8,7
           2,95      3,0     3,0
          12,45     12,4    12,5
           6,65      6,6     6,7
           7,55      7,6     7,6
           9,75      9,8     9,8
          -----     ----    ---- 
   Totale 52,35     52,4    52,7
   Si noti che il procedimento (b) e' superiore al procedimento (c)
   poiche' gli errori cumulativi dell'arrotondamento vengono
   minimizzati nel procedimento (b).
   [Spiegel, op.cit., pag. 7]
HTH
Alex (5630)

    Totale 52,35     52,4    52,7
Chiarissimo, grazie. Naturalmente ho fatto ulteriori test (ho la testa
dura :-) ) e ho visto che, in effetti, nel caso peggiore (quando tutte
le cifre da arrotondare sono pari) i due metodi si equivalgono ma in
tutti gli altri casi risulta migliore il (b).
Quindi, se volessimo ridefinire una volta per tutte la ROUND(), la STR()
e la TRANSFORM() dovremmo tenere conto di questo fatto. Fatemi
pensare... ecco una proposta per quanto riguarda la funzione ROUND():
FUNCTION MAIN()
    LOCAL aVal := { 4.35, 8.65, 2.95, 12.45, 6.65, 7.55, 9.75 }
    LOCAL nTotA := 0
    LOCAL nTotB := 0
    LOCAL nTotC := 0
    LOCAL i
    FOR i = 1 TO LEN( aVal )
        nTotA += aVal[ i ]
        nTotB += ROUND2( aVal[ i ], 1 )
        nTotC += ROUND( aVal[ i ], 1 )
    NEXT
    ? nTotA, nTotB, nTotC
    RETURN NIL
#define EPS 0.00000000000001
FUNCTION ROUND2( nValue, nDecimals )
    LOCAL nMult := 1
    LOCAL i
    FOR i = 1 TO nDecimals; nMult *= 10; NEXT
    nValue = ( nValue + EPS ) * nMult
    IF INT( nValue ) % 2 != 0; nValue += 0.5; ENDIF
    RETURN INT( nValue ) / nMult
Provatela e fatemi sapere. Sicuramente è migliorabile e potrebbe
nascondere anche qualche bug.



OK ci provo.
ciao,
    Fabrizio Malaguti


Non vorrei entrare a sproposito in un thread cosě lungo, ma per quanto
riguarda le valute, l'euro in particolare, credo valga la regola che il
cinque millesimale si approssima al centesimo superiore:
3,465 = 3,47
Ciao a tutti.


Ah! Per favore, qualcuno puň informarsi una volta per tutte come si deve
arrotondare? Mica dovremmo utilizzare due funzioni di arrotondamento
diverse, no? O si?...
