2014-09-15 11 views
16

Ein Wert float (nur Einzelwert) ist ein 4-Byte-Wert und soll jede reelle Zahl darstellen. Aufgrund der Formatierung und der endlichen Anzahl von Bytes, aus denen es besteht, gibt es einen minimalen Wert und einen maximalen Wert, die es darstellen kann, und es hat eine endliche Genauigkeit, abhängig von seinem eigenen Wert.Wie finden Sie den nächsten nicht gleichwertigen Wert eines Gleitkommawerts?

Ich würde gerne wissen, ob es einen Weg gibt, den nächstmöglichen Wert über oder unter einem Referenzwert zu erhalten, angesichts der endlichen Genauigkeit eines Gleitkommawertes. Bei Integern ist das trivial: Man addiert oder subtrahiert einfach 1. Aber mit einem float kann man den minimalen Float-Wert nicht einfach addieren oder subtrahieren und erwarten, dass er sich von deinem ursprünglichen Wert unterscheidet. I.e.

float FindNearestSmaller (const float a) 
{ 
    return a - FLT_MIN; /* This doesn't necessarily work */ 
} 

In der Tat wird das oben genannte fast nie funktionieren. Im obigen Fall wird die Rückmeldung im Allgemeinen immer noch gleich a sein, da die FLT_MIN weit über die Genauigkeit von a hinausgeht. Sie können dies leicht selbst ausprobieren: es funktioniert für z.B. 0.0f, oder für sehr kleine Anzahl von Auftrag FLT_MIN, aber nicht für irgendetwas zwischen 0 und 100.

Wie würden Sie den Wert erhalten, der am nächsten, aber kleiner oder größer als a, da Gleitkommagenauigkeit ist?

Hinweis: Obwohl ich hauptsächlich an einer C/C++ Antwort interessiert bin, nehme ich an, dass die Antwort für die meisten Programmiersprachen anwendbar ist.

+0

Wirklich neugierig, eine Antwort darauf zu sehen. – Almo

+0

'memcpy' in eine ganze Zahl, addiere eins,' memcpy' zurück in 'float'? –

+0

@BartFriederichs: Nein, das wird nicht in allen Fällen funktionieren. –

Antwort

13

Der üblicher Weg eines Gleitkommawert Nachbarn zu finden, ist die Funktion nextafter für double und nextafterf für float. Das zweite Argument gibt die Richtung an. Beachten Sie, dass Unendlichkeiten gültige Werte in IEEE 754 Fließkomma sind, so dass Sie sehr gut nextafter(x, +1.0/0.0) aufrufen können, um den Wert direkt über x zu erhalten, und dies funktioniert auch für DBL_MAX (wenn Sie dagegen nextafter(x, DBL_MAX) geschrieben haben, würde DBL_MAX zurückgegeben, wenn angewendet wird).

Zwei Nicht-Standard-Möglichkeiten, die manchmal nützlich sind, sind:

  1. Zugriff der Darstellung der float/double als unsigned integer von gleicher Größe, und Inkrementieren oder Dekrementieren Dieser ganzen Zahl ist. Das Gleitkommaformat wurde sorgfältig entworfen, so dass für positive Gleitkommazahlen bzw. negative Gleitkommazahlen die als Ganzzahl dargestellten Bits der Repräsentation sich monoton mit dem dargestellten Gleitkomma entwickeln.

  2. Ändern Sie den Rundungsmodus nach oben und fügen Sie die kleinste positive Fließkommazahl hinzu. Die kleinste positive Gleitkommazahl ist auch das kleinste Inkrement, das zwischen zwei Gleitkommazahlen liegen kann, so dass kein Gleitkommawert übersprungen wird. Die kleinste positive Fließkommazahl ist FLT_MIN * FLT_EPSILON.


Aus Gründen der Vollständigkeit, werde ich hinzufügen, dass auch ohne den Rundungsmodus von seinem Wechsel „zum nächsten“ default erzeugt einen Schwimmer durch (1.0f + FLT_EPSILON) Multiplizieren einer Zahl, die sich entweder der unmittelbare Nachbar ist aus Null oder der Nachbar danach. Es ist wahrscheinlich am billigsten, wenn Sie bereits das Vorzeichen des Floats kennen, das Sie erhöhen/verringern möchten, und es macht Ihnen nichts aus, dass es manchmal nicht den unmittelbaren Nachbarn produziert. Die Funktionen nextafter und nextafterf sind so spezifiziert, dass eine correct implementation auf dem x86 auf eine Anzahl von speziellen Werten und FPU-Zuständen testen muss und daher ziemlich teuer für das ist, was sie tut.

In Richtung Null gehen, multiplizieren mit 1.0f - FLT_EPSILON.

Dies funktioniert natürlich nicht für 0.0f, und im Allgemeinen für die kleineren denormalisierten Zahlen.

Die Werte, für die von 1.0f + FLT_EPSILON Voraus durch 2 ULPs Multiplikation sind nur unterhalb einer Potenz von zwei, und zwar in dem Intervall [0,75 * 2 p ... 2 p). Wenn es Ihnen nichts ausmacht, eine Multiplikation und Addition durchzuführen, sollte x + (x * (FLT_EPSILON * 0.74)) für alle normalen Zahlen funktionieren (aber immer noch nicht für Null oder für alle kleinen Denormalzahlen).

11

Betrachten Sie die "nextafter" -Funktion, die Teil von Standard C ist (und wahrscheinlich C++, aber ich habe nicht überprüft).

+0

http://linux.die.net/man/3/nextafter –

+1

Es gibt einen std :: nextafter und std :: nexttoward, der in C++ 11 beginnt. –

0

Ich habe es auf meiner Maschine ausprobiert. Und alle drei Ansätze:
1. Zugabe mit 1 und memcopying
2. Zugabe FLT_EPSILON
3. Vervielfachungs durch (1.0f + FLT_EPSILON)
scheint die gleiche Antwort zu geben.


sehen das Ergebnis hier
bash-3.2 $ cc float_test.c -o float_test; ./float_test 1.023456 10
Ursprüngliche num: 1.023456
int addiert = 1.023456 01-EPS hinzugefügt = 1.023456 mult von 01 * (EPS + 1) = 1,023456
int addiert = 1.023456 02-EPS hinzugefügt = 1.023456 mult von 02 * (eps + 1) = 1.023456
int hinzugefügt = 1.023456 03-eps hinzugefügt = 1.023456 multi von 03) = 1.023456
int addiert = 1,023457 05-EPS hinzugefügt = 1,023457 mult von 05 * (EPS + 1) = 1,023457
int addiert = 1,023457 06-EPS hinzugefügt = 1,023457 mult von 06 * (EPS + 1) = 1,023457
in t hinzugefügt = 1.023457 07-eps hinzugefügt = 1.023457 mult durch 07 * (eps + 1) = 1.023457 int hinzugefügt = 1.023457 08-eps hinzugefügt = 1.023457 mult von 08 * (eps + 1) = 1.023457 int hinzugefügt = 1.023457 09-EPS hinzugefügt = 1,023457 mult von 09 * (EPS + 1) = 1,023457
int addiert = 1,023457 10-EPS hinzugefügt = 1,023457 mult um 10 * (EPS + 1) = 1,023457

-Code

#include <float.h> 
#include <stdlib.h> 
#include <stdio.h> 
#include <string.h> 
#include <assert.h> 

int main(int argc, char *argv[]) 
{ 

    if(argc != 3) { 
     printf("Usage: <binary> <floating_pt_num> <num_iter>\n"); 
     exit(0); 
    } 

    float f = atof(argv[1]); 
    int count = atoi(argv[2]); 

    assert(count > 0); 

    int i; 
    int num; 
    float num_float; 

    printf("Original num: %f\n", f); 
    for(i=1; i<=count; i++) { 
     memcpy(&num, &f, 4); 
     num += i; 
     memcpy(&num_float, &num, 4); 
     printf("int added = %f \t%02d-eps added = %f \tmult by %2d*(eps+1) = %f\n", num_float, i, f + i*FLT_EPSILON, i, f*(1.0f + i*FLT_EPSILON)); 
    } 

    return 0; 
} 
+1

'% .9f' ist besser als'% f', um anzuzeigen, was passiert, und '% a' ist noch besser. –

+0

Schlagen Sie Tests gegen Zahlen auf beiden Seiten einer Power-of-2 (und weg von 1.0, vielleicht '-') wie -1023.9 ​​und -1024.1. Zuversichtlich signifikante Unterschiede werden gezeigt. – chux

+0

@chux gut das wird erwartet. Bei größeren Zahlen multipliziert man mit "1 + FLT_EPSILON" eine größere Drift. Auch die Add-by-1-Integer-Methode beeinflusst die Mantisse ebenfalls um einen größeren Betrag; in manchen Fällen wie '2^30' ändert sich auch der Exponent. Das Hinzufügen von 'FLT_EPSILON' führt jedoch immer noch zu konsistenten Ergebnissen. Tatsächlich sind die Veränderungen jetzt noch langsamer, weil wir größere Zahlen ändern; Der Effekt von 'FLT_EPSILON' ist also beschattet. –

Verwandte Themen