it-swarm.com.de

Warum ist der Vergleich von Floats in Java inkonsistent?

class Test{  
    public static void main(String[] args){  
        float f1=3.2f;  
        float f2=6.5f;  

        if(f1==3.2){
            System.out.println("same");  
        }else{
            System.out.println("different");  
        }
        if(f2==6.5){
            System.out.println("same");  
        }else{  
            System.out.println("different");  
        }
    }  
}  

ausgabe:

different
same

Warum ist die Ausgabe so? Ich habe same als Ergebnis im ersten Fall erwartet.

40
PSR

Der Unterschied besteht darin, dass 6.5 sowohl in float als auch in double genau dargestellt werden kann, während 3.2 in beiden Typen nicht genau dargestellt werden kann. und die zwei nächsten Annäherungen sind unterschiedlich.

Ein Gleichheitsvergleich zwischen float und double konvertiert zuerst den float in einen double und vergleicht dann die beiden. Also der Datenverlust.


Sie sollten niemals Floats oder Doubles vergleichen, um die Gleichheit zu gewährleisten. weil Sie nicht wirklich garantieren können, dass die Zahl, die Sie dem Float oder Double zuweisen, genau ist.

Dieser Rundungsfehler ist ein charakteristisches Merkmal der Gleitkommaberechnung.

Um unendlich viele reelle Zahlen in eine endliche Anzahl von Bits zu zerlegen, ist eine ungefähre Darstellung erforderlich. Obwohl es unendlich viele Ganzzahlen gibt, kann das Ergebnis von Ganzzahlberechnungen in den meisten Programmen in 32 Bit gespeichert werden.

Im Gegensatz dazu ergeben die meisten Berechnungen mit reellen Zahlen bei einer festgelegten Anzahl von Bits Größen, die mit so vielen Bits nicht genau dargestellt werden können. Daher muss das Ergebnis einer Gleitkommaberechnung oft gerundet werden, um wieder in seine endliche Darstellung zu passen. Dieser Rundungsfehler ist das charakteristische Merkmal der Gleitkommaberechnung.

Check Was jeder Informatiker über Gleitkomma-Arithmetik wissen sollte!

40

Sie sind beide Implementierungen verschiedener Teile von dem IEEE-Gleitkomma-Standard . Ein float ist 4 Bytes breit, während ein double 8 Bytes breit ist.

Als Faustregel sollten Sie in den meisten Fällen die Verwendung von double bevorzugen und float nur verwenden, wenn Sie einen guten Grund dafür haben. (Ein gutes Beispiel für die Verwendung von float im Gegensatz zu double ist "I know Ich brauche nicht so viel Präzision und das muss ich auch Speichern Sie eine Million davon im Speicher. ") Erwähnenswert ist auch, dass es schwer zu beweisen ist, dass Sie keine double Genauigkeit benötigen.

Wenn Sie Gleitkommawerte auf Gleichheit vergleichen, möchten Sie normalerweise etwas wie Math.abs(a-b) < EPSILON verwenden, wobei a und b die Gleitkommawerte sind, die verglichen werden, und EPSILON ist ein kleiner Gleitkommawert wie 1e-5. Der Grund dafür ist, dass Gleitkommawerte selten den exakten Wert codieren, den sie "sollten" - stattdessen codieren sie normalerweise einen sehr nahen Wert -, sodass Sie "schielen" müssen, wenn Sie feststellen, ob zwei Werte gleich sind.

[~ # ~] edit [~ # ~] : Jeder sollte den unten angegebenen Link @Kugathasan Abimaran lesen: Was jeder Informatiker tun sollte Mehr über Gleitkomma-Arithmetik wissen!

25
sigpwned

Um zu sehen, womit Sie es zu tun haben, können Sie die toHexString-Methode von Float und Double verwenden:

class Test {
    public static void main(String[] args) {
        System.out.println("3.2F is: "+Float.toHexString(3.2F));
        System.out.println("3.2  is: "+Double.toHexString(3.2));
        System.out.println("6.5F is: "+Float.toHexString(6.5F));
        System.out.println("6.5  is: "+Double.toHexString(6.5));
    }
}
$ Java Test
3.2F is: 0x1.99999ap1
3.2  is: 0x1.999999999999ap1
6.5F is: 0x1.ap2
6.5  is: 0x1.ap2

Im Allgemeinen hat eine Zahl eine genaue Darstellung, wenn sie gleich A * 2 ^ B ist, wobei A und B ganze Zahlen sind, deren zulässige Werte durch die Sprachspezifikation festgelegt werden (und double hat mehr zulässige Werte).

In diesem Fall,
6.5 = 13/2 = (1 + 10/16) * 4 = (1 + a/16) * 2 ^ 2 == 0x1.ap2, während
3.2 = 16/5 = (1 + 9/16 + 9/16 ^ 2 + 9/16 ^ 3 + ...) * 2 ^ 1 == 0x1.999. . . p1.
Aber Java kann nur eine endliche Anzahl von Ziffern enthalten, so dass es irgendwann die .999 ... abschneidet. = 1. Das ist in der Basis 10. In der Basis 16 wäre es 0.fff ... = 1.)

5
Teepeemm
class Test {  
  public static void main(String[] args) {  
    float f1=3.2f;  
    float f2=6.5f;  

    if(f1==3.2f)  
      System.out.println("same");  
    else  
      System.out.println("different");  

    if(f2==6.5f)  
      System.out.println("same");  
    else  
      System.out.println("different");  
    }  
  }

Versuchen Sie es so und es wird funktionieren. Ohne 'f' vergleichen Sie ein Floating mit einem anderen Floating-Typ und einer anderen Genauigkeit, was wie in Ihrem Fall zu unerwarteten Ergebnissen führen kann.

3
S4beR

Es ist nicht möglich, Werte vom Typ float und double direkt zu vergleichen. Bevor die Werte verglichen werden können, muss entweder double in float oder float in double konvertiert werden. Wenn man den vorherigen Vergleich durchführt, fragt die Konvertierung: "Enthält das float die bestmögliche float Darstellung des double Werts?" Wenn man die letzte Konvertierung durchführt, lautet die Frage "Enthält das float eine perfekte Darstellung des Wertes des double". In vielen Zusammenhängen ist die erstere Frage die aussagekräftigere, aber Java setzt voraus, dass alle Vergleiche zwischen float und double die letztere Frage stellen sollen.

Ich würde vorschlagen, dass unabhängig davon, was eine Sprache zu tolerieren bereit ist, die eigenen Codierungsstandards direkte Vergleiche zwischen Operanden des Typs float und double absolut positiv verbieten sollten. Gegebener Code wie:

float f = function1();
double d = function2();
...
if (d==f) ...

es ist unmöglich zu sagen, welches Verhalten beabsichtigt ist, wenn d einen Wert darstellt, der in float nicht genau darstellbar ist. Wenn die Absicht besteht, dass f in double konvertiert wird und das Ergebnis dieser Konvertierung mit d verglichen wird, sollte man den Vergleich als schreiben

if (d==(double)f) ...

Obwohl die Typumwandlung das Verhalten des Codes nicht ändert, wird deutlich, dass das Verhalten des Codes beabsichtigt ist. Wenn der Vergleich zeigen soll, ob f die beste float Darstellung von d enthält, sollte es sein:

if ((float)d==f)

Beachten Sie, dass das Verhalten hiervon sehr verschieden ist, was ohne die Besetzung passieren würde. Wenn Ihr ursprünglicher Code den Operanden double jedes Vergleichs in float umgewandelt hätte, wären beide Gleichheitstests bestanden.

2
supercat

Im Allgemeinen ist die Verwendung des Operators == mit Gleitkommazahlen aufgrund von Approximationsproblemen nicht empfehlenswert.

1
HAL9000

6.5 kann genau binär dargestellt werden, 3.2 nicht. Aus diesem Grund spielt der Unterschied in der Präzision für 6.5 keine Rolle, also 6.5 == 6.5f.

So aktualisieren Sie Binärzahlen schnell:

100 -> 4

10 -> 2

1 -> 1

0.1 -> 0.5 (or 1/2)

0.01 -> 0.25 (or 1/4)

etc.

6.5 in binär: 110.1 (Genaues Ergebnis, die restlichen Ziffern sind nur Nullen)

3.2 im Binärformat: 11.001100110011001100110011001100110011001100110011001101... (Hier ist Präzision wichtig!)

Ein Float hat nur eine Genauigkeit von 24 Bit (der Rest wird für Vorzeichen und Exponenten verwendet).

3.2f in binär: 11.0011001100110011001100 (Nicht gleich der doppelten Genauigkeitsnäherung)

Grundsätzlich ist es dasselbe, als würden Sie 1/5 und 1/7 in Dezimalzahlen schreiben:

1/5 = 0,2
1,7 = 0,14285714285714285714285714285714.
0
atul jamwal