19

Consider the following code for adding all the elements of a vector:

#include<iostream>
#include<algorithm>
#include<numeric>
#include<vector>
using namespace std;
int main(void)
{

   std::vector<double> V;
   V.push_back(1.2);
   V.push_back(3.6);
   V.push_back(5.6);
   double sum = accumulate(V.begin(),V.end(),0);

   cout << "The sum of the elements of the vector V is " << sum << endl;
   return 0;
}

When I compile this on Cygwin on Windows and run it, I get the output at the terminal as

The sum of the elements of the vector V is 9

The accumulate function seems to be rounding down all the numbers and adding them up, which would explain the answer.

Is this something wrong with the Cygwin g++ compiler, or my misinterpretation of the accumulate function for adding up a vector of doubles?

Jamal
  • 763
  • 7
  • 22
  • 32
smilingbuddha
  • 14,334
  • 33
  • 112
  • 189

4 Answers4

37

std::accumulate is declared as such:

template <typename InputIt, typename T>
T accumulate(InputIt first, InputIt last, T init);

The second template argument to std::accumulate is deduced as int because 0 is of type int. Pass a double instead, like 0.0.

  • 4
    Oh dear! This seems like a misfeature to me. Is it not more logical to deduce type from looking at the nature of the elements of the vector? Is there any use to this way of defining accumulate? – smilingbuddha Dec 23 '13 at 01:29
  • 1
    @smilingbuddha It is possible to write a wrapper that chooses the type of `init` using `typename std::iterator_traits::value_type` instead of a template parameter. I believe that this isn’t done because it would be less flexible, though I’m not sure. –  Dec 23 '13 at 01:30
  • @rightfold Why not just convert the value by the container's `value_type`? –  Dec 23 '13 at 01:31
  • 1
    @remyabel `std::accumulate` works with iterators, not containers. –  Dec 23 '13 at 01:31
  • @smilingbuddha See my answer. I posted an example. –  Dec 23 '13 at 02:19
  • @rightfold: With current C++, that could be dealt with via `std::iterator_traits`, but `std::accumulate` predates that. – Jerry Coffin Dec 23 '13 at 03:43
  • @JerryCoffin see my first comment. Good point about the chronology, though. –  Dec 23 '13 at 03:43
  • This is exactly why I've started a `"numeric2.h"` header in my project. There really should be a 2-argument overload using `std::iterator_traits::value_type()` as initial value. If the type is "numerical enough", `value_type` will be a sensible zero value. – MSalters Dec 23 '13 at 08:17
  • After banging my head against this problem for a while I found your answer. Thanks. Strange that even with specifying `std::plus()` it still takes the template from the init value. – Victor Eijkhout Nov 13 '21 at 19:13
6

Change 0 to 0.0. It then gets 10.4 for me. While the container is double, the type deduction is int because of the initial argument passed to std::accumulate. Therefore, the container is assigned int values.

  • You may use `decltype(*first)` for `vt`. – Jarod42 Dec 23 '13 at 02:36
  • 1
    Note that with your `accumulate`, you may have opposite problem : `accumulate` where result stands in `int` but not in `uint8_t`. And using `accumulate` to offset an pointer is no more possible (`accumulate`) with your version (or any other usage where `T` and `decltype(*first)` are unrelated). – Jarod42 Dec 23 '13 at 03:00
  • 1
    @Jarod42: Yes--probably want to do something like `decltype(std::declval(vt) + std::declval(T))` to get the result as whatever type the addition would/will give. This will let the user force promotion to a wider type, but not (probably accidentally) truncate to a narrower type. – Jerry Coffin Dec 23 '13 at 03:49
5

The value being returned from the std::accumulate function is an integer, not a double because of this:

double sum = accumulate(V.begin(), V.end(), 0);
//                                          ^-- Integer!

The template parameter inference done by the C++ compiler makes the return type of the accumulate function the same as the init parameter, which in your case is an integer. Therefore the return value is being rounded to the nearest whole number. The return value is then being implicitly cast back into a double when it's assigned into sum.

To fix this, you can simply pass the zero as a double (i.e. 0.0) instead.

Karl Nicoll
  • 16,090
  • 3
  • 51
  • 65
3

The last argument of the std::accumulate call determines the type of the internal sum as well as the return type. Since 0 is an int everything gets rounded down during addition, and the result is an integer as well.

orlp
  • 112,504
  • 36
  • 218
  • 315