round () für float in C ++

232

Ich benötige eine einfache Gleitkomma-Rundungsfunktion, also:

double round(double);

round(0.1) = 0
round(-0.1) = 0
round(-0.9) = -1

Ich kann ceil()und floor()in der math.h finden - aber nicht round().

Ist es in der Standard-C ++ - Bibliothek unter einem anderen Namen vorhanden oder fehlt es?

Roddy
quelle
1
Wenn Sie die Zahl nur als gerundete Zahl ausgeben möchten, können Sie dies anscheinend nur tun std::cout << std::fixed << std::setprecision(0) << -0.9.
Frank
43
Dies schützen ... Neue Benutzer mit brillanten neuen Rundungsschemata sollten zuerst vorhandene Antworten lesen.
Shog9
12
roundist seit C ++ 11 in verfügbar <cmath>. Wenn Sie sich in Microsoft Visual Studio befinden, fehlt es leider immer noch: connect.microsoft.com/VisualStudio/feedback/details/775474/…
Alessandro Jacopson
3
Wie ich in meiner Antwort feststelle, hat das Rollen Ihrer eigenen roundviele Einschränkungen. Vor C ++ 11 stützte sich der Standard auf C90, das nicht enthalten war round. C ++ 11 basiert auf C99, das zwar hat, roundaber auch, wie ich bereits erwähnt trunchabe, unterschiedliche Eigenschaften hat und je nach Anwendung besser geeignet sein kann. Die meisten Antworten scheinen auch zu ignorieren, dass ein Benutzer möglicherweise einen integralen Typ zurückgeben möchte, der noch mehr Probleme aufweist.
Shafik Yaghmour
2
@uvts_cvs Dies scheint kein Problem mit der neuesten Version von Visual Studio zu sein. Sehen Sie es live .
Shafik Yaghmour

Antworten:

144

In der C ++ 98-Standardbibliothek gibt es kein round (). Sie können jedoch selbst eine schreiben. Das Folgende ist eine Implementierung der Aufrundung :

double round(double d)
{
  return floor(d + 0.5);
}

Der wahrscheinliche Grund dafür, dass die C ++ 98-Standardbibliothek keine Rundungsfunktion enthält, besteht darin, dass sie tatsächlich auf unterschiedliche Weise implementiert werden kann. Das Obige ist ein üblicher Weg, aber es gibt auch andere, wie z. B. Round-to-Even , die weniger voreingenommen und im Allgemeinen besser sind, wenn Sie viel runden möchten. Die Implementierung ist jedoch etwas komplexer.

Andreas Magnusson
quelle
53
Dies behandelt negative Zahlen nicht richtig. Die Antwort von litb ist richtig.
Registrierter Benutzer
39
@InnerJoin: Ja, negative Zahlen werden anders behandelt als die Antwort von litb, aber das macht es nicht "falsch".
Roddy
39
Das Hinzufügen von 0,5 vor dem Abschneiden wird für mehrere Eingaben, einschließlich 0,49999999999999994, nicht auf die nächste Ganzzahl gerundet. Siehe blog.frama-c.com/index.php?post/2013/05/02/nearbyintf1
Pascal Cuoq
10
@ Sergi0: Es gibt kein "richtig" und "falsch", da es mehr als eine Rundungsdefinition gibt , die entscheidet, was auf halber Strecke passiert. Überprüfen Sie Ihre Fakten, bevor Sie ein Urteil fällen.
Jon
16
@ MuhammadAnnaqeeb: Sie haben Recht, die Dinge haben sich seit der Veröffentlichung von C ++ 11 immens verbessert. Diese Frage wurde in einer anderen Zeit gestellt und beantwortet, als das Leben hart war und die Freuden gering waren. Es bleibt hier als Ode an Helden, die damals lebten und kämpften, und an jene armen Seelen, die immer noch nicht in der Lage sind, moderne Werkzeuge zu benutzen.
Andreas Magnusson
96

Boost bietet eine einfache Reihe von Rundungsfunktionen.

#include <boost/math/special_functions/round.hpp>

double a = boost::math::round(1.5); // Yields 2.0
int b = boost::math::iround(1.5); // Yields 2 as an integer

Weitere Informationen finden Sie in der Boost-Dokumentation .

Bearbeiten : Da C ++ 11 sind std::round, std::lroundundstd::llround .

Daniel Wolf
quelle
2
Ich habe bereits Boost in meinem Projekt verwendet, +1 dafür, viel besser als den naiven floor(value + 0.5)Ansatz!
Gustavo Maciel
@GustavoMaciel Ich weiß, dass ich etwas spät dran bin, aber die Implementierung zu beschleunigen ist floor(value + 0.5).
n. 'Pronomen' m.
Eigentlich nicht: github.com/boostorg/math/blob/develop/include/boost/math/… 4 Jahre später möchte ich auch sagen, dass floor(value + 0.5)das überhaupt nicht naiv ist, sondern vom Kontext und der Natur abhängt von Werten, die Sie runden möchten!
Gustavo Maciel
84

Der C ++ 03-Standard stützt sich auf den C90-Standard für das, was der Standard die Standard-C-Bibliothek nennt, die im Abschnitt zum Entwurf des C ++ 03-Standards (der C ++ 03 am nächsten liegende öffentlich zugängliche Standardentwurf ist N1804 ) behandelt wird. 1.2 Normative Verweise :

Die in Abschnitt 7 von ISO / IEC 9899: 1990 und Abschnitt 7 von ISO / IEC 9899 / Amd.1: 1995 beschriebene Bibliothek wird im Folgenden als Standard C-Bibliothek bezeichnet. 1)

Wenn wir zur C-Dokumentation für round, lround, llround on cppreference gehen , können wir sehen, dass round und verwandte Funktionen Teil von C99 sind und daher in C ++ 03 oder früher nicht verfügbar sind.

In C ++ 11 ändert sich dies, da C ++ 11 auf dem C99-Standardentwurf für die C-Standardbibliothek basiert und daher std :: round und für integrale Rückgabetypen std :: lround, std :: llround bereitstellt :

#include <iostream>
#include <cmath>

int main()
{
    std::cout << std::round( 0.4 ) << " " << std::lround( 0.4 ) << " " << std::llround( 0.4 ) << std::endl ;
    std::cout << std::round( 0.5 ) << " " << std::lround( 0.5 ) << " " << std::llround( 0.5 ) << std::endl ;
    std::cout << std::round( 0.6 ) << " " << std::lround( 0.6 ) << " " << std::llround( 0.6 ) << std::endl ;
}

Eine andere Option auch von C99 wäre std :: trunc, die:

Berechnet die nächste Ganzzahl, deren Größe nicht größer als arg ist.

#include <iostream>
#include <cmath>

int main()
{
    std::cout << std::trunc( 0.4 ) << std::endl ;
    std::cout << std::trunc( 0.9 ) << std::endl ;
    std::cout << std::trunc( 1.1 ) << std::endl ;

}

Wenn Sie Nicht-C ++ 11-Anwendungen unterstützen müssen, verwenden Sie am besten Boost Round, Iround, Lround, Llround oder Boost Trunc .

Es ist schwer, eine eigene Version der Runde zu rollen

Das Rollen Ihres eigenen ist wahrscheinlich nicht die Mühe wert, da es schwieriger ist, als es aussieht: Rundungsfloat auf die nächste Ganzzahl, Teil 1 , Rundungsfloat auf die nächste Ganzzahl, Teil 2 und Rundungsfloat auf die nächste Ganzzahl, Teil 3 erklären:

Beispielsweise funktioniert eine gemeinsame Rolle, die Ihre Implementierung verwendet std::floorund hinzufügt 0.5, nicht für alle Eingaben:

double myround(double d)
{
  return std::floor(d + 0.5);
}

Eine Eingabe, bei der dies fehlschlägt, ist 0.49999999999999994( live sehen ).

Eine andere übliche Implementierung besteht darin, einen Gleitkommatyp in einen integralen Typ umzuwandeln, was zu undefiniertem Verhalten führen kann, wenn der integrale Teil im Zieltyp nicht dargestellt werden kann. Wir können dies aus dem Entwurf des C ++ - Standardabschnitts für 4.9 Floating-Integral-Konvertierungen ersehen, der besagt ( Hervorhebung von mir ):

Ein Wert eines Gleitkommatyps kann in einen Wert eines ganzzahligen Typs konvertiert werden. Die Konvertierung wird abgeschnitten. Das heißt, der Bruchteil wird verworfen. Das Verhalten ist undefiniert, wenn der abgeschnittene Wert im Zieltyp nicht dargestellt werden kann. [...]

Beispielsweise:

float myround(float f)
{
  return static_cast<float>( static_cast<unsigned int>( f ) ) ;
}

Gegeben std::numeric_limits<unsigned int>::max()ist 4294967295dann folgender Aufruf:

myround( 4294967296.5f ) 

wird einen Überlauf verursachen ( live sehen ).

Wir können sehen, wie schwierig dies wirklich ist, wenn wir uns diese Antwort auf den prägnanten Weg ansehen , um round () in C zu implementieren. welche referenzierende Newlibs- Version von Single Precision Float Round. Es ist eine sehr lange Funktion für etwas, das einfach erscheint. Es ist unwahrscheinlich, dass jemand ohne genaue Kenntnisse über Gleitkommaimplementierungen diese Funktion korrekt implementieren kann:

float roundf(x)
{
  int signbit;
  __uint32_t w;
  /* Most significant word, least significant word. */
  int exponent_less_127;

  GET_FLOAT_WORD(w, x);

  /* Extract sign bit. */
  signbit = w & 0x80000000;

  /* Extract exponent field. */
  exponent_less_127 = (int)((w & 0x7f800000) >> 23) - 127;

  if (exponent_less_127 < 23)
    {
      if (exponent_less_127 < 0)
        {
          w &= 0x80000000;
          if (exponent_less_127 == -1)
            /* Result is +1.0 or -1.0. */
            w |= ((__uint32_t)127 << 23);
        }
      else
        {
          unsigned int exponent_mask = 0x007fffff >> exponent_less_127;
          if ((w & exponent_mask) == 0)
            /* x has an integral value. */
            return x;

          w += 0x00400000 >> exponent_less_127;
          w &= ~exponent_mask;
        }
    }
  else
    {
      if (exponent_less_127 == 128)
        /* x is NaN or infinite. */
        return x + x;
      else
        return x;
    }
  SET_FLOAT_WORD(x, w);
  return x;
}

Wenn andererseits keine der anderen Lösungen verwendbar ist, könnte newlib möglicherweise eine Option sein, da es sich um eine gut getestete Implementierung handelt.

Shafik Yaghmour
quelle
5
@downvoter bitte erklären, was verbessert werden kann? Die überwiegende Mehrheit der Antworten hier ist einfach falsch, da sie versuchen, ihre eigene Runde zu rollen, die alle in der einen oder anderen Form fehlschlagen. Wenn in meiner Erklärung etwas fehlt, lassen Sie es mich bitte wissen.
Shafik Yaghmour
1
Schöne vollständige Antwort - besonders der knapp unter 0,5 Teil. Eine weitere Nische : round(-0.0). C-Spezifikation scheint nicht zu spezifizieren. Ich würde -0.0als Ergebnis erwarten .
chux
3
@chux interessant, und der Standard IEEE 754-2008 legt fest, dass beim Runden Vorzeichen von Nullen und Unendlichkeiten erhalten bleiben (siehe 5.9).
Ruslan
1
@Shafik das ist eine tolle Antwort. Ich hätte nie gedacht, dass selbst das Runden eine nicht triviale Operation ist.
Ruslan
1
Vielleicht erwähnenswert, dass dies aus numerischen und Leistungsgründen std::rint()oft vorzuziehen ist, std::round()wenn C ++ 11 verfügbar ist. Im Gegensatz round()zum Spezialmodus wird der aktuelle Rundungsmodus verwendet . Es kann auf x86 viel effizienter sein, wo rintes in eine einzelne Anweisung integriert werden kann. (gcc und clang tun dies auch ohne -ffast-math godbolt.org/g/5UsL2e , während nur clang das nahezu Äquivalent einfügt.nearbyint() ) ARM unterstützt nur einzelne Anweisungen round(), auf x86 kann es jedoch nur mit mehreren Anweisungen und nur mit-ffast-math
Peter Cordes
71

Es kann erwähnenswert sein, dass Sie, wenn Sie ein ganzzahliges Ergebnis aus der Rundung wünschen, es weder durch die Decke noch durch den Boden führen müssen. Dh

int round_int( double r ) {
    return (r > 0.0) ? (r + 0.5) : (r - 0.5); 
}
Kalaxy
quelle
3
Gibt zwar nicht das erwartete Ergebnis für 0.49999999999999994 (naja, je nachdem, was Sie natürlich erwarten, aber 0 scheint mir vernünftiger als 1)
stijn
@stijn Guter Fang. Ich habe festgestellt, dass das Hinzufügen des langen Doppelliteral-Suffix zu meinen Konstanten Ihr Beispielproblem behoben hat, aber ich weiß nicht, ob es andere Präzisionsbeispiele gibt, die es nicht abfangen würde.
Kalaxy
1
Übrigens, wenn Sie 0,49999999999999994 anstelle von 0,5 hinzufügen, funktioniert dies sowohl für 0,49999999999999994 als auch für 5000000000000001.0 als Eingabe. Ich bin mir nicht sicher, ob es für alle Werte in Ordnung ist, und ich konnte keine Referenz finden, die besagt, dass dies die ultimative Lösung ist.
Stijn
1
@stijn Es ist für alle Werte in Ordnung, wenn Sie sich nicht darum kümmern, in welche Richtung die Werte genau zwischen zwei Ganzzahlen gerundet werden. Ohne nachzudenken würde ich es durch Fallanalyse mit den folgenden Fällen beweisen: 0 <= d <0,5, 0,5 <= d <1,5, 1,5 <= d <2 ^ 52, d> = 2 ^ 52. Ich habe auch den Single-Precision-Fall ausführlich getestet.
Pascal Cuoq
3
Gemäß 4.9 [conv.fpint] ist "das Verhalten undefiniert, wenn der abgeschnittene Wert im Zieltyp nicht dargestellt werden kann." Das ist also ein bisschen gefährlich. Andere SO-Antworten beschreiben, wie dies robust gemacht wird.
Tony Delroy
41

Es ist seit C ++ 11 in cmath verfügbar (laut http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf ).

#include <cmath>
#include <iostream>

int main(int argc, char** argv) {
  std::cout << "round(0.5):\t" << round(0.5) << std::endl;
  std::cout << "round(-0.5):\t" << round(-0.5) << std::endl;
  std::cout << "round(1.4):\t" << round(1.4) << std::endl;
  std::cout << "round(-1.4):\t" << round(-1.4) << std::endl;
  std::cout << "round(1.6):\t" << round(1.6) << std::endl;
  std::cout << "round(-1.6):\t" << round(-1.6) << std::endl;
  return 0;
}

Ausgabe:

round(0.5):  1
round(-0.5): -1
round(1.4):  1
round(-1.4): -1
round(1.6):  2
round(-1.6): -2
Schibum
quelle
1
es gibt auch lroundund llroundfür integrale Ergebnisse
sp2danny
@ sp2danny: oder besser, lrintum den aktuellen Rundungsmodus anstelle des roundfunky Tiebreak von Null zu verwenden.
Peter Cordes
27

Es wird normalerweise als implementiert floor(value + 0.5).

Bearbeiten: und es wird wahrscheinlich nicht rund genannt, da mir mindestens drei Rundungsalgorithmen bekannt sind: auf Null runden, auf die nächste ganze Zahl runden und die Rundung des Bankiers. Sie fragen nach einer runden bis nächsten Ganzzahl.

MSN
quelle
1
Es ist gut, zwischen verschiedenen Versionen von "rund" zu unterscheiden. Es ist gut zu wissen, wann man welche auswählt.
xtofl
5
Es gibt in der Tat verschiedene Rundungsalgorithmen, die alle vernünftige Behauptungen aufstellen können, "korrekt" zu sein. Der Boden (Wert + 0,5) gehört jedoch nicht dazu. Für einige Werte, wie z. B. 0,49999997f oder das entsprechende Doppel, ist die Antwort einfach falsch - sie wird auf 1,0 gerundet, wenn alle zustimmen, dass sie Null sein sollte. Siehe diesen Beitrag für Details: blog.frama-c.com/index.php?post/2013/05/02/nearbyintf1
Bruce Dawson
14

Wir betrachten zwei Probleme:

  1. Rundungsumwandlungen
  2. Typkonvertierung.

Rundungsumwandlungen bedeuten Rundung ± Float / Double auf die nächste Etage / Ceil Float / Double. Vielleicht endet Ihr Problem hier. Wenn jedoch erwartet wird, dass Sie Int / Long zurückgeben, müssen Sie eine Typkonvertierung durchführen. Daher kann das Problem "Überlauf" Ihre Lösung treffen. Überprüfen Sie Ihre Funktion auf Fehler

long round(double x) {
   assert(x >= LONG_MIN-0.5);
   assert(x <= LONG_MAX+0.5);
   if (x >= 0)
      return (long) (x+0.5);
   return (long) (x-0.5);
}

#define round(x) ((x) < LONG_MIN-0.5 || (x) > LONG_MAX+0.5 ?\
      error() : ((x)>=0?(long)((x)+0.5):(long)((x)-0.5))

von: http://www.cs.tut.fi/~jkorpela/round.html

Sangeet
quelle
Verwenden LONG_MIN-0.5und LONG_MAX+0.5 Einführen von Komplikationen, da die Mathematik möglicherweise nicht genau ist. LONG_MAXkann die doubleGenauigkeit für eine genaue Konvertierung überschreiten . Weitere wollen wahrscheinlich assert(x < LONG_MAX+0.5); (<vs <=), da LONG_MAX+0.5sie genau darstellbar sind und (x)+0.5ein genaues Ergebnis haben können, LONG_MAX+1dessen longBesetzung fehlschlägt . Andere Eckprobleme auch.
chux
Rufen Sie Ihre Funktion nicht auf round(double), es gibt bereits eine Standardfunktion für die Mathematikbibliothek mit diesem Namen (in C ++ 11), daher ist sie verwirrend. Verwenden Sie, std::lrint(x)wenn es verfügbar ist.
Peter Cordes
11

Eine bestimmte Art der Rundung ist auch in Boost implementiert:

#include <iostream>

#include <boost/numeric/conversion/converter.hpp>

template<typename T, typename S> T round2(const S& x) {
  typedef boost::numeric::conversion_traits<T, S> Traits;
  typedef boost::numeric::def_overflow_handler OverflowHandler;
  typedef boost::numeric::RoundEven<typename Traits::source_type> Rounder;
  typedef boost::numeric::converter<T, S, Traits, OverflowHandler, Rounder> Converter;
  return Converter::convert(x);
}

int main() {
  std::cout << round2<int, double>(0.1) << ' ' << round2<int, double>(-0.1) << ' ' << round2<int, double>(-0.9) << std::endl;
}

Beachten Sie, dass dies nur funktioniert, wenn Sie eine Ganzzahlkonvertierung durchführen.

Philipp
quelle
2
Boost bietet auch eine Reihe einfacher Rundungsfunktionen. siehe meine Antwort.
Daniel Wolf
Sie können auch boost:numeric::RoundEven< double >::nearbyintdirekt verwenden, wenn Sie keine Ganzzahl möchten. @ DanielWolf beachten Sie, dass die einfache Funktion mit +0.5 implementiert wird, die Probleme hat, wie von aka.nice
stijn
6

Sie können die Genauigkeit auf n Stellen runden mit:

double round( double x )
{
const double sd = 1000; //for accuracy to 3 decimal places
return int(x*sd + (x<0? -0.5 : 0.5))/sd;
}
Carl
quelle
4
Wenn Ihre Compiler-Int-Größe nicht standardmäßig 1024 Bit beträgt, ist dies für ein riesiges Double nicht genau ...
aka.nice
Ich denke, das ist akzeptabel, wenn es verwendet wird: Wenn Ihr Doppelwert 1,0 e + 19 ist, ist es nicht sinnvoll, auf 3 Stellen abzurunden.
Carl
3
Sicher, aber die Frage bezieht sich auf eine generische Runde, und Sie können nicht steuern, wie sie verwendet wird. Es gibt keinen Grund für ein Versagen der Runde, wo Decke und Boden dies nicht tun würden.
aka.nice
Dies hat ein undefiniertes Verhalten für Argumente außerhalb des Bereichs von int. (In der Praxis auf x86, out-of-Range - FP - Werte machen CVTTSD2SIproduzieren0x80000000 als der ganzzahlige Bitmuster, das heißt INT_MIN, die dann zurück umgewandelt werden double.
Peter Cordes
5

Heutzutage sollte es kein Problem sein, einen C ++ 11-Compiler zu verwenden, der eine C99 / C ++ 11-Mathematikbibliothek enthält. Aber dann stellt sich die Frage: Welche Rundungsfunktion wählen Sie?

C99 / C ++ 11 round()ist oft nicht die gewünschte Rundungsfunktion . Es wird ein funky Rundungsmodus verwendet, der von 0 als Gleichstand für Fälle auf halber Strecke abrundet ( +-xxx.5000). Wenn Sie diesen Rundungsmodus speziell möchten oder auf eine C ++ - Implementierung abzielen, die round()schneller als ist rint(), verwenden Sie sie (oder emulieren Sie ihr Verhalten mit einer der anderen Antworten auf diese Frage, die sie zum Nennwert angenommen und diese spezifisch sorgfältig reproduziert haben Rundungsverhalten.)

round()Die Rundung unterscheidet sich von der IEEE754-Standardrunde auf den nächstgelegenen Modus mit gleichmäßiger Unterbrechung . Nearest-Even vermeidet statistische Verzerrungen bei der durchschnittlichen Größe von Zahlen, tendiert jedoch zu geraden Zahlen.

Es gibt zwei Rundungsfunktionen für Mathematikbibliotheken, die den aktuellen Standardrundungsmodus verwenden: std::nearbyint()und std::rint()beide wurden in C99 / C ++ 11 hinzugefügt, sodass sie jederzeit verfügbar std::round()sind. Der einzige Unterschied ist, dass nearbyintFE_INEXACT niemals ausgelöst wird.

Aus rint()Leistungsgründen bevorzugen : gcc und clang inline es leichter, aber gcc inline nie nearbyint()(auch mit -ffast-math)


gcc / clang für x86-64 und AArch64

Ich habe einige Testfunktionen in den Compiler-Explorer von Matt Godbolt gestellt , in denen Sie die Ausgabe von source + asm sehen können (für mehrere Compiler). Weitere Informationen zum Lesen der Compiler-Ausgabe finden Sie in diesen Fragen und Antworten sowie in Matts CppCon2017-Vortrag: „Was hat mein Compiler in letzter Zeit für mich getan? Aufschrauben des Compilerdeckels “ ,

Im FP-Code ist es normalerweise ein großer Gewinn, kleine Funktionen zu integrieren. Insbesondere unter Nicht-Windows, wo die Standardaufrufkonvention keine aufruferhaltenen Register enthält, kann der Compiler keine FP-Werte in XMM-Registern über a speichern call. Selbst wenn Sie asm nicht wirklich kennen, können Sie leicht erkennen, ob es sich nur um einen Tail-Call für die Bibliotheksfunktion handelt oder ob es sich um eine oder zwei mathematische Anweisungen handelt. Alles, was in eine oder zwei Anweisungen eingefügt wird, ist besser als ein Funktionsaufruf (für diese bestimmte Aufgabe unter x86 oder ARM).

Unter x86 kann alles, was in SSE4.1 integriert roundsdist, automatisch mit SSE4.1 roundpd(oder AVX vroundpd) vektorisiert werden . (FP-> Integer-Konvertierungen sind auch in gepackter SIMD-Form verfügbar, mit Ausnahme von FP-> 64-Bit-Integer, für die AVX512 erforderlich ist.)

  • std::nearbyint()::

    • x86 clang: Inlines zu einem einzelnen Insn mit -msse4.1.
    • x86 gcc: Inlines zu einem einzelnen Insn nur mit -msse4.1 -ffast-mathund nur auf gcc 5.4 und früher . Später nie gcc inlines es (vielleicht haben sie nicht , dass einer der unmittelbaren Bits realisieren können die ungenauen Ausnahme unterdrücken? Das ist , was Klirren Nutzen, aber ältere gcc verwendet die gleiche sofort wie rintwenn es funktioniert inline it)
    • AArch64 gcc6.3: Standardmäßig Inlines zu einem einzelnen Insn.
  • std::rint::

    • x86 clang: Inlines zu einem einzelnen Insn mit -msse4.1
    • x86 gcc7: Inlines zu einem einzelnen Insn mit -msse4.1. (Ohne SSE4.1 inline zu mehreren Anweisungen)
    • x86 gcc6.x und früher: Inlines zu einem einzelnen Insn mit -ffast-math -msse4.1.
    • AArch64 gcc: Standardmäßig Inlines zu einem einzelnen Insn
  • std::round::

    • x86 clang: nicht inline
    • x86 gcc: Inline zu mehreren Anweisungen mit -ffast-math -msse4.1, wobei zwei Vektorkonstanten erforderlich sind.
    • AArch64 gcc: Inlines zu einer einzelnen Anweisung (HW-Unterstützung für diesen Rundungsmodus sowie IEEE-Standard und die meisten anderen.)
  • std::floor/ std::ceil/std::trunc

    • x86 clang: Inlines zu einem einzelnen Insn mit -msse4.1
    • x86 gcc7.x: Inlines zu einem einzelnen Insn mit -msse4.1
    • x86 gcc6.x und früher: Inlines zu einem einzelnen Insn mit -ffast-math -msse4.1
    • AArch64 gcc: Standardmäßig wird eine einzelne Anweisung eingefügt

Rundung auf int/ long/ long long:

Hier haben Sie zwei Möglichkeiten: Verwenden lrint(wie, rintaber Rückgabe longoder long longfür llrint) oder Verwenden einer FP-> FP-Rundungsfunktion und anschließendes Konvertieren in einen Ganzzahltyp auf normale Weise (mit Kürzung). Einige Compiler optimieren auf die eine Weise besser als auf die andere.

long l = lrint(x);

int  i = (int)rint(x);

Beachten Sie, dass zuerst oder -> int i = lrint(x)konvertiert und dann die Ganzzahl auf abgeschnitten wird . Dies macht einen Unterschied für Ganzzahlen außerhalb des Bereichs: Undefiniertes Verhalten in C ++, aber gut definiert für die x86-FP -> int-Anweisungen (die der Compiler ausgibt, es sei denn, er sieht die UB zur Kompilierungszeit, während er eine konstante Weitergabe ausführt) darf Code machen, der kaputt geht, wenn er jemals ausgeführt wird).floatdoublelongint

Unter x86 erzeugt eine FP-> Ganzzahlkonvertierung, die die Ganzzahl überläuft, INT_MINoder LLONG_MIN(ein Bitmuster von 0x8000000oder das 64-Bit-Äquivalent, wobei nur das Vorzeichenbit gesetzt ist). Intel nennt dies den Wert "Integer Indefinite". (Siehe den cvttsd2simanuellen Eintrag , den SSE2-Befehl, der (mit Kürzung) skalares Doppel in vorzeichenbehaftete Ganzzahlen konvertiert. Er ist mit einem 32-Bit- oder 64-Bit-Ganzzahlziel verfügbar (nur im 64-Bit-Modus). Es gibt auch einen cvtsd2si(Konvertieren mit aktueller Rundung) mode), das ist es, was der Compiler ausgeben soll, aber leider werden gcc und clang das nicht ohne tun -ffast-math.

Beachten Sie auch, dass FP zu / von unsignedint / long unter x86 (ohne AVX512) weniger effizient ist. Die Konvertierung in 32-Bit ohne Vorzeichen auf einem 64-Bit-Computer ist ziemlich billig. Konvertieren Sie einfach in 64-Bit-Signatur und schneiden Sie sie ab. Ansonsten ist es deutlich langsamer.

  • x86 klirrte mit / ohne -ffast-math -msse4.1: (int/long)rintInlines zu roundsd/ cvttsd2si. (verpasste Optimierung zu cvtsd2si). lrintüberhaupt nicht inline.

  • x86 gcc6.x und früher ohne -ffast-math: weder inlines noch inline

  • x86 gcc7 ohne -ffast-math: (int/long)rintrundet und konvertiert separat (mit 2 Gesamtanweisungen von SSE4.1 ist aktiviert, andernfalls mit einer Reihe von Code, der für rintohne eingefügt ist roundsd). lrintnicht inline.
  • x86 gcc mit -ffast-math : alle Wege inline zu cvtsd2si(optimal) , keine Notwendigkeit für SSE4.1.

  • AArch64 gcc6.3 ohne -ffast-math: (int/long)rintInlines zu 2 Anweisungen. lrintnicht inline

  • AArch64 gcc6.3 with -ffast-math: (int/long)rintKompiliert zu einem Aufruf von lrint. lrintnicht inline. Dies kann eine verpasste Optimierung sein, es sei denn, die beiden Anweisungen, auf die wir verzichten, -ffast-mathsind sehr langsam.
Peter Cordes
quelle
TODO: ICC und MSVC sind auch auf Godbolt verfügbar, aber ich habe mir ihre Ausgabe dafür nicht angesehen. Änderungen willkommen ... Auch: Wäre es sinnvoller, zuerst nach Compiler / Version und dann nach Funktion darin aufzuschlüsseln? Die meisten Leute werden die Compiler nicht wechseln, je nachdem, wie gut sie FP-> FP oder FP-> Integer-Rundungen kompilieren.
Peter Cordes
2
+1 für die Empfehlung, rint()wo dies eine praktikable Wahl ist, was normalerweise der Fall ist. Ich denke, der Name round()impliziert für einige Programmierer, dass dies das ist, was sie wollen, während rint()es mysteriös erscheint. Beachten Sie, dass round()kein "funky" -Rundungsmodus verwendet wird: "Round-to-Next-Tie-Away" ist ein offizieller IEEE-754 (2008) -Rundungsmodus. Es ist merkwürdig, dass nearbyint()es nicht inline wird, da es weitgehend dasselbe rint()ist und unter bestimmten Bedingungen identisch sein sollte -ffast-math. Das sieht für mich bug-ish aus.
Njuffa
4

Vorsicht vor floor(x+0.5). Folgendes kann für ungerade Zahlen im Bereich [2 ^ 52,2 ^ 53] passieren:

-bash-3.2$ cat >test-round.c <<END

#include <math.h>
#include <stdio.h>

int main() {
    double x=5000000000000001.0;
    double y=round(x);
    double z=floor(x+0.5);
    printf("      x     =%f\n",x);
    printf("round(x)    =%f\n",y);
    printf("floor(x+0.5)=%f\n",z);
    return 0;
}
END

-bash-3.2$ gcc test-round.c
-bash-3.2$ ./a.out
      x     =5000000000000001.000000
round(x)    =5000000000000001.000000
floor(x+0.5)=5000000000000002.000000

Dies ist http://bugs.squeak.org/view.php?id=7134 . Verwenden Sie eine Lösung wie die von @konik.

Meine eigene robuste Version wäre ungefähr so:

double round(double x)
{
    double truncated,roundedFraction;
    double fraction = modf(x, &truncated);
    modf(2.0*fraction, &roundedFraction);
    return truncated + roundedFraction;
}

Ein weiterer Grund zur Vermeidung von Boden (x + 0,5) wird hier angegeben .

aka.nice
quelle
2
Ich bin interessiert über die Downvotes zu wissen. Liegt es daran, dass das Unentschieden von Null weg und nicht bis zur nächsten Geraden gelöst wird?
aka.nice
1
Hinweis: In der C-Spezifikation heißt es "Rundung von Fällen auf halber Strecke von Null weg, unabhängig von der aktuellen Rundungsrichtung". Daher ist eine Rundung ohne Rücksicht auf ungerade / gerade konform.
chux
4

Wenn Sie letztendlich die doubleAusgabe Ihrer round()Funktion in eine konvertieren möchten int, sehen die akzeptierten Lösungen dieser Frage ungefähr so ​​aus:

int roundint(double r) {
  return (int)((r > 0.0) ? floor(r + 0.5) : ceil(r - 0.5));
}

Dies erreicht auf meinem Computer ungefähr 8,88 ns , wenn es in gleichmäßig zufälligen Werten übergeben wird.

Das Folgende ist, soweit ich das beurteilen kann, funktional gleichwertig, taktet jedoch auf meinem Computer mit 2,48 ns , um einen signifikanten Leistungsvorteil zu erzielen:

int roundint (double r) {
  int tmp = static_cast<int> (r);
  tmp += (r-tmp>=.5) - (r-tmp<=-.5);
  return tmp;
}

Zu den Gründen für die bessere Leistung gehört die übersprungene Verzweigung.

dshin
quelle
Dies hat ein undefiniertes Verhalten für Argumente außerhalb des Bereichs von int. (In der Praxis auf x86, out-of-Range - FP - Werte machen CVTTSD2SIproduzieren0x80000000 als der ganzzahlige Bitmuster, das heißt INT_MIN, die dann zurück umgewandelt werden double.
Peter Cordes
2

Es ist nicht erforderlich, etwas zu implementieren, daher bin ich mir nicht sicher, warum so viele Antworten Definitionen, Funktionen oder Methoden beinhalten.

In C99

Wir haben das folgende und und den Header <tgmath.h> für typgenerische Makros.

#include <math.h>
double round (double x);
float roundf (float x);
long double roundl (long double x);

Wenn Sie dies nicht kompilieren können, haben Sie wahrscheinlich die Mathematikbibliothek ausgelassen. Ein ähnlicher Befehl funktioniert auf jedem C-Compiler, den ich habe (mehrere).

gcc -lm -std=c99 ...

In C ++ 11

Wir haben die folgenden und zusätzlichen Überladungen in #include <cmath>, die auf IEEE-Gleitkommazahlen mit doppelter Genauigkeit beruhen.

#include <math.h>
double round (double x);
float round (float x);
long double round (long double x);
double round (T x);

Es gibt auch Entsprechungen im Standard-Namespace .

Wenn Sie dies nicht kompilieren können, verwenden Sie möglicherweise die C-Kompilierung anstelle von C ++. Der folgende grundlegende Befehl erzeugt weder Fehler noch Warnungen mit g ++ 6.3.1, x86_64-w64-mingw32-g ++ 6.3.0, clang-x86_64 ++ 3.8.0 und Visual C ++ 2015 Community.

g++ -std=c++11 -Wall

Mit ordinaler Teilung

Wenn zwei Ordnungszahlen geteilt werden, wobei T kurz, int, lang oder eine andere Ordnungszahl ist, ist dies der Rundungsausdruck.

T roundedQuotient = (2 * integerNumerator + 1)
    / (2 * integerDenominator);

Richtigkeit

Es besteht kein Zweifel, dass bei Gleitkommaoperationen seltsam aussehende Ungenauigkeiten auftreten. Dies ist jedoch nur dann der Fall, wenn die Zahlen angezeigt werden, und hat wenig mit Rundung zu tun.

Die Quelle ist nicht nur die Anzahl der signifikanten Stellen in der Mantisse der IEEE-Darstellung einer Gleitkommazahl, sondern hängt auch mit unserem Dezimaldenken als Mensch zusammen.

Zehn ist das Produkt von fünf und zwei, und fünf und zwei sind relativ prim. Daher können die IEEE-Gleitkomma-Standards möglicherweise nicht perfekt als Dezimalzahlen für alle binären digitalen Darstellungen dargestellt werden.

Dies ist bei den Rundungsalgorithmen kein Problem. Es ist die mathematische Realität, die bei der Auswahl der Typen und der Gestaltung der Berechnungen, der Dateneingabe und der Anzeige von Zahlen berücksichtigt werden sollte. Wenn eine Anwendung die Ziffern anzeigt, die diese Probleme mit der Dezimal-Binär-Konvertierung anzeigen, drückt die Anwendung visuell Genauigkeit aus, die in der digitalen Realität nicht vorhanden ist und geändert werden sollte.

FauChristian
quelle
1
"Ich bin mir nicht sicher, warum so viele Antworten Definitionen, Funktionen oder Methoden beinhalten." Schauen Sie sich an, wann es gefragt wurde - C ++ 11 war noch nicht verfügbar. ;)
jaggedSpire
@jaggedSpire, dann gib mir einen Daumen hoch, wenn du es für angemessen hältst, denn alle Antworten mit hoher Punktzahl sind veraltet und im Kontext der heute am häufigsten verwendeten Compiler irreführend.
FauChristian
2

Funktion double round(double)mit der Verwendung der modfFunktion:

double round(double x)
{
    using namespace std;

    if ((numeric_limits<double>::max() - 0.5) <= x)
        return numeric_limits<double>::max();

    if ((-1*std::numeric_limits<double>::max() + 0.5) > x)
        return (-1*std::numeric_limits<double>::max());

    double intpart;
    double fractpart = modf(x, &intpart);

    if (fractpart >= 0.5)
        return (intpart + 1);
    else if (fractpart >= -0.5)
        return intpart;
    else
        return (intpart - 1);
    }

Um sauber zu kompilieren, sind "math.h" und "Limits" erforderlich. Die Funktion arbeitet nach folgendem Rundungsschema:

  • Runde 5.0 ist 5.0
  • Runde 3,8 ist 4,0
  • Runde 2,3 ist 2,0
  • Runde von 1,5 ist 2,0
  • Runde von 0,501 ist 1,0
  • Runde von 0,5 ist 1,0
  • Runde von 0,499 ist 0,0
  • Runde von 0,01 ist 0,0
  • Runde von 0.0 ist 0.0
  • Runde von -0,01 ist -0,0
  • Runde von -0,499 ist -0,0
  • Runde von -0,5 ist -0,0
  • Runde von -0,501 ist -1,0
  • Runde von -1,5 ist -1,0
  • Runde von -2,3 ist -2,0
  • Runde von -3,8 ist -4,0
  • Runde von -5,0 ist -5,0
konik
quelle
2
Dies ist eine gute Lösung. Ich bin mir nicht sicher, ob das Runden von -1,5 auf -1,0 Standard ist, ich würde -2,0 durch Symmetrie erwarten. Ich sehe auch nicht die Spitze der führenden Wache, die ersten beiden könnten entfernt werden.
aka.nice
2
Ich habe die Norm ISO / IEC 10967-2 unter open-std.org/jtc1/sc22/wg11/docs/n462.pdf eingecheckt und aus Anhang B.5.2.4 muss die Rundungsfunktion tatsächlich symmetrisch sein, rounding_F (x) = neg_F (rounding_F (neg_F (x)))
aka.nice
Dies wird im Vergleich zu C ++ 11 rint()oder langsamer sein nearbyint(), aber wenn Sie wirklich keinen Compiler verwenden können, der eine ordnungsgemäße Rundungsfunktion bietet, und Sie mehr Präzision als Leistung benötigen ...
Peter Cordes
1

Wenn Sie in der Lage sein müssen, Code in Umgebungen zu kompilieren, die den C ++ 11-Standard unterstützen, aber auch in Umgebungen, die ihn nicht unterstützen, denselben Code kompilieren müssen, können Sie ein Funktionsmakro verwenden, um zwischen std zu wählen :: round () und eine benutzerdefinierte Funktion für jedes System. Übergeben Sie einfach -DCPP11oder /DCPP11an den C ++ 11-kompatiblen Compiler (oder verwenden Sie die integrierten Versionsmakros) und erstellen Sie einen Header wie den folgenden:

// File: rounding.h
#include <cmath>

#ifdef CPP11
    #define ROUND(x) std::round(x)
#else    /* CPP11 */
    inline double myRound(double x) {
        return (x >= 0.0 ? std::floor(x + 0.5) : std::ceil(x - 0.5));
    }

    #define ROUND(x) myRound(x)
#endif   /* CPP11 */

Ein kurzes Beispiel finden Sie unter http://ideone.com/zal709 .

Dies entspricht std :: round () in Umgebungen, die nicht C ++ 11-kompatibel sind, einschließlich der Beibehaltung des Vorzeichenbits für -0.0. Dies kann jedoch zu einem leichten Leistungseinbruch führen und wahrscheinlich Probleme beim Runden bestimmter bekannter "Problem" -Gleitkommawerte wie 0,49999999999999994 oder ähnlicher Werte verursachen.

Wenn Sie Zugriff auf einen C ++ 11-kompatiblen Compiler haben, können Sie alternativ einfach std :: round () aus dem <cmath>Header holen und daraus einen eigenen Header erstellen, der die Funktion definiert, sofern dieser noch nicht definiert ist. Beachten Sie jedoch, dass dies möglicherweise keine optimale Lösung ist, insbesondere wenn Sie für mehrere Plattformen kompilieren müssen.

Justin Time - Monica wieder einsetzen
quelle
1

Basierend auf der Antwort von Kalaxy ist das Folgende eine Vorlagenlösung, die jede Gleitkommazahl basierend auf der natürlichen Rundung auf den nächsten ganzzahligen Typ rundet. Im Debug-Modus wird auch ein Fehler ausgegeben, wenn der Wert außerhalb des Bereichs des Integer-Typs liegt, wodurch er in etwa als funktionsfähige Bibliotheksfunktion dient.

    // round a floating point number to the nearest integer
    template <typename Arg>
    int Round(Arg arg)
    {
#ifndef NDEBUG
        // check that the argument can be rounded given the return type:
        if (
            (Arg)std::numeric_limits<int>::max() < arg + (Arg) 0.5) ||
            (Arg)std::numeric_limits<int>::lowest() > arg - (Arg) 0.5)
            )
        {
            throw std::overflow_error("out of bounds");
        }
#endif

        return (arg > (Arg) 0.0) ? (int)(r + (Arg) 0.5) : (int)(r - (Arg) 0.5);
    }
quant
quelle
1
Wie ich in meiner Antwort ausgeführt habe , 0.5funktioniert das Hinzufügen nicht in allen Fällen. Obwohl Sie sich zumindest mit dem Überlaufproblem befassen, vermeiden Sie undefiniertes Verhalten.
Shafik Yaghmour
1

Wie in Kommentaren und anderen Antworten erwähnt, wurde die ISO C ++ - Standardbibliothek round()erst nach ISO C ++ 11 hinzugefügt , als diese Funktion unter Bezugnahme auf die ISO C99-Standardmathematikbibliothek aufgerufen wurde.

Für positive Operanden in [½, ub ] round(x) == floor (x + 0.5), wo ub 2 23 für floatbei IEEE-754 kartiert (2008) binary32, und 2 52 zu , doublewenn es zu IEEE-754 abgebildet wird (2008) binary64. Die Zahlen 23 und 52 entsprechen der Anzahl der gespeicherten Mantissenbits in diesen beiden Gleitkommaformaten. Für positive Operanden in [+0, ½) round(x) == 0und für positive Operanden in ( ub , + ∞] round(x) == x. Da die Funktion symmetrisch zur x-Achse ist, können negative Argumente xentsprechend behandelt werden round(-x) == -round(x).

Dies führt zu dem folgenden kompakten Code. Es wird zu einer angemessenen Anzahl von Maschinenanweisungen auf verschiedenen Plattformen kompiliert. Ich habe den kompaktesten Code auf GPUs beobachtet, für my_roundf()den etwa ein Dutzend Anweisungen erforderlich sind. Abhängig von der Prozessorarchitektur und der Toolchain kann dieser Gleitkomma-basierte Ansatz entweder schneller oder langsamer sein als die ganzzahlige Implementierung von newlib, auf die in einer anderen Antwort verwiesen wird .

Ich habe die Intel-Implementierung mit Intel Compiler Version 13 mit und my_roundf()ausführlich getestet . Ich habe auch überprüft, ob die newlib-Version mit der in der Bibliothek des Intel-Compilers übereinstimmt . Ausführliche Tests sind bei doppelter Genauigkeit nicht möglich , der Code ist jedoch strukturell identisch mit der Implementierung mit einfacher Genauigkeit.roundf()/fp:strict/fp:fastroundf()mathimfround()

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <math.h>

float my_roundf (float x)
{
    const float half = 0.5f;
    const float one = 2 * half;
    const float lbound = half;
    const float ubound = 1L << 23;
    float a, f, r, s, t;
    s = (x < 0) ? (-one) : one;
    a = x * s;
    t = (a < lbound) ? x : s;
    f = (a < lbound) ? 0 : floorf (a + half);
    r = (a > ubound) ? x : (t * f);
    return r;
}

double my_round (double x)
{
    const double half = 0.5;
    const double one = 2 * half;
    const double lbound = half;
    const double ubound = 1ULL << 52;
    double a, f, r, s, t;
    s = (x < 0) ? (-one) : one;
    a = x * s;
    t = (a < lbound) ? x : s;
    f = (a < lbound) ? 0 : floor (a + half);
    r = (a > ubound) ? x : (t * f);
    return r;
}

uint32_t float_as_uint (float a)
{
    uint32_t r;
    memcpy (&r, &a, sizeof(r));
    return r;
}

float uint_as_float (uint32_t a)
{
    float r;
    memcpy (&r, &a, sizeof(r));
    return r;
}

float newlib_roundf (float x)
{
    uint32_t w;
    int exponent_less_127;

    w = float_as_uint(x);
    /* Extract exponent field. */
    exponent_less_127 = (int)((w & 0x7f800000) >> 23) - 127;
    if (exponent_less_127 < 23) {
        if (exponent_less_127 < 0) {
            /* Extract sign bit. */
            w &= 0x80000000;
            if (exponent_less_127 == -1) {
                /* Result is +1.0 or -1.0. */
                w |= ((uint32_t)127 << 23);
            }
        } else {
            uint32_t exponent_mask = 0x007fffff >> exponent_less_127;
            if ((w & exponent_mask) == 0) {
                /* x has an integral value. */
                return x;
            }
            w += 0x00400000 >> exponent_less_127;
            w &= ~exponent_mask;
        }
    } else {
        if (exponent_less_127 == 128) {
            /* x is NaN or infinite so raise FE_INVALID by adding */
            return x + x;
        } else {
            return x;
        }
    }
    x = uint_as_float (w);
    return x;
}

int main (void)
{
    uint32_t argi, resi, refi;
    float arg, res, ref;

    argi = 0;
    do {
        arg = uint_as_float (argi);
        ref = newlib_roundf (arg);
        res = my_roundf (arg);
        resi = float_as_uint (res);
        refi = float_as_uint (ref);
        if (resi != refi) { // check for identical bit pattern
            printf ("!!!! arg=%08x  res=%08x  ref=%08x\n", argi, resi, refi);
            return EXIT_FAILURE;
        }
        argi++;
    } while (argi);
    return EXIT_SUCCESS;
}
Njuffa
quelle
Ich habe eine Bearbeitung vorgenommen, um zu vermeiden, dass angenommen intwird , dass sie mehr als 16 Bit breit ist. Es wird natürlich immer noch davon ausgegangen, dass floates sich um 4-Byte-IEEE754-Binär32 handelt. Ein C ++ 11 static_assertoder vielleicht ein Makro #ifdef/ #errorkönnte das überprüfen. (Aber wenn C ++ 11 verfügbar ist, sollten Sie es natürlich verwenden std::roundoder für den aktuellen Rundungsmodus verwenden, std::rintder gut zu gcc und clang passt.)
Peter Cordes
Übrigens, gcc -ffast-math -msse4.1Inlines std::round()zu einem add( AND(x, L1), OR(x,L2)und dann zu einem roundsd. dh es implementiert ziemlich effizient roundin Bezug auf rint. Es gibt jedoch keinen Grund, dies manuell in C ++ zu tun, denn wenn Sie haben std::rint()oder std::nearbyint()auch haben std::round(). Siehe meine Antwort für einen Godbolt-Link und einen Überblick darüber, was mit verschiedenen gcc / clang-Versionen inline ist oder nicht.
Peter Cordes
@PeterCordes Ich weiß genau, wie man round()effizient implementiert rint()(wenn letzterer im Modus "Round-to-Nearest-or-Even" arbeitet): Ich habe das für die CUDA-Standard-Mathematikbibliothek implementiert. Diese Frage schien jedoch zu fragen, wie round()mit C ++ vor C ++ 11 implementiert werden rint()soll, floor()und wäre daher auch nicht verfügbar, nur und ceil().
Njuffa
@ PeterCordes Sorry, ich habe falsch geschrieben. round()wird leicht rint()im Round-to-Zero- Modus synthetisiert , auch bekannt als trunc(). Hätte nicht vor dem ersten Kaffee antworten sollen.
Njuffa
1
@PeterCordes Ich stimme zu, dass es wahrscheinlich ist, dass OP nicht das spezifische Rundungsverhalten von benötigt round(); Die meisten Programmierer sind sich der Unterscheidung zwischen round()vs rint()und Round-to-Nearest-Even einfach nicht bewusst , wobei letztere normalerweise direkt von der Hardware bereitgestellt werden und daher effizienter sind. Ich habe dies im CUDA-Programmierhandbuch dargelegt, um Programmierer darauf aufmerksam zu machen: "Die empfohlene Methode, einen Gleitkommaoperanden mit einfacher Genauigkeit auf eine Ganzzahl zu runden, wobei das Ergebnis eine Gleitkommazahl mit einfacher Genauigkeit ist rintf(), ist nicht roundf()".
Njuffa
0

Ich verwende die folgende Implementierung von round in asm für x86-Architektur und MS VS-spezifisches C ++:

__forceinline int Round(const double v)
{
    int r;
    __asm
    {
        FLD     v
        FISTP   r
        FWAIT
    };
    return r;
}

UPD: um einen doppelten Wert zurückzugeben

__forceinline double dround(const double v)
{
    double r;
    __asm
    {
        FLD     v
        FRNDINT
        FSTP    r
        FWAIT
    };
    return r;
}

Ausgabe:

dround(0.1): 0.000000000000000
dround(-0.1): -0.000000000000000
dround(0.9): 1.000000000000000
dround(-0.9): -1.000000000000000
dround(1.1): 1.000000000000000
dround(-1.1): -1.000000000000000
dround(0.49999999999999994): 0.000000000000000
dround(-0.49999999999999994): -0.000000000000000
dround(0.5): 0.000000000000000
dround(-0.5): -0.000000000000000
Aleksey F.
quelle
Der Ergebniswert sollte ein Gleitkommawert mit doppelter Genauigkeit sein.
Wahrheitssucher
@ truthseeker: Ja, ich musste den erforderlichen Rückgabewert sehen. OK, siehe "UPD".
Aleksey F.
Der Compiler wird hoffentlich inline rint()oder nearbyint()zu einem SSE4.1- roundsdBefehl oder einem x87- frndintBefehl, was viel schneller ist als die zwei Roundtrips zum Speichern / Neuladen, die erforderlich sind, um diesen Inline-Asm für Daten in einem Register zu verwenden. MSVC Inline Asm ist ziemlich schlecht für das Umschließen einzelner Anweisungen, zum Beispiel, frndintweil es keine Möglichkeit gibt, die Eingabe in ein Register zu bekommen. Die Verwendung am Ende einer Funktion mit dem Ergebnis in ist st(0)möglicherweise zuverlässig, um die Ausgabe zurückzugeben. anscheinend ist das eaxfür ganze Zahlen sicher , selbst wenn es die Funktion enthält, die den asm enthält.
Peter Cordes
@PeterCordes Moderne Optimierungen sind willkommen. Ich konnte SSE4.1 jedoch nicht verwenden, da es zu diesem Zeitpunkt noch nicht existierte. Mein Ziel war es, die minimale Implementierung von Round bereitzustellen, die sogar auf alten Intel P3- oder P4-Familien aus den 2000er Jahren funktionieren kann.
Aleksey F.
P3 hat nicht einmal SSE2, daher verwendet der Compiler bereits x87 für doubleund sollte sich daher frndintselbst für ausgeben können rint(). Wenn Ihr Compiler SSE2 verwendet, doublelohnt es sich möglicherweise nicht , ein von einem XMM-Register auf x87 und zurück zu übertragen.
Peter Cordes
0

Der beste Weg, um einen Gleitkommawert durch "n" Dezimalstellen abzurunden, ist wie folgt mit in O (1) Zeit: -

Wir müssen den Wert um 3 Stellen abrunden, dh n = 3.So,

float a=47.8732355;
printf("%.3f",a);
Parveen Kumar
quelle
-4
// Convert the float to a string
// We might use stringstream, but it looks like it truncates the float to only
//5 decimal points (maybe that's what you want anyway =P)

float MyFloat = 5.11133333311111333;
float NewConvertedFloat = 0.0;
string FirstString = " ";
string SecondString = " ";
stringstream ss (stringstream::in | stringstream::out);
ss << MyFloat;
FirstString = ss.str();

// Take out how ever many decimal places you want
// (this is a string it includes the point)
SecondString = FirstString.substr(0,5);
//whatever precision decimal place you want

// Convert it back to a float
stringstream(SecondString) >> NewConvertedFloat;
cout << NewConvertedFloat;
system("pause");

Es könnte eine ineffiziente schmutzige Art der Konvertierung sein, aber zum Teufel, es funktioniert lol. Und es ist gut, weil es für den tatsächlichen Schwimmer gilt. Nicht nur die Ausgabe visuell beeinflussen.

Brad
quelle
Dies ist unglaublich ineffizient und wird auch abgeschnitten (indem immer nachfolgende Ziffern verworfen werden), anstatt auf die nächste zu runden.
Peter Cordes
-6

Ich war das:

#include <cmath.h>

using namespace std;

double roundh(double number, int place){

    /* place = decimal point. Putting in 0 will make it round to whole
                              number. putting in 1 will round to the
                              tenths digit.
    */

    number *= 10^place;
    int istack = (int)floor(number);
    int out = number-istack;
    if (out < 0.5){
        floor(number);
        number /= 10^place;
        return number;
    }
    if (out > 0.4) {
        ceil(number);
        number /= 10^place;
        return number;
    }
}
Peter Mortensen
quelle
3
Meinten Sie nicht pow (10, place) und nicht den binären Operator ^ in 10 ^ place? 10 ^ 2 auf meiner Maschine gibt mir 8 !! Trotzdem funktioniert der Code auf meinem Mac 10.7.4 und gcc nicht und gibt den ursprünglichen Wert zurück.
Pete855217