2013-08-06 13 views
5

Questa è una domanda molto semplice, ma importante poiché influisce enormemente su tutto il mio progetto.Troncare un doppio in un float in C

Supponiamo che io ho il seguente codice snipet:

unsigned int x = 0xffffffff; 
float f = (float)((double)x * (double)2.328306436538696e-010); // x/2^32 

Mi aspetterei che f essere qualcosa di simile a 0,99999, ma, invece, si arrotonda a 1, dal momento che è la più vicina float approssimazione. Non va bene dato che ho bisogno dei valori di float nell'intervallo di [0,1), non di [0,1]. Sono sicuro che sia qualcosa di semplice, ma apprezzerei un po 'di aiuto.

risposta

0

La mia soluzione finale era di ridurre le dimensioni del mio moltiplicatore costante.Probabilmente era la soluzione migliore dal momento che non c'era motivo di moltiplicarsi per un doppio in ogni caso. La precisione non è stata vista dopo la conversione in un float.

così 2.328306436538696e-010 è stato cambiato a 2.3283063

3

Il valore oltre il quale un double giri a 1 o più quando convertiti in float nella modalità di arrotondamento di default IEEE 754 è 0x1.ffffffp-1 (in notazione esadecimale di C99, dal momento che la tua domanda è etichettato “C”).

Le opzioni sono:

  1. attivare la modalità di arrotondamento FPU di andata e verso il basso prima della conversione, o
  2. moltiplicare per (0x1.ffffffp-1/0xffffffffp0) (dare o prendere uno ULP) sfruttare appieno precisione singola gamma [ 0, 1) senza ottenere il valore 1.0f.

Metodo 2 leads to use the constant0x1.ffffff01fffffp-33:

double factor = nextafter(0x1.ffffffp-1/0xffffffffp0, 0.0); 
unsigned int x = 0xffffffff; 
float f = (float)((double)x * factor); 
printf("factor:%a\nunrounded:%a\nresult:%a\n", factor, (double)x * factor, f); 

Stampe:

factor:0x1.ffffff01fffffp-33 
unrounded:0x1.fffffefffffffp-1 
result:0x1.fffffep-1 
1

Non c'è molto che si può fare - la vostra int detiene 32 bit, ma la mantissa di un float vale solo 24. arrotondamento sta per accadere. È possibile modificare la modalità di arrotondamento del processore per arrotondare verso il basso anziché al più vicino, ma ciò causerà alcuni effetti collaterali che si desidera evitare soprattutto se non si ripristina la modalità di arrotondamento al termine.

Non c'è niente di sbagliato nella formula che stai utilizzando, sta producendo la risposta più precisa possibile per l'input dato. C'è solo un caso limite che non soddisfa un severo requisito. Non c'è niente di sbagliato con test per il caso specifico fine e sostituendolo con il valore più vicino che soddisfa il requisito:

if (f >= 1.0f) 
    f = 0.99999994f; 

0,999999940395355224609375 è il valore più vicino che un galleggiante IEEE-754 può assumere senza essere uguale a 1,0.

+1

Questa non è una risposta utile. Come hanno mostrato altre risposte (e hanno mostrato come), ci sono cose che puoi fare. –

+0

@EricPostpischil, come non è utile? Fornisce una soluzione operativa al problema, senza lasciare una modalità di arrotondamento in vigore che cambierà tutti i calcoli intermedi e successivi. –

+0

L'affermazione "Non c'è molto che puoi fare" è fuorviante e inutilmente scoraggiante. L'affermazione sui bit in un 'int' e a' float' è irrilevante; l'OP non si aspetta una mappa esatta. Non chiedono di evitare l'arrotondamento, ma solo di controllarlo. –

8

In C (dal C99), è possibile cambiare la direzione di arrotondamento con fesetround da libm

#include <stdio.h> 
#include <fenv.h> 
int main() 
{ 
    #pragma STDC FENV_ACCESS ON 
    fesetround(FE_DOWNWARD); 
    // volatile -- uncomment for GNU gcc and whoever else doesn't support FENV 
    unsigned long x = 0xffffffff; 
    float f = (float)((double)x * (double)2.328306436538696e-010); // x/2^32 
    printf("%.50f\n", f); 
} 

Testato con IBM XL, Sun Studio, clang, GNU GCC. Questo mi dà 0.99999994039535522460937500000000000000000000000000 in tutti i casi

+0

È una funzione C++ 11? –

+0

Funzione @MarkB C99, inclusa in C++ 11 – Cubbi

+0

@EricPostpischil grazie per l'indicazione, riscritta in C – Cubbi

1

Si può semplicemente troncare il valore alla massima precisione (mantenendo i 24 bit alti) e dividere per 2^24 per ottenere il valore più vicino che un galleggiante può rappresentare senza essere arrotondato a 1;

unsigned int i = 0xffffffff; 
float value = (float)(i>>8)/(1<<24); 

printf("%.20f\n", value); 
printf("%a\n", value); 

>>> 0.99999994039535522461 
>>> 0x1.fffffep-1 
+0

Questo può essere un buon approccio, se arrotondare ogni valore verso zero (non solo quelli vicini a 1) si adatta all'OP. L'hack per l'illustrazione non è necessario; possiamo usare l'identificatore di formato '% a' per visualizzare i numeri in virgola mobile in un modo che illustri la loro composizione. –

+0

@EricPostpischil Grazie per il formato '% a', non lo sapevo. –