std::accumulate: Merkwürdiger Fehler bei vector<vector>
-
Hallo,
ich habe in Summe sicher 4 Stunden investiert, um eine Abweichung zwischen matlab und C++ Ergebnissen zu finden.
Folgendes:
std::vector<std::vector<double>> data_i; // hat z.B. 3 Vektoren mit 200 Elementen // Ich möchte die Summe fuer jeden Vector bilden (Die 3 ist jetzt mal hardcodiert) std::vector<double> vec_sum_data_i; for (std::size_t j = 0; j < 3; ++j) { vec_sum_data_i.push_back(std::accumulate(data_i[j].begin(), data_i[j].end(), 0)); }
Ich weiß nicht wieso, aber ich erhalte stellenweise als Summe eine 0, obwohl das definitiv falsch ist. Beispiel bei den 3 Vektoren: Die ersten beiden sollen eine Summe von 0 haben, der letzte Vektor eine Summe von 88 (die letzte stimmt).
Rechne ich die Summe selber aus, dann stimmen alle:for (std::size_t j = 0; j < 3; ++j) { double summe = 0.0; for (auto& k : data_i[j]) { summe += k; } // [...] }
Ich kann mir das nicht erklären Auffällig ist, dass die Summe 0 umso häufiger aufzutreten scheint, je mehr Vektoren vorhanden sind Was mag das sein?
-
Probier mal
vec_sum_data_i.push_back(std::accumulate(data_i[j].begin(), data_i[j].end(), 0.0 /* <- mit Dezimalpunkt */));
-
Erklärung:
accumulate()
ist ein Template. D.h. irgendwo muss es den Typ für Summe/Rückgabewert herbekommen, und den holt es sich, wenn nicht anders angegeben, vom dritten Argument des Funktionsaufrufs:
http://www.cplusplus.com/reference/numeric/accumulate/Und "0" wird zunächst mal als
int
interpretiert, daher wird der ganze Aufruf mit int berechnet (und alle Zahlen aus dem Vector verlieren ihren Nachkommateil), ergo ein int-Ergebnis.Du kannst auch schreiben
... std::accumulate<double>(data_i[j].begin() ...
dann wird
double
erzwungen und man übersieht es ggf. nicht so leicht - zusätzlich zum "0.0", was die korrekte Form für einedouble
-Null ist.
-
minastaros schrieb:
Du kannst auch schreiben
... std::accumulate<double>(data_i[j].begin() ...
dann wird
double
erzwungen und man übersieht es ggf. nicht so leicht - zusätzlich zum "0.0", was die korrekte Form für einedouble
-Null ist.Don't help the compiler! Lasse den Compiler alle Typen deduzieren, es gibt Fälle wo ein Update der Funktion dann was Falsches macht.
Besser:vec_sum_data_i.push_back(std::accumulate(data_i[j].begin(), data_i[j].end(), double(0)));
Das machts auch deutlicher.
-
minastaros schrieb:
Du kannst auch schreiben
... std::accumulate<double>(data_i[j].begin() ...
dann wird
double
erzwungen und man übersieht es ggf. nicht so leicht - zusätzlich zum "0.0", was die korrekte Form für einedouble
-Null ist.Kann man leider nicht schreiben weil der Typ für die Summe erst der zweite Template-Parameter ist. Wenn dann so:
std::accumulate<std::vector<double>::iterator, double>(data_i[j].begin(), ...
Aber sowas will eigentlich niemand schreiben.
-
Wenn man will kann man sich auch einen Wrapper schreiben ders richtig macht:
#include <iostream> #include <numeric> #include <vector> template <typename InputIt> auto sum(InputIt first, InputIt last, typename std::remove_reference<decltype(*first)>::type init) -> decltype(init) { return std::accumulate(first, last, init); } int main() { std::vector<double> v = { 0.5, 1, 2 }; std::cout << sum(v.begin(), v.end(), 0 /*Jetzt auch ohne Dezimal-Punkt!*/) << '\n'; }
Eigentlich nervig dass sowas nicht der Standard ist... warum eigentlich nicht? Hab als ich den Fehler das erste mal hatte auch ewig gebraucht bis ich das gemerkt habe
-
Weil
accumulate
allgemein genug sein soll, auch auf Typen addieren zu können, die mit dem Containerelement nichts am Hut haben.PS: Du suchst womöglich
iterator_traits<>::value_type
.
-
Arcoth schrieb:
Weil
accumulate
allgemein genug sein soll, auch auf Typen addieren zu können, die mit dem Containerelement nichts am Hut haben.Sorry, aber das versteh ich nicht?
-
happystudent schrieb:
Arcoth schrieb:
Weil
accumulate
allgemein genug sein soll, auch auf Typen addieren zu können, die mit dem Containerelement nichts am Hut haben.Sorry, aber das versteh ich nicht?
Sagen wir mal, du hast einen Typen, der nicht zu
int
konvertierbar ist. Auf den ich aberint
addieren kann. I.e.+= i
ist möglich. Dann wird das von der STL vorgegebeneaccumulate
problemlos funktionieren, deines aber nicht.
-
Arcoth schrieb:
Sagen wir mal, du hast einen Typen, der nicht zu
int
konvertierbar ist. Auf den ich aberint
addieren kann. I.e.+= i
ist möglich. Dann wird das von der STL vorgegebeneaccumulate
problemlos funktionieren, deines aber nicht.Ok, da hast du recht, das hatte ich nicht bedacht... naja, vielleicht geht das ja dann mit concepts-Überladungen sauber (da kenn ich mich aber noch nicht wirklich aus). Weil ungünstig finde ich das trotzdem^^
-
Nur als Nachtrag: Mit dem double (0.0) war das Problem dann auch gelöst, danke
-
Arcoth schrieb:
Sagen wir mal, du hast einen Typen, der nicht zu
int
konvertierbar ist. Auf den ich aberint
addieren kann. I.e.+= i
ist möglich. Dann wird das von der STL vorgegebeneaccumulate
problemlos funktionieren, deines aber nicht.Ich hab nochmal ein bisschen darüber nachgedacht und so ganz passt mir diese Erklärung nicht (vielleicht verstehe ich auch was falsch).
Laut Doku sieht der Code von
std::accumulate
etwa so aus (entspricht auch dem meines Compilers VS2013):template<class InputIt, class T> T accumulate(InputIt first, InputIt last, T init) { for (; first != last; ++first) { init = init + *first; } return init; }
Den Wertetyp des Input-Iterators nenne ich mal
T
.Du meinst also man kann auf ein
T
einenint
addieren. Allerdings istT
ja auf der rechten Seite, also müsste man wenn dann denoperator+
mitint
als linksseitigem Argument undT
als rechtsseitigem Argument überladen. Außerdem muss dieseroperator+
dann etwas zuint
konvertierbares zurückgeben, dainit
(vom Typint
) gleich diesem gesetzt wird. Daher kann man schonmal nicht einint
auf einT
addieren, sondern höchstens einT
auf einint
addieren. Und das Ergebnis muss dann wieder einint
(bzw. in ein solches konvertierbar) sein.Das heißt, die einzige Möglichkeit die mir einfällt dein Szenario umzusetzen wäre so:
struct foo { int val = 0; }; // Nicht zu int konvertierbar ... int operator+(int i, foo const &f) { return i + f.val; } // ... aber auf int addierbar
Das scheint aber keinen Sinn zu machen. Denn dann gilt folgendes:
foo f; int i = f; // Geht nicht int j = 0 + f; // Geht
was offensichtlich semantisch gesehen Mist ist.
-
@happystudent Ne, ich meinte folgendes:
struct A { A operator+(int); }; int arr[] {1, 2, 3}; std::accumulate(arr, arr+3, A());
-
Das hier wäre sogar ein halbwegs sinnvoller Anwendungsfall:
vector<char> foo = {'T', 'e', 's', 't'}; auto str = accumulate(foo.begin(), foo.end(), string());
-
Stimmt, ihr habt recht. Daran hatte ich nicht gedacht