Gibt es eine Standardmethode, um den n-ten Gleitkommawert "nextafter" in C ++ zu erhalten?

8

C ++ hat std::nextafter(), was den nächsten darstellbaren Wert nach einem gegebenen Gleitkommawert f zurückgibt . In meinem Fall möchte ich n Bits Slop in den unteren Mantissenbits zulassen , sodass für 3 Bits Slop der 8. nächste Wert nach einem bestimmten Wert f erforderlich wäre . Ich könnte nextafter()acht Mal anrufen , aber gibt es eine bessere Möglichkeit, damit umzugehen?

Bei den meisten Werten können Sie mit dem Umwandeln des FP-Werts in uint_64, dem Hinzufügen der Toleranz ( 1<<3für 3 Bit Slop) und dem anschließenden Umwandeln doubledank des Layouts von IEEE 754 auskommen. Dies basiert jedoch auf dem Gleitkomma von IEEE 754 ( eine gute Annahme, aber auch nicht ganz solide).

(Als Hintergrund möchte ich dies verwenden, um Schnittpunkte der Strahloberfläche zu heben, die sich aufgrund von FP-Ungenauigkeiten gelegentlich innerhalb der Oberfläche befinden. Diejenigen, die mit robustem Gleitkomma vertraut sind, werden verstehen, warum dies epsiloneine schreckliche Lösung ist.)

Steve Hollasch
quelle
Es hört sich nicht so an, als ob Sie genau den 8. nächsten Wert benötigen. Wäre es gut genug, f (vorausgesetzt, es ist positiv) mit 1,00 ... 001 zu multiplizieren?
Marc Glisse
Sie können auch den Wert von verwenden std::numeric_limits<T>::is_iec559, um zu überprüfen, ob IEEE 754 verwendet wird, und die Funktion entsprechend zu spezialisieren.
IlCapitano

Antworten:

2

Ein naiver Ansatz könnte darin bestehen, den Abstand zwischen einem Wert und dem nächsten darstellbaren Float mit 8 zu multiplizieren, anstatt 8 Mal aufzurufen std::nextafter

double advance_float(double x, int d)
{
    double step = std::copysign((std::nextafter(x, x + d) - x) * d, d);
    return x + step;
}

Hier gibt es einige Tests, aber es liegt an Ihnen, festzustellen, ob dies für Ihren Anwendungsfall geeignet ist.

Bearbeiten

Wie von Steve Hollash bemerkt , xkann das so groß sein, dass x + d == d. Daniel Jour schlug vor, frexp(und ldexp) zu nutzen, aber im folgenden Versuch werde ich einen anderen Ansatz verwenden, um die Richtung zu bestimmen.

double advance_float(double x, int d)
{
    const double to = std::copysign(std::numeric_limits<double>::infinity(), d);
    const double next = std::nextafter(x, to);
    return x + std::copysign(d * (next - x), d);
}

Beachten Sie, dass dies std::numeric_limits<double>::has_infinity == trueansonsten vorausgesetzt wird ::lowest()und ::max()verwendet werden muss.

Das sind einige Ergebnisse

         xd vorheriges x nächstes
-------------------------------------------------- ----------------------------------------
           1 1 0x1.fffffffffffffp-1 0x1p + 0 0x1.0000000000001p + 0
           1 8 0x1.ffffffffffff8p-1 0x1p + 0 0x1.0000000000008p + 0
     3.14159 8 0x1.921fb54442d1p + 1 0x1.921fb54442d18p + 1 0x1.921fb54442d2p + 1
      100,01 8 0x1,900a3d70a3d69p + 6 0x1,900a3d70a3d71p + 6 0x1,900a3d70a3d79p + 6
     -100,01 8 -0x1,900a3d70a3d79p + 6 -0x1,900a3d70a3d71p + 6 -0x1,900a3d70a3d69p + 6
       1e + 67 8 0x1,7bd29d1c87a11p + 222 0x1,7bd29d1c87a19p + 222 0x1,7bd29d1c87a21p + 222
       1e-59 8 0x1.011c2eaabe7dp-196 0x1.011c2eaabe7d8p-196 0x1.011c2eaabe7ep-196
           0 8 -0x0.0000000000008p-1022 0x0p + 0 0x0.0000000000008p-1022
4.94066e-324 8 -0x0.0000000000007p-1022 0x0.0000000000001p-1022 0x0.00000000009p-1022
Bob__
quelle
Interessanter Ansatz. Wie geschrieben, ist das x+djedoch nicht das, wonach Sie suchen. Wenn x groß ist, dann ist (x + d) == x. Aber ich mag die Idee: Berechnen Sie den Wert, der dem Bit niedrigster Ordnung entspricht, mit einem übereinstimmenden Exponenten, der durch den "Slop" skaliert und dann zum ursprünglichen Wert addiert wird.
Steve Hollasch
@SteveHollasch Stimmt, das ist das Problem nextafter, wie man die "Richtung" passiert.
Bob__
Hinzufügen frexpdavor und ldexpdanach sollte funktionieren, nein?
Daniel Jour
@ DanielJour Meinst du das ? Es scheint "zu funktionieren", aber ich vermisse sicherlich viele Eckfälle.
Bob__
Wunderbar. Nicht nur eine Lösung, sondern eine Reihe neuer Tools, die mir nicht bekannt waren. Mit frexpinsbesondere kann ich Welten bewegen. Vielen Dank!
Steve Hollasch
4

Soweit ich weiß, gibt es dafür keine Standardfunktion. Boost hat Sie jedoch abgedeckt: Siehe boost::math::float_advance. Wenn Sie dies zum Vergleichen von zwei Floats verwenden, möchten Sie wahrscheinlich boost::math::float_distancestattdessen.

Eerorika
quelle
Der Link ist nicht auf die neueste Version. (Das ist wahrscheinlich nicht wichtig.)
Keith Thompson