Domanda

Ecco gli obiettivi che sto cercando di realizzare:

  • Ho bisogno di mettere in valigia a 32 bit IEEE galleggia in 30 bit.
  • voglio fare questo diminuendo la dimensione di mantissa da 2 bit.
  • L'operazione di per sé dovrebbe essere il più veloce possibile.
  • Sono consapevole del fatto che una certa precisione sarà perso, e questo è accettabile.
  • Sarebbe un vantaggio, se l'operazione non avrebbe rovinato i casi particolari come Snan, QNAN, infiniti, ecc, ma sono pronto a sacrificare questa velocità sopra.

Credo che questa domanda si compone di due parti:

1) Posso semplicemente cancellare i bit meno significativi mantissa? Ho provato questo, e finora funziona, ma forse sto in cerca di guai ... Qualcosa di simile:

float f;
int packed = (*(int*)&f) & ~3;
// later
f = *(float*)&packed;

2) Se ci sono casi in cui 1) fallirà, allora quale sarebbe il modo più veloce per raggiungere questo obiettivo?

Grazie in anticipo

È stato utile?

Soluzione 4

Non è possibile selezionare una delle risposte come quella definitiva, perché la maggior parte di loro hanno informazioni valide, ma non è proprio quello che stavo cercando. Quindi mi limiterò a riassumere le mie conclusioni.

Il metodo per la conversione ho postato nella parte della mia domanda 1) è chiaramente sbagliato da C ++ standard, in modo che altri metodi per bit estratto del galleggiante dovrebbe essere usato.

E, cosa più importante ... per quanto mi pare di capire dalla lettura delle risposte e altre fonti su IEEE754 galleggianti, è ok per cadere i bit meno significativi da mantissa. Sarà in gran parte effetto solo precisione, con una sola eccezione: Snan. Poiché SNAN è rappresentato dall'insieme esponente a 255, e mantissa! = 0, ci possono essere situazioni in cui sarebbe mantissa <= 3, e rilasciando ultimi due bit convertirebbe Snan a +/- infinito. Ma poiché Snan non vengono generati durante operazioni in virgola mobile sulla CPU, la sua sicurezza in ambiente controllato.

Altri suggerimenti

In realtà si violano le rigide regole di aliasing (sezione 3.10 dello standard C ++) con questi calchi reinterpretare. Questo sarà probabilmente saltare in aria in faccia quando si accende le ottimizzazioni del compilatore.

standard di C ++, sezione 3.10 paragrafo 15 dice:

  

Se un programma tenta di accedere al valore memorizzato di un oggetto attraverso un Ivalue di diverso da uno dei seguenti tipi il comportamento è indefinito

     
      
  • il tipo dinamico dell'oggetto,
  •   
  • una versione cv-qualificato del tipo dinamico dell'oggetto,
  •   
  • un tipo simile al tipo dinamico dell'oggetto,
  •   
  • un tipo che è il segno o tipo senza segno corrispondente al tipo dinamico dell'oggetto,
  •   
  • un tipo che è la firma o tipo senza segno corrispondente ad una versione cv qualificato di tipo dinamico dell'oggetto,
  •   
  • un tipo di aggregato o unione che include uno dei tipi indicati sopra tra i suoi componenti (compresi, ricorsivamente, un membro di un subaggregate o unione contenuta),
  •   
  • un tipo che è una (possibilmente cv-qualificato) tipo di classe di base di tipo dinamico dell'oggetto,
  •   
  • un char o unsigned char tipo.
  •   

In particolare, 3.10 / 15 non permette di accedere ad un oggetto galleggiante tramite un lvalue di tipo unsigned int. Io in realtà mi sono morso da questo. Il programma che ho scritto ha smesso di funzionare dopo aver acceso ottimizzazioni. A quanto pare, GCC non si aspettava un lvalue di tipo float di alias un lvalue di tipo int che è una fiera assunzione da parte di 3.10 / 15. Le istruzioni ricevuti mescolate in giro da l'ottimizzatore sotto il come-se regola sfruttando 3.10 / 15 e ha smesso di funzionare.

Con il seguente ipotesi

  • float corrisponde veramente un 32bit IEEE flottante,
  • sizeof (float) == sizeof (int)
  • int unsigned ha alcun bit di riempimento o rappresentazioni trap

si dovrebbe essere in grado di farlo in questo modo:

/// returns a 30 bit number
unsigned int pack_float(float x) {
    unsigned r;
    std::memcpy(&r,&x,sizeof r);
    return r >> 2;
}

float unpack_float(unsigned int x) {
    x <<= 2;
    float r;
    std::memcpy(&r,&x,sizeof r);
    return r;
}

Questa non soffre dal "3.10-violazione" ed è in genere molto veloce. Almeno GCC tratta memcpy come funzione intrinseca. Nel caso in cui non è necessario le funzioni per lavorare con NANS, infiniti o numeri con elevatissima magnitudo si può anche migliorare la precisione, sostituendo "r >> 2" con "(r + 1) >> 2":

unsigned int pack_float(float x) {
    unsigned r;
    std::memcpy(&r,&x,sizeof r);
    return (r+1) >> 2;
}

Questo funziona anche se cambia l'esponente causa di un overflow mantissa perché IEEE-754 codifica mappe valori consecutivi virgola mobile a interi consecutivi (ignorando +/- zero). Questa mappatura approssima in realtà un logaritmo abbastanza bene.

ciecamente cadere i 2 bit meno significativi del galleggiante può fallire per piccolo numero di particolari codifiche NaN.

Un NaN è codificato come esponente = 255, mantissa! = 0, ma IEEE-754 non dice nulla su cui devono essere utilizzati i valori mantiassa. Se il valore di mantissa è <= 3, si potrebbe trasformare un NaN in un'infinità!

Si dovrebbe incapsulare in una struttura, in modo da non mescolare accidentalmente l'uso del galleggiante Tagged con regolare "unsigned int":

#include <iostream>
using namespace std;

struct TypedFloat {
    private:
        union {
            unsigned int raw : 32;
            struct {
                unsigned int num  : 30;  
                unsigned int type : 2;  
            };
        };
    public:

        TypedFloat(unsigned int type=0) : num(0), type(type) {}

        operator float() const {
            unsigned int tmp = num << 2;
            return reinterpret_cast<float&>(tmp);
        }
        void operator=(float newnum) {
            num = reinterpret_cast<int&>(newnum) >> 2;
        }
        unsigned int getType() const {
            return type;
        }
        void setType(unsigned int type) {
            this->type = type;
        }
};

int main() { 
    const unsigned int TYPE_A = 1;
    TypedFloat a(TYPE_A);

    a = 3.4;
    cout << a + 5.4 << endl;
    float b = a;
    cout << a << endl;
    cout << b << endl;
    cout << a.getType() << endl;
    return 0;
}

I non può garantire la sua portabilità però.

Come molta precisione cosa hai bisogno? Se float a 16 bit è sufficiente (sufficiente per alcuni tipi di grafica), quindi float a 16 bit di ILM ( "metà"), parte di OpenEXR è grande, obbedisce tutti i tipi di regole (http://www.openexr.com/ ), e avrete un sacco di spazio rimane dopo si pack in una struct.

D'altra parte, se si conosce l'intervallo approssimativo di valori che stanno andando a prendere, si dovrebbe considerare punto fisso. Sono più utile di quanto si creda.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top