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 eine double -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 eine double -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 eine double -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 😃


  • Mod

    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?


  • Mod

    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 aber int addieren kann. I.e. += i ist möglich. Dann wird das von der STL vorgegebene accumulate problemlos funktionieren, deines aber nicht.



  • Arcoth schrieb:

    Sagen wir mal, du hast einen Typen, der nicht zu int konvertierbar ist. Auf den ich aber int addieren kann. I.e. += i ist möglich. Dann wird das von der STL vorgegebene accumulate 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 aber int addieren kann. I.e. += i ist möglich. Dann wird das von der STL vorgegebene accumulate 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 einen int addieren. Allerdings ist T ja auf der rechten Seite, also müsste man wenn dann den operator+ mit int als linksseitigem Argument und T als rechtsseitigem Argument überladen. Außerdem muss dieser operator+ dann etwas zu int konvertierbares zurückgeben, da init (vom Typ int ) gleich diesem gesetzt wird. Daher kann man schonmal nicht ein int auf ein T addieren, sondern höchstens ein T auf ein int addieren. Und das Ergebnis muss dann wieder ein int (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.


  • Mod

    @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 👍


Log in to reply