Il calcolo a virgola mobile è troppo esatto

5

In Java, ho eseguito questo metodo:

private static void floatAddition() {
    float a = 0.1F, b = 0.2F, c = a+b;
    System.out.println(a + " + " + b + " = " + c);
}

e il risultato è 0.3 per floatAddition. Ora sto cercando di capire perché sta aggiungendo un risultato rotondo, e non a qualcosa di leggermente fuori a causa di punti fluttuanti.

Ho trasformato manualmente 0.1 e 0.2 in un sistema binario a 32 bit con standard IEEE 754, questo è ciò che ho ottenuto:

0.1 (decimal) = 0 01111011 10011001100110011001101 (binary)
0.2 (decimal) = 0 01111100 10011001100110011001101 (binary)

Ora aggiungo questi due insieme. Dal momento che gli esponenti sono diversi, devo portarli entrambi a uno più grande (2 ^ -3) quindi sto denormalizzando 0.1.

0.1 (decimal) = 0.11001100110011001100111 * 2-3 (binary)
0.2 (decimal) = 1.10011001100110011001101 * 2-3 (binary)

Questo è il risultato dell'aggiunta manuale:

0.1 (decimal) + 0.2 (decimal) = 10.01100110011001100110100 * 2-3 (binary)

Nello standard IEEE 754, questo sarebbe

0 01111101 00110011001100110011010

Ora per verificare se sono corretto, ho utilizzato questo convertitore IEEE 754: link Secondo questo sito, il mio risultato dovrebbe essere stato 0.30000001192092896.

Perché il calcolo del punto mobile è più preciso di quanto dovrebbe essere?

    
posta Baustein 20.02.2016 - 15:21
fonte

2 risposte

6

Guarda un codice veloce:

public class Floats {
    public static void main(String[] args) {
        float a = 0.1F;
        float b = 0.2F;
        float c = a+b;
        System.out.println(a + " + " + b + " = " + c);

        System.out.printf("0x%8x", Float.floatToIntBits(c));
    }
}

L'output di questo codice è:

0.1 + 0.2 = 0.3
0x3e99999a

L'ultima riga è la rappresentazione esadecimale del valore che viene reso come 0.3.

Quindi, vediamo cosa fa C.

#include <stdio.h>
int main (int argc, char* argv[]) {
    float a = 0.1F;
    float b = 0.2F;
    float c = a + b;
    printf("%f + %f = %f\n", a, b, c);
    printf("0x%8x\n", *(unsigned int*)&c);  
}

E questo produce l'output:

0.100000 + 0.200000 = 0.300000
0x3e99999a

Noterai che la rappresentazione del valore float è equivalente.

Sono certo che il codice segua correttamente le specifiche. Potresti essere confuso nella conversione da float a stringa, ma è qualcosa che va fatto con varie formattazioni o altre librerie per visualizzare ciò che desideri.

Ma la rappresentazione in virgola mobile sottostante è esattamente ciò che dovrebbe essere.

Per inciso, la differenza tra C e Java può essere vista passando a printf dove ci si aspetta che sia un po 'più preciso con i valori:

public class Floats {
    public static void main(String[] args) {
        float a = 0.1F;
        float b = 0.2F;
        float c = a+b;
        System.out.println(a + " + " + b + " = " + c);
        System.out.printf("%f + %f = %f\n", a, b, c);

        System.out.printf("0x%8x", Float.floatToIntBits(c));
    }
}

stampa:

0.1 + 0.2 = 0.3
0.100000 + 0.200000 = 0.300000
0x3e99999a

Nota che printf in Java corrisponde a printf in C.

Inserimento nel JLS che troviamo nella sezione 5.1 .11 ,

If T is float, then use new Float(x).

Scavando attraverso questo, si arriva a sun.misc.FloatingDecimal che quando viene creato con FloatingDecimal(float f) che passa attraverso un gioco di calcoli per cercare di capire cosa visualizzare. Java8 lo modifica un po 'per fare il BinaryToAsciiConverter , ma l'idea è sempre la stessa.

    
risposta data 20.02.2016 - 15:45
fonte
3

Probabilmente perché 0.3f = 0.30000001192092896.

0.3f è il numero in virgola mobile a precisione singola più vicino al numero reale 0.3. Il numero reale 0,3 non può essere rappresentato esattamente come un singolo numero in virgola mobile di precisione (o come qualsiasi numero in virgola mobile binario), pertanto 0,3 f deve essere leggermente diverso dal numero reale 0,3.

Il virgola mobile a precisione singola ha una mantissa a 24 bit. Per 0.3, il bit più alto della mantissa ha un valore di 0.25 = 2 ^ -2, il bit più basso ha un valore di 2 ^ -25, e dovresti aspettarti un errore di arrotondamento fino a

2 ^ -26 = 0.000 000 014 901 161

In realtà, 0.3f è stato arrotondato per

0.000 000 011 920 928 96

che è interamente nell'intervallo che ti aspetteresti.

    
risposta data 20.02.2016 - 17:26
fonte

Leggi altre domande sui tag