Visualizzazione dei risultati da 1 a 9 su 9
  1. #1

    [C++] (float/double) Versus (numeri reali)

    Non so se i miei sentimenti siano da considerarsi "strani" ma ultimamente (sviluppando piccole applicazioni di tipo scientifico, per risolvere problemi ingegneristici) sto diventando davvero insofferente all'uso dei tipi di base float/double/ecc...
    Mi piacerebbe avere una classe che rappresenti un numero reale e che abbia, oltre agli ovvi operatori, anche alcuni metodi utili. In particolare dei metodi tostring(), fromstring(), sarebbero utilissimi.
    Inserirei anche una approssimazione nell'overloading dell'operatore ==.
    Per ora sto usando una funzione scritta da me
    codice:
    bool areEqual(double x, double y, double epsilon=0.0000000001)
    {
      if(abs(x-y)<=epsilon)
           return true;
       return false;
    }
    
    //ma comunque a mio avviso
    if (a==0)
    // rimane più leggibile di
    if(areEqual(a, 0.0)
    Inoltre inserirei questa approssimazione anche negli operatori <= e >=
    Detto questo comunque non so se sia una buona idea creare una classe di questo tipo, se per motivi prestazionali convenga comunque utilizzare i tipi di base.
    Poi ammesso che voglia implementarla: dovrei semplicemente mettere come membro privato un double?
    Insomma anche sul'implementazione avrei (almeno) due possibilità:

    Possibilità1
    codice:
    //rappresentato esattamente da un double
    class Real{
    public:
         Real(double d =0.0);
    
    //metodi
    
    private:
         double _value;
    };
    Possibilità2
    codice:
    //due interi rappresentanti la parte intera e la parte decimale
    class Real{
    public:
         Real(double d =0.0);
    
    //metodi
    
    private:
         int _intera, _decimale;
    };
    Qualcuno di voi ha mai implementato qualcosa di simile?
    Ho guardato in giro a varie librerie matematiche, e cen'è davvero di tutti i tipi, ma nessuna è realmente su misura ai miei bisogni

  2. #2
    Non so, se sia conveniente implementare la seconda possibilità; poiché rappresentando il reale con due interi: uno contenente la parte intera e l'altro la parte decimale; stiamo retrocedendo all'uso dalla virgola fissa. Implica uno stroncamento fisso dei decimali di un reale. Mi spiego: i floating pointer dello standard IEEE 754 vengono rappresentati in memoria come se fossero in notazione scientifica, cosa ci consente di fare ciò...

    Se noi abbiamo a disposizione 32bit per rappresentare un reale, possiamo: usare la virgola fissa, considerando il primo bit come quello di segno, i successivi 15 per rappresentare la parte intera, infine i restati 16 per la parte decimale. Naturalmente c'è un grosso svantaggio in questa pratica: non è possibile memorizzare numeri troppo grandi perché il massimo numero rappresentabile in 15bit è 2^15 =32768-1 inoltre se vogliamo porvi un numero molto vicino allo zero, quindi con una lunga parte decimale, saremmo limitati a 16 bit.

    |S| parte intera | decimale |
    |1|1000010111011010|100000000000000|

    Come ho già anticipato i numeri in virgola mobile, ne sono un'esempio i tipi di dato ( float, double ), vengono rappresentati in notazione scientifica, però in base 2.

    |S| Esp | Mantissa |
    |1|10000101|11011010100000000000000|

    Anche in questo caso il primo bit è quello di segno, il successivo byte rappresenta l'esponente(con segno in complemento a due), i restanti 27bit sono la mantissa. Possiamo avere come esponente tutti i numeri che vanno da -127 a 128 e come mantissa 2^27=134217728.
    Quindi facendosi due calcoli, il massimo e il minimo numero memorizzabile sono: +/-mantissa^(+esponente) = +/-2,289101314×10¹⁰⁴⁰(dice la mia calcolatrice). Mentre i due numeri più vicini allo zero sono: +/-mantissa^(-esponente) = 0 (dice la mia calcolatrice ma sicuramente una più potente ti risponderà come si deve).

    Naturalmente, nessuno ci vietta di implementare un sistema di numeri, che segue lo standard IEEE 754, purtuttavia rappresentando i numeri, per fare un esempio in 256bit. Cosi facendo non saremmo più limitati dalle caratteristiche dell'unità di calcolo in virgola mobile di cui disponiamo. In sostanza bisognerebbe proprio fare il lavoro della fpu, ma via software...
    Dante

  3. #3
    Il problema è che l'epsilon non è fissato, ma dipende dai calcoli che stai facendo, per cui introdurlo implicitamente negli operatori di confronto è molto rischioso.
    Amaro C++, il gusto pieno dell'undefined behavior.

  4. #4
    Comunque puoi introdurre dello "zucchero" sintattico con qualche trucco di operator overloading:
    codice:
    #ifndef ABOUT_HPP_INCLUDED
    #define ABOUT_HPP_INCLUDED
    
    #include <cmath>
    
    struct about_eps
    {
        double epsilon;
    
        about_eps(double epsilon=1E-6)
            : epsilon(epsilon)
        {}
    };
    
    struct about_helper
    {
        double sx;
        double epsilon;
        about_helper(double sx, double epsilon)
            : sx(sx), epsilon(epsilon)
        {}
    };
    
    inline about_helper operator<(double sx, about_eps ab)
    {
        return about_helper(sx, ab.epsilon);
    }
    
    inline bool operator>(about_helper ah, double dx)
    {
        return std::abs(dx-ah.sx)<ah.epsilon;
    }
    
    const about_eps about;
    
    #endif
    a questo punto puoi scrivere roba di questo tipo:
    codice:
    #include <iostream>
    #include "about.hpp"
    
    int main()
    {
        double d=1., u=1.-1E-10;
        if(d <about> u)
            std::cout<<"Più o meno uguali!\n";
        return 0;
    }
    Amaro C++, il gusto pieno dell'undefined behavior.

  5. #5
    Quante belle risposte! Allora per prima cosa rispondo a pistilloi
    Originariamente inviato da pistilloi
    Non so, se sia conveniente implementare la seconda possibilità; poiché rappresentando il reale con due interi: uno contenente la parte intera e l'altro la parte decimale; stiamo retrocedendo all'uso dalla virgola fissa. Implica uno stroncamento fisso dei decimali di un reale.
    Ti ringrazio un sacco per l'esaustiva spiegazione ma conosco esattamente questi aspetti, li ho studiati da poco nel corso di calcolo numerico. Avevo preso in considerazione l'eventualità di un numero in virgola fissa perchè pur non avendo le caratteristiche del floating point va comunque bene per tantissime applicazioni, dove approssimare alla quinta cifra decimale è un lusso eccessivo (se devo misurare la potenza di un motore a combustione interna (KW) non me ne frega niente dei decimi di Watt).
    Comunque nonostante questo hai ragione e non è una saggia idea "regredire" quando ho un tipo aviegola mobile lì pronto ad essere utilizzato.

    MItaly
    Il problema è che l'epsilon non è fissato, ma dipende dai calcoli che stai facendo, per cui introdurlo implicitamente negli operatori di confronto è molto rischioso
    A proposito dell'epsilon, avevo pensato di inserirlo nella classe dei numeri reali, in modo che ogni istanza abbia il proprio, impostato ad un valore di default, e poi modificabile caso per caso, in modo che sia adatto al caso. Questo però significherebbe salvare in memoria il doppio dei numeri, che non è una grande idea!
    Per il momento ho una overloading di quella funzione areEqual
    codice:
    bool areEqual(double x, double y)
    {
     //confronto usando un epsilon fissato a livello globale
    }
    In questo modo utilizzo quasi sempre questa funzione e posso cambiare quando voglio la mia "precisione", ma per casi specifici uso quella che prende in input anche epsilon.
    Per quanto riguarda invece i metodi toString(), fromString() posso effettivamente fregarmene.
    Detto questo: bella idea usare l'helper, con quello il codice rimane comunque leggibile e si sorvola il problema.
    Ma mi serve un attimo per capire bene come funziona questo
    if(d <about> u)

  6. #6
    Originariamente inviato da MegaAlchimista
    A proposito dell'epsilon, avevo pensato di inserirlo nella classe dei numeri reali, in modo che ogni istanza abbia il proprio, impostato ad un valore di default, e poi modificabile caso per caso, in modo che sia adatto al caso. Questo però significherebbe salvare in memoria il doppio dei numeri, che non è una grande idea!
    Non solo, quando poi fai un'operazione tra due di questi numeri che epsilon usi per il risultato? Inoltre, a me suona pericoloso portarsi dietro in una cosa che dovrebbe essere un numero "normale" un epsilon, suona poco intuitivo.
    (anche se un'idea del tuo genere l'avevo in mente per fare i conti con grandezze che si portano dietro una "incertezza", propagando le incertezze con le regole per la propagazione degli errori - ma va bene solo in certi casi)
    Per quanto riguarda invece i metodi toString(), fromString() posso effettivamente fregarmene.
    Tra l'altro in C++11 c'è già il metodo to_string (e credo che sia fornito anche in boost, forse tra i lexical_cast).
    Detto questo: bella idea usare l'helper, con quello il codice rimane comunque leggibile e si sorvola il problema.
    Nota che puoi anche fare il confronto con un epsilon arbitrario:
    codice:
    if(v <about_eps(1E-6)> u)
        ...
    Ma mi serve un attimo per capire bene come funziona questo
    if(d <about> u)
    Ci sono un po' di giri loschi per "fare finta" che <about> sia un operatore.

    d è un double, about è di tipo const about_eps, gli operatori minore e maggiore hanno associatività da sinistra a destra; per cui
    codice:
    d <about> u
    viene letto come
    codice:
    (d<about)>u
    Per prima cosa, viene invocato l'operatore < per tipi double e about_eps; questo restituisce un oggetto about_helper in cui vengono memorizzati d e l'epsilon contenuta in about.
    Quindi viene invocato l'operatore > per i tipi about_helper e double; questo dall'oggetto temporaneo about_helper recupera l'epsilon e d e fa il confronto con u, restituendo true se d e u distano meno di epsilon.

    Dato che tutti i metodi sono inline poi qualunque compilatore decente trasformerà tutti questi passaggi in std::abs(d-u)<epsilon.
    Amaro C++, il gusto pieno dell'undefined behavior.

  7. #7
    Ok la mia momentanea confusione derivava dal non aver letto bene gli operatori e non aver notato che < torna about_helper mentre > torna bool,
    Quindi in pratica l'operatore < compie il passaggio intermedio, mentre il risultato che voglio è fornito dall'operatore >.

    Grazie mille davvero mi hai dato un super consiglio al quale non avrei mai pensato: credo proprio che andrò ad implementare le uguaglianze fra double in questo modo

  8. #8
    Originariamente inviato da MegaAlchimista
    Ok la mia momentanea confusione derivava dal non aver letto bene gli operatori e non aver notato che < torna about_helper mentre > torna bool,
    Quindi in pratica l'operatore < compie il passaggio intermedio, mentre il risultato che voglio è fornito dall'operatore >.
    Esattamente.
    Grazie mille davvero mi hai dato un super consiglio al quale non avrei mai pensato: credo proprio che andrò ad implementare le uguaglianze fra double in questo modo
    Amaro C++, il gusto pieno dell'undefined behavior.

  9. #9
    Mentre invece la condizione di <= (o in alternativa >= ) si implementerebbe così?
    codice:
      
    // a<=b
    if( a<b || a <about> b )
    Questo dovrebbe coprire tutti i casi in cui a< b+epsilon.

Permessi di invio

  • Non puoi inserire discussioni
  • Non puoi inserire repliche
  • Non puoi inserire allegati
  • Non puoi modificare i tuoi messaggi
  •  
Powered by vBulletin® Version 4.2.1
Copyright © 2026 vBulletin Solutions, Inc. All rights reserved.