2010-11-21 15 views
15

Attualmente sto attuare una tabella hash in C++ e sto cercando di fare una funzione di hash per i galleggianti ...funzione hash per carri

stavo andando per il trattamento di carri come numeri interi imbottitura i numeri decimali, ma poi ho capito che probabilmente avrei raggiunto l'overflow con grandi numeri ...

C'è un buon modo per fare hash float?

Non devi darmi la funzione direttamente, ma mi piacerebbe vedere/capire i concetti diversi ...

Note:

  1. non ne ho bisogno a essere molto veloce, distribuito equamente, se possibile.

  2. Ho letto che i float non devono essere sottoposti a hash a causa della velocità del calcolo, qualcuno può confermare/spiegare questo e darmi altri motivi per cui i float non dovrebbero essere sottoposti a hash? Io non capisco il motivo per cui (oltre alla velocità)

risposta

15

Dipende dall'applicazione ma la maggior parte del tempo non deve essere sottoposto a hash, poiché l'hashing viene utilizzato per la ricerca rapida di corrispondenze esatte e la maggior parte dei float sono il risultato di calcoli che generano un float che è solo un'approssimazione della risposta corretta. Il metodo solitamente per verificare l'uguaglianza fluttuante consiste nel verificare se rientra in qualche delta (in valore assoluto) della risposta corretta. Questo tipo di controllo non si presta alle tabelle di ricerca con hash.

EDIT:

Normalmente, a causa di errori di arrotondamento e limitazioni intrinseche di aritmetica in virgola mobile, se ci si aspetta che i numeri in virgola mobile a e b dovrebbe essere uguali tra loro, perché la matematica dice così, è necessario selezionare relativamente piccolo delta > 0 e quindi dichiarare a e b uguale a abs(a-b) < delta, dove abs è la funzione del valore assoluto. Per ulteriori dettagli, vedere this article.

Ecco un piccolo esempio che illustra il problema:

float x = 1.0f; 
x = x/41; 
x = x * 41; 
if (x != 1.0f) 
{ 
    std::cout << "ooops...\n"; 
} 

A seconda della piattaforma, compilatore e ottimizzazione livelli, questo può stampare ooops... al vostro schermo, il che significa che l'equazione matematica x/y * y = x non necessariamente tenere su il tuo computer.

Ci sono casi in cui l'aritmetica in virgola mobile produce risultati esatti, ad es. interi e razionali di dimensioni ragionevoli con denominatori di potenza di 2.

+0

Potresti spiegarci un po 'di più? "Il metodo solitamente per verificare l'uguaglianza fluttuante consiste nel verificare se rientra in qualche delta (in valore assoluto) della risposta corretta." – Pacane

+0

+1 - La risposta non è di farlo in primo luogo. Non utilizzare i float come chiavi nelle mappe o nelle tabelle hash; prima o poi ti imbatterai in problemi. –

+2

@Leo Davidson So che correrò nei guai, l'obiettivo di questo esercizio è quello di trovare esattamente quando ;-) – Pacane

4
unsigned hash(float x) 
{ 
    union 
    { 
     float f; 
     unsigned u; 
    }; 
    f = x; 
    return u; 
} 

comportamento Tecnicamente non definito, ma la maggior parte dei compilatori supportano questo. Soluzione alternativa:

unsigned hash(float x) 
{ 
    return (unsigned&)x; 
} 

Entrambe le soluzioni dipendono dalla endianness della vostra macchina, così per esempio su x86 e SPARC, produrranno risultati diversi. Se ciò non ti infastidisce, usa una di queste soluzioni.

+2

Non ci sono alcune funzioni standard che possono essere utilizzate per afferrare la mantissa e l'esponente? Non sono un tipo di ragazzo fluttuante, o molto di C++, quindi mi stavo solo chiedendo ... –

+0

@ Greg: Non per quanto ne so. Perché vorresti prendere la mantissa e l'esponente, comunque? Un float è a 32 bit, perché non interpretarlo semplicemente come un unsigned? Finché si evitano i NaN, si dovrebbe * star bene ... – fredoverflow

+2

@FredOverflow: stavo solo supponendo che afferrare la mantissa e l'esponente separatamente produca meno risultati dipendenti dalla macchina e dal compilatore. Dipenderei ancora dalle dimensioni della mantissa e dall'esponente che potrebbe rivelarsi solo come compilatore e dipendente dalla macchina. –

10

Se la funzione di hash ha fatto la seguente si otterrebbe un certo grado di indeterminatezza sul hash ricerca

unsigned int Hash(float f) 
{ 
    unsigned int ui; 
    memcpy(&ui, &f, sizeof(float)); 
    return ui & 0xfffff000; 
} 

In questo modo potrai mascherare i 12 bit meno significativi che consentono un certo grado di incertezza .. Tuttavia dipende davvero dall'applicazione.

+2

No, '0xfffff000' nasconde 3 nibbles, ovvero 12 bit. Probabilmente un po 'troppo. Se vuoi mascherare 3 bit, usa '0xfffffff8'. – fredoverflow

+1

@FredOverflow: No .. hai ragione .. Non intendevo 3 ... fallimento mentale lì. modificato – Goz

+0

@Goz: questo dipende dalla rappresentazione interna di 'float' sulla macchina di destinazione, tuttavia, poiché qui si assume che la mantissa si trova nei bit meno significativi e viene memorizzata in modo little-endian. Anche se l'idea di sfocatura è sicuramente la strada da percorrere. –

2

Potete naturalmente rappresentano un float come int tipo delle stesse dimensioni di hash, tuttavia questo approccio ingenuo ha alcune insidie ​​che è necessario stare attenti a ...

semplice conversione ad una rappresentazione binaria è soggetto a errore poiché i valori uguali non necessariamente hanno la stessa rappresentazione binaria.

Un caso ovvio: -0.0 non corrisponde a 0.0 per esempio. *

Inoltre, semplicemente conversione in un int delle stesse dimensioni solito invia distribuzione molto uniforme, che è spesso importante (che implementa un hash/set che utilizza benne per esempio).

passi suggeriti per l'attuazione:

  • filtrare i casi non finite (nan, inf) e (0.0, -0.0se è necessario fare questo in modo esplicito o meno dipende dal metodo utilizzato).
  • convertire in int della stessa dimensione
    (cioè - utilizzare un sindacato, ad esempio per rappresentare il float come int, non semplicemente gettati a un int).
  • ridistribuisci i bit, (intenzionalmente vago qui!), questo è fondamentalmente un compromesso tra velocità e qualità. Ma se hai molti valori in un piccolo intervallo probabilmente non li vuoi anche in un intervallo simile.

*: Si può wan't per controllare (e nan-nan) troppo. Come gestire quelli esattamente dipende dal tuo caso d'uso (potresti voler ignorare il segno per tutti gli nan come fa CPython).

di Python _Py_HashDouble è un buon riferimento per come si potrebbe hash un float, nel codice di produzione (ignorare il controllo -1 alla fine, dato che è un valore speciale per Python).

+0

Il caso ovvio di "-0.0 non corrisponde a 0.0 per esempio" è il ** solo ** esempio di una coppia di valori a virgola mobile che sono uguali per '==' e hanno rappresentazioni diverse, quindi non sono sicuro del perché ne crei una generalità. Gli infiniti certamente non hanno bisogno di essere filtrati. Alcuni hanno (seriamente) raccomandato di restituire un intero casuale per 'hash (NaN)', ma sembra più corretto trattare semplicemente l'uso di 'NaN' come chiave in una tabella hash come errore: http: //research.swtch. com/randhash –

+0

PS: il post del blog a cui mi sono collegato è stato pubblicato il 1 ° aprile. Non me ne sono reso conto perché l'ho letto dagli archivi. Potrebbe non essere serio, ma allo stesso tempo, un risultato casuale per l'hash (NaN) significa che l'associazione (i) con NaN come chiave sono presenti nell'hashtable e può essere iterata su, quindi è in realtà una buona soluzione per alcuni casi d'uso. –

+0

@Pascal Cuoq: esattamente come si gestiscono i valori di '! Finite' dipende dalla propria implementazione, sto semplicemente affermando che dovresti essere consapevole di loro quando l'hashing galleggia, e semplicemente convertire un float in un int come suggerito in altri le risposte stanno trascurando molto. re: '-0 vs 0' - c'è' -nan'/'nan', ma come classarli può dipendere dalle proprie preferenze (si potrebbe voler ignorare il segno di un' nan' come fa Python). Aggiornata la risposta. – ideasman42

3

È possibile utilizzare l'hash std, non è male:

std::size_t myHash = std::cout << std::hash<float>{}(myFloat); 
1

Se siete interessati, ho appena fatto una funzione di hash che usa in virgola mobile e in grado di hash galleggianti. Passa anche SMHasher (che è il test di bias principale per le funzioni hash non crittografiche). È molto più lento delle normali funzioni hash non crittografiche a causa dei calcoli float.

Non sono sicuro che se tifuhash diventerà utile per tutte le applicazioni, ma è interessante vedere una semplice funzione in virgola mobile passare sia PractRand che SMHasher.

La funzione di aggiornamento di stato principale è molto semplice, e si presenta come:

function q(state, val, numerator, denominator) { 
    // Continued Fraction mixed with Egyptian fraction "Continued Egyptian Fraction" 
    // with denominator = val + pos/state[1] 
    state[0] += numerator/denominator; 
    state[0] = 1.0/state[0]; 

    // Standard Continued Fraction with a_i = val, b_i = (a_i-1) + i + 1 
    state[1] += val; 
    state[1] = numerator/state[1]; 
} 

In ogni caso, è possibile get it on npm Oppure si può check out the github

Utilizzando è semplice:

const tifu = require('tifuhash'); 

const message = 'The medium is the message.'; 
const number = 333333333; 
const float = Math.PI; 

console.log(tifu.hash(message), 
    tifu.hash(number), 
    tifu.hash(float), 
tifu.hash()); 

C'è una demo di alcuni hash su runkit qui https://runkit.com/593a239c56ebfd0012d15fc9/593e4d7014d66100120ecdb9

Nota a margine: penso che in futuro l'utilizzo di virgola mobile, possibilmente di grandi matrici di calcoli in virgola mobile, potrebbe essere un modo utile per rendere più complicate le funzioni di hash in futuro. Uno strano effetto collaterale che ho scoperto di usare il floating point è che gli hash dipendono dall'obiettivo, e suppongo che potrebbero essere usati per impronte digitali delle piattaforme su cui sono stati calcolati.