Warum verwenden viele (alte) Programme Floor (0,5 + Eingabe) anstelle von Round (Eingabe)?

80

Die Unterschiede liegen im zurückgegebenen Wert, der meiner Meinung nach Eingaben zum Brechen von Unentschieden liefert, wie zum Beispiel diesen Code :

int main()
{
    std::cout.precision(100);

    double input = std::nextafter(0.05, 0.0) / 0.1;
    double x1 = floor(0.5 + input);
    double x2 = round(input);

    std::cout << x1 << std::endl;
    std::cout << x2 << std::endl;
}

welche Ausgänge:

1
0

Aber am Ende sind es nur unterschiedliche Ergebnisse, man wählt das bevorzugte. Ich sehe viele "alte" C / C ++ - Programme, die floor(0.5 + input)anstelle von verwenden round(input).

Gibt es einen historischen Grund? Günstigstes auf der CPU?

markzzz
quelle
20
std :: round bewegt Fälle auf halbem Weg von Null weg. Das ist mathematisch nicht einheitlich, wie der Boden (f + .5), bei dem die halben Fälle immer nach oben gehen. Der Grund für die Verwendung der Bodenmethode ist, dass sie für eine ordnungsgemäße Rundung in der technischen Welt erforderlich ist.
Michaël Roy
7
Wie in round () für float in C ++ vor C ++ 11 angegeben, hatten wir keine round. Wie ich in meiner Antwort festgestellt habe, ist es ein schwieriges Problem, die eigene Runde richtig zu schreiben.
Shafik Yaghmour
16
@Arne Bei Verwendung von std :: round () beträgt der Abstand zwischen den gerundeten Werten von -0,5 und +0,5 2. Bei Verwendung von floor ist dies 1. Tritt nur auf, wenn die beiden Werte entgegengesetzte Vorzeichen haben. Sehr irritierend, wenn Sie versuchen, gerade Linien zu zeichnen, oder wenn Sie das falsche Texturpixel auswählen.
Michaël Roy
20
Einige Programmiersprachen und -umgebungen (einschließlich .NET) verwenden eine täuschende Sache namens Banker's Rounding, bei der x.5 auf die nächste EVEN-Zahl rundet. Also 0,5 Runden auf 0 und 1,5 Runden auf 2. Sie können sich vorstellen, welche Verwirrung dies beim Debuggen verursachen kann. Ich denke, die Lösung für dieses böse "Feature" besteht darin, überhaupt keine .Round () -Funktion zu haben und stattdessen .RoundBankers (), .RoundHalfUp (), .RoundHalfDown () usw. (oder .BankersRound () usw. Zu haben aber Intellisense würde besser mit .RoundBankers ()) funktionieren. Zumindest auf diese Weise müssten Sie wissen, was Sie erwartet.
user3685427
8
@ user3685427: Banker-Rundung ist in finanziellen und statistischen Anwendungen erforderlich, bei denen die subtile und systemische Aufwärtsverzerrung beseitigt werden muss, die durch Rundung von Null entsteht . Es ist fast unmöglich, ohne tatsächliche Kenntnis der Hardware-Gleitkomma-Implementierung zu implementieren, daher ist die Auswahl als Standard in C #.
Pieter Geerkens

Antworten:

115

std::roundwird in C ++ 11 eingeführt. Vorher war nur std::floorverfügbar, also verwendeten Programmierer es.

Haccks
quelle
Nichts. Aber es ist älter als C ++ 11. Ich fand es logisch, dass C ++ es vor 11 bekam. Das ist alles;)
markzzz
12
@markzzz - Die C ++ - Standardbibliothek erbt die Standardbibliothek von C nicht automatisch. Es wird sorgfältig ausgewählt und ausgewählt. Die Synchronisierung mit C99 dauerte 12 Jahre.
Geschichtenerzähler - Unslander Monica
2
@haccks: In der Tat. IMHO C war C ++ in Bezug auf mathematische Funktionen bis C ++ 11 voraus.
Bathseba
3
Es ist wichtig zu beachten, dass die verwendete Methode für einige Eingaben unterbrochen wird und dass C ++ 11 auf C99 basiert, während C ++ 03 auf C90 basiert, was zu @markzzz geht.
Shafik Yaghmour
1
@markzzz: Ihre Bemerkung ist genau richtig (trotz Kritiker). Die Sache ist, es gab eine lange Lücke ohne C ++ - Standards, der erste C ++ - Standard war C ++ 98 und die erste größere Revision war C ++ 11. Es gab ein kleines Update, C ++ 03, aber wie die Wikipedia-Seite feststellt, handelte es sich hauptsächlich um eine "Bugfix" -Version. Daher war C ++ 11 nach 13 Jahren iatus die erste Gelegenheit, die C-Standardbibliothek einzuholen.
Matthieu M.
21

Es gibt überhaupt keinen historischen Grund. Diese Art von Abweichung gibt es seit Jahr. Leute tun dies, wenn sie sich sehr, sehr ungezogen fühlen. Es ist ein Missbrauch der Gleitkomma-Arithmetik, und viele erfahrene professionelle Programmierer fallen darauf herein. Sogar die Java-Bods haben bis zur Version 1.7 gearbeitet. Lustige Typen.

Meine Vermutung ist, dass eine anständige sofort einsatzbereite deutsche Rundungsfunktion erst in C ++ 11 offiziell verfügbar war (obwohl C sie in C99 erhalten hat), aber das ist wirklich keine Entschuldigung für die Übernahme der sogenannten Alternative.

Hier ist die Sache: floor(0.5 + input) stellt nicht immer das gleiche Ergebnis wie der entsprechende std::roundAufruf wieder her!

Der Grund ist ziemlich subtil: Der Grenzwert für eine deutsche Rundung, a.5für eine ganze Zahl, aist durch eine zufällige Eigenschaft des Universums eine dyadische Rationalität . Da dies genau in einem IEEE754-Gleitkomma bis zur 52. Potenz von 2 dargestellt werden kann und danach das Runden ohnehin ein No-Op ist, std::roundfunktioniert es immer richtig. Weitere Gleitkommaschemata finden Sie in der Dokumentation.

Das Hinzufügen 0.5zu a doublekann jedoch zu Ungenauigkeiten führen, die bei einigen Werten zu einem leichten Unter- oder Überschwingen führen. Wenn Sie darüber nachdenken, führt das Addieren von zwei doubleWerten - das ist der Beginn unwissentlicher Denary-Konvertierungen - und das Anwenden einer Funktion, die eine sehr starke Funktion der Eingabe ist (z. B. eine Rundungsfunktion), zwangsläufig zu Tränen.

Tu es nicht .

Referenz: Warum gibt Math.round (0.49999999999999994) 1 zurück?

Bathseba
quelle
2
Kommentare sind nicht für eine ausführliche Diskussion gedacht. Dieses Gespräch wurde in den Chat verschoben .
Andy
2
nearbyint()ist normalerweise eine bessere Wahl als round(), da nearbyintder aktuelle Rundungsmodus anstelle des funky Tiebreak von Null entfernt verwendet wird round()(für den x86 nicht einmal Hardware-Unterstützung bietet, obwohl ARM dies tut). Beide wurden in C ++ 11 zu C ++ hinzugefügt.
Peter Cordes
9

Ich denke, hier irren Sie:

Aber es sind nur unterschiedliche Ergebnisse am Ende, man wählt das bevorzugte. Ich sehe viele "alte" C / C ++ - Programme, die Floor (0,5 + Eingabe) anstelle von Round (Eingabe) verwenden.

Das ist nicht der Fall. Sie müssen das richtige Rundungsschema für die Domain auswählen . In einer Finanzanwendung werden Sie nach den Regeln des Bankiers runden (übrigens nicht mit float). Beim Abtasten static_cast<int>(floor(f + .5))ergibt das Aufrunden mit weniger Abtastrauschen jedoch den Dynamikbereich. Wenn Sie Pixel ausrichten, dh eine Position in Bildschirmkoordinaten konvertieren, erhalten Sie mit einer anderen Rundungsmethode Löcher, Lücken und andere Artefakte.

Michaël Roy
quelle
"dies erhöht den Dynamikbereich" - sieht aus wie bedeutungsloser zusätzlicher Text; versehentlich von irgendwoher kopiert? Vielleicht möchten Sie es löschen.
Anatolyg
Nein. Durch Verringern des Abtastrauschens wird das Grundrauschen verringert, und dies erhöht tatsächlich den Dynamikbereich.
Michaël Roy
Könnten Sie ein einfaches Beispiel oder eine Referenz angeben, um die Zunahme des Dynamikbereichs / die Abnahme des Rauschens zu veranschaulichen?
Ruslan
1
Bei einer ganzzahligen Standardrundung (fehlende Rundung) "verschwinden" alle Werte zwischen null und minus eins oder ändern das Vorzeichen (gleicher Wert und für 0 bis + 1 Eingänge). Alle negativen Werte werden um mindestens ein Bit versetzt. Hier ist ein bisschen Rauschen mit zusätzlicher Verzerrung.
Michaël Roy
4

Ein einfacher Grund könnte sein, dass es verschiedene Methoden zum Runden von Zahlen gibt. Wenn Sie also die verwendete Methode nicht kennen, können Sie unterschiedliche Ergebnisse erzielen.

Mit floor () können Sie mit den Ergebnissen übereinstimmen. Wenn der Float 0,5 oder höher ist, wird durch Hinzufügen bis zum nächsten int gestoßen. Aber .49999 lässt nur die Dezimalstelle fallen.

MivaScott
quelle
+1 Der Punkt dieser Antwort wird durch die Kommentare zu der Frage gemacht, in denen es Uneinigkeit darüber gibt, was round()tut.
Loduwijk
@ Aaron Die Antwort ist falsch, was aber floor(x + 0.5)tut.
EOF
Oh, hah! Guter Fang dann. Das ist ironisch. "Verwenden Sie das bekanntere X, da wir uns nicht einig sind oder nicht genau über Y Bescheid wissen." Was machen Sie also, wenn dies auch für X gilt?
Loduwijk
1
@ Aaron Easy. Sie tun das einzig Vernünftige und nearbyint(x)verwenden eine vernünftige (bis zur nächsten geraden) Rundung, vorausgesetzt, Sie haben nicht mit der Gleitkommaumgebung herumgespielt.
EOF
@EOF: Deine Rundungswahl ist nicht vernünftig. Eine Rundungsfunktion ohne Periode 1 in der nichtlinearen Komponente ist verrückt.
Eric Towers
2

Viele Programmierer passen Redewendungen an, die sie beim Programmieren mit anderen Sprachen gelernt haben. Nicht alle Sprachen haben eine round()Funktion, und in diesen Sprachen ist es normal, sie floor(x + 0.5)als Ersatz zu verwenden. Wenn diese Programmierer anfangen, C ++ zu verwenden, stellen sie nicht immer fest, dass es eine integrierte Funktion gibt round(), sondern verwenden weiterhin den Stil, den sie gewohnt sind.

Mit anderen Worten, nur weil Sie viel Code sehen, der etwas bewirkt, heißt das nicht, dass es einen guten Grund dafür gibt. Beispiele hierfür finden Sie in jeder Programmiersprache. Denken Sie an das Störgesetz :

Neunzig Prozent von allem ist Mist

Barmar
quelle