Wer auch immer schonmal eine Mathebibliothek geschrieben hat weiß, dass es zwar einfach ist, an die speziellen Gleitkommawerte wie
+INF /
-INF und
NaN zu kommen – dass es aber eine umso haarigere Angelegenheit ist, diese Zahlen als Compile-Time-Constant in den Quelltext zu kriegen.
GCC bietet dafür (nicht standardisierte) Präprozessormakros an; da ist die Sache also noch einfach. Visual C++ ist hingegen richtig hartnäckig. Beispielsweise wird
static double const infinity = 1.0 / 0.0;
mit einer Fehlermeldung quittiert, die auf eine ungünstige Formulierung des C++-Standards zurückzuführen ist: Ein Programm ist
ill-formed, sobald eine Konstante mit einem Wert initialisiert wird, der mathematisch nicht definiert ist. Das ist hier ein großes Problem, denn im IEEE 754-Standard ist die Division durch Null zwar als
+-INF wohldefiniert; so lange sie aber
mathematisch undefiniert ist, muss der Compiler abbrechen. (GCC frisst das hingegen – habe ich gehört – mit einer Warnung.)
Ich habe auch den Schritt über
union probiert:
static union {
unsigned int asInt;
float asFloat;
} const binaryNaN = { 0x7F800000 };
static float const NaN = binaryNaN::asFloat;
Allerdings scheint VC hier dynamische Initialisierung anzuwenden:
binaryNaN wird zwar bei der Übersetzung ausgewertet; die Initialisierung von
NaN geschieht jedoch erst zu Programmstart (als fünfzeilige
fld-
fstp-Funktion). Im Release-Build konnte ich die Funktion zwar nicht mehr finden;
NaN landete aber trotzdem im Read-Write-Datenabschnitt der Exe – im Zweifel gegen den Compiler.
Das Problem scheint übrigens auch Visual C++’ CRT zu haben, denn wenn man
std::numeric_limits<float>::infinity() aufruft, wird die
float aus dem entsprechenden Symbol in
msvcrt.dll importiert.
Dabei ist die Lösung so banal wie effektiv:
Sind
Intrinsic Functions aktiviert, wird die Auswertung
vieler häufige Routinen von der Ausführungszeit zur Übersetzungszeit vorgezogen, falls sie mit statischen Parametern aufgerufen werden. Und darunter finden sich auch die üblichen mathematischen Routinen inklusive
log() und
sqrt():
static double const negativeInfinity = ::log(0.0);
static double const positiveInfinity = -negativeInfinity;
static double const NaN = ::sqrt(-1.0);
Tadaaaaaa! Wird absolut statisch ausgewertet und bei Verwendung schön optimiert.
(dass keine einzige der Lösungen hier auf allen Compiler gleich funktioniert, dürfte klar sein.)