Frage Gleitkommaarithmetik und Maschine Epsilon


Ich versuche eine Näherung des Epsilonwertes für die float Typ (und ich weiß, dass es bereits in der Standardbibliothek ist).

Die Epsilon-Werte auf dieser Maschine sind (mit einer gewissen Annäherung gedruckt):

 FLT_EPSILON = 1.192093e-07
 DBL_EPSILON = 2.220446e-16
LDBL_EPSILON = 1.084202e-19

FLT_EVAL_METHOD ist 2 So ist alles erledigt long double Präzision und float, double und long double sind 32, 64 und 96 Bit.

Ich habe versucht, eine Annäherung an den Wert von 1 zu bekommen und ihn durch 2 zu teilen, bis er zu klein wird float Art:

# include <stdio.h>

int main(void)
{
    float floatEps = 1;

    while (1 + floatEps / 2 != 1)
        floatEps /= 2;

    printf("float eps = %e\n", floatEps);
}

Die Ausgabe ist nicht das, wonach ich gesucht habe:

float epsilon = 1.084202e-19

Zwischenoperationen werden mit größter Präzision durchgeführt (aufgrund des Wertes von FLT_EVAL_METHOD), so scheint dieses Ergebnis legitim.

Dies jedoch:

// 2.0 is a double literal
while ((float) (1 + floatEps / 2.0) != 1)
    floatEps /= 2;

gibt diese Ausgabe, die die richtige ist:

float epsilon = 1.192093e-07

aber dieser:

// no double literals
while ((float) (1 + floatEps / 2) != 1)
    floatEps /= 2;

führt wieder zu einem falschen Ergebnis, als das erste:

float epsilon = 1.084202e-19

Diese beiden letzten Versionen sollten auf dieser Plattform gleichwertig sein, ist das ein Compiler Bug? Wenn nicht, was passiert?

Code ist kompiliert mit:

gcc -O0 -std=c99 -pedantic file.c

Die GCC-Version ist ziemlich alt, aber ich bin an der Universität und kann sie nicht aktualisieren:

$ gcc -v
Using built-in specs.
Target: i486-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Debian 4.4.5-8'
--with-bugurl=file:///usr/share/doc/gcc-4.4/README.Bugs
--enable-languages=c,c++,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-4.4
--enable-shared --enable-multiarch --enable-linker-build-id --with-system-zlib
--libexecdir=/usr/lib --without-included-gettext --enable-threads=posix
--with-gxx-include-dir=/usr/include/c++/4.4 --libdir=/usr/lib --enable-nls
--enable-clocale=gnu --enable-libstdcxx-debug --enable-objc-gc
--enable-targets=all --with-arch-32=i586 --with-tune=generic
--enable-checking=release --build=i486-linux-gnu --host=i486-linux-gnu
--target=i486-linux-gnu
Thread model: posix
gcc version 4.4.5 (Debian 4.4.5-8)

Die aktuelle Version von gcc, 4.7, verhält sich auf meinem Heimcomputer korrekt. Es gibt auch Kommentare, die besagen, dass verschiedene Versionen zu unterschiedlichen Ergebnissen führen.

Nach einigen Antworten und Kommentaren, die klarstellten, was sich wie erwartet verhält und was nicht, änderte ich die Frage etwas, um es klarer zu machen.


5
2018-04-17 15:13


Ursprung


Antworten:


Der Compiler darf auswerten float Ausdrücke in einer größeren Genauigkeit, die es mag, so sieht es aus wie der erste Ausdruck in ausgewertet wird long double Präzision. Im zweiten Ausdruck erzwingen Sie das Skalieren des Ergebnisses auf float nochmal.

Als Antwort auf einige Ihrer zusätzlichen Fragen und die folgende Diskussion: Sie suchen im Grunde nach der kleinsten Nicht-Null-Differenz mit 1 eines Gleitkomma-Typs. Abhängig von der Einstellung von FLT_EVAL_METHOD Ein Compiler kann entscheiden, alle Gleitkommaausdrücke mit höherer Genauigkeit als die beteiligten Typen zu bewerten. Auf einem Pentium sind die internen Register der Gleitkommaeinheit traditionell 80 Bit und es ist praktisch, diese Genauigkeit für alle kleineren Gleitkommatypen zu verwenden. Am Ende hängt dein Test also von der Genauigkeit deines Vergleichs ab !=. In Ermangelung einer expliziten Umwandlung wird die Genauigkeit dieses Vergleichs von Ihrem Compiler und nicht von Ihrem Code bestimmt. Mit der expliziten Besetzung skalieren Sie den Vergleich auf den gewünschten Typ.

Wie du bestätigt hast, hat dein Compiler gesetzt FLT_EVAL_METHOD zu 2 Daher verwendet es die höchste Genauigkeit für jede Gleitkommaberechnung.

Als Schlussfolgerung zu der untenstehenden Diskussion sind wir zuversichtlich zu sagen, dass es einen Fehler in Bezug auf die Umsetzung der FLT_EVAL_METHOD=2 Fall in gcc vor Version 4.5 und das ist ab mindestens Version 4.6 behoben. Wenn die ganze Zahl konstant ist 2 wird im Ausdruck anstelle der Gleitkommakonstante verwendet 2.0, die Besetzung zu float wird in der generierten Assembly weggelassen. Es ist auch erwähnenswert, dass von der Optimierungsebene -O1Die richtigen Ergebnisse werden mit diesen älteren Compilern erzeugt, aber die generierte Assembly ist ziemlich unterschiedlich und enthält nur wenige Fließkommaoperationen.


6
2018-04-17 15:18



Ein C99-C-Compiler kann Gleitkommaausdrücke so auswerten, als ob sie einen präziseren Gleitkommatyp hätten als ihren tatsächlichen Typ.

Das Makro FLT_EVAL_METHOD wird vom Compiler gesetzt, um die Strategie anzugeben:

-1 nicht bestimmbar;

0 werte alle Operationen und Konstanten nur für den Bereich und die Genauigkeit des Typs aus;

1 evaluiere Operationen und   Konstanten vom Typ float und double zur Reichweite und Genauigkeit des   double type, evaluiere lange Doppeloperationen und Konstanten zum   Reichweite und Präzision des langen Doppeltyps;

2 werte alles aus   Operationen und Konstanten für den Bereich und die Präzision der langen   Doppeltyp.

Aus historischen Gründen sind beim Auswählen der x86-Prozessoren zwei gängige Optionen 0 und 2.

Datei m.c ist dein erstes Programm. Wenn ich es mit meinem Compiler kompiliere, erhalte ich:

$ gcc -std=c99 -mfpmath=387 m.c
$ ./a.out 
float eps = 1.084202e-19
$ gcc -std=c99  m.c
$ ./a.out 
float eps = 1.192093e-07

Wenn ich dieses andere Programm unten kompiliere, setzt der Compiler das Makro entsprechend dessen, was es tut:

#include <stdio.h>
#include <float.h>

int main(){
  printf("%d\n", FLT_EVAL_METHOD);
}

Ergebnisse:

$ gcc -std=c99 -mfpmath=387 t.c
$ ./a.out 
2
$ gcc -std=c99 t.c
$ ./a.out 
0

2
2018-04-17 15:44