sum.hpp 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. // Copyright 2018 Hans Dembinski
  2. //
  3. // Distributed under the Boost Software License, version 1.0.
  4. // (See accompanying file LICENSE_1_0.txt
  5. // or copy at http://www.boost.org/LICENSE_1_0.txt)
  6. #ifndef BOOST_HISTOGRAM_ACCUMULATORS_SUM_HPP
  7. #define BOOST_HISTOGRAM_ACCUMULATORS_SUM_HPP
  8. #include <boost/core/nvp.hpp>
  9. #include <boost/histogram/fwd.hpp> // for sum<>
  10. #include <cmath> // std::abs
  11. #include <type_traits> // std::is_floating_point, std::common_type
  12. namespace boost {
  13. namespace histogram {
  14. namespace accumulators {
  15. /**
  16. Uses Neumaier algorithm to compute accurate sums of floats.
  17. The algorithm is an improved Kahan algorithm
  18. (https://en.wikipedia.org/wiki/Kahan_summation_algorithm). The algorithm uses memory for
  19. two numbers and is three to five times slower compared to using a single number to
  20. accumulate a sum, but the relative error of the sum is at the level of the machine
  21. precision, independent of the number of samples.
  22. A. Neumaier, Zeitschrift fuer Angewandte Mathematik und Mechanik 54 (1974) 39-51.
  23. */
  24. template <class ValueType>
  25. class sum {
  26. static_assert(std::is_floating_point<ValueType>::value,
  27. "ValueType must be a floating point type");
  28. public:
  29. using value_type = ValueType;
  30. using const_reference = const value_type&;
  31. sum() = default;
  32. /// Initialize sum to value and allow implicit conversion
  33. sum(const_reference value) noexcept : sum(value, 0) {}
  34. /// Allow implicit conversion from sum<T>
  35. template <class T>
  36. sum(const sum<T>& s) noexcept : sum(s.large_part(), s.small_part()) {}
  37. /// Initialize sum explicitly with large and small parts
  38. sum(const_reference large_part, const_reference small_part) noexcept
  39. : large_(large_part), small_(small_part) {}
  40. /// Increment sum by one
  41. sum& operator++() noexcept { return operator+=(1); }
  42. /// Increment sum by value
  43. sum& operator+=(const_reference value) noexcept {
  44. // prevent compiler optimization from destroying the algorithm
  45. // when -ffast-math is enabled
  46. volatile value_type l;
  47. value_type s;
  48. if (std::abs(large_) >= std::abs(value)) {
  49. l = large_;
  50. s = value;
  51. } else {
  52. l = value;
  53. s = large_;
  54. }
  55. large_ += value;
  56. l = l - large_;
  57. l = l + s;
  58. small_ += l;
  59. return *this;
  60. }
  61. /// Add another sum
  62. sum& operator+=(const sum& s) noexcept {
  63. operator+=(s.large_);
  64. small_ += s.small_;
  65. return *this;
  66. }
  67. /// Scale by value
  68. sum& operator*=(const_reference value) noexcept {
  69. large_ *= value;
  70. small_ *= value;
  71. return *this;
  72. }
  73. bool operator==(const sum& rhs) const noexcept {
  74. return large_ + small_ == rhs.large_ + rhs.small_;
  75. }
  76. bool operator!=(const sum& rhs) const noexcept { return !operator==(rhs); }
  77. /// Return value of the sum.
  78. value_type value() const noexcept { return large_ + small_; }
  79. /// Return large part of the sum.
  80. const_reference large_part() const noexcept { return large_; }
  81. /// Return small part of the sum.
  82. const_reference small_part() const noexcept { return small_; }
  83. // note: windows.h illegially uses `#define small char`, cannot use method "small"
  84. // lossy conversion to value type must be explicit
  85. explicit operator value_type() const noexcept { return value(); }
  86. template <class Archive>
  87. void serialize(Archive& ar, unsigned /* version */) {
  88. ar& make_nvp("large", large_);
  89. ar& make_nvp("small", small_);
  90. }
  91. // begin: extra operators to make sum behave like a regular number
  92. sum& operator*=(const sum& rhs) noexcept {
  93. const auto scale = static_cast<value_type>(rhs);
  94. large_ *= scale;
  95. small_ *= scale;
  96. return *this;
  97. }
  98. sum operator*(const sum& rhs) const noexcept {
  99. sum s = *this;
  100. s *= rhs;
  101. return s;
  102. }
  103. sum& operator/=(const sum& rhs) noexcept {
  104. const auto scale = 1.0 / static_cast<value_type>(rhs);
  105. large_ *= scale;
  106. small_ *= scale;
  107. return *this;
  108. }
  109. sum operator/(const sum& rhs) const noexcept {
  110. sum s = *this;
  111. s /= rhs;
  112. return s;
  113. }
  114. bool operator<(const sum& rhs) const noexcept {
  115. return operator value_type() < rhs.operator value_type();
  116. }
  117. bool operator>(const sum& rhs) const noexcept {
  118. return operator value_type() > rhs.operator value_type();
  119. }
  120. bool operator<=(const sum& rhs) const noexcept {
  121. return operator value_type() <= rhs.operator value_type();
  122. }
  123. bool operator>=(const sum& rhs) const noexcept {
  124. return operator value_type() >= rhs.operator value_type();
  125. }
  126. // end: extra operators
  127. private:
  128. value_type large_{};
  129. value_type small_{};
  130. };
  131. } // namespace accumulators
  132. } // namespace histogram
  133. } // namespace boost
  134. #ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
  135. namespace std {
  136. template <class T, class U>
  137. struct common_type<boost::histogram::accumulators::sum<T>,
  138. boost::histogram::accumulators::sum<U>> {
  139. using type = boost::histogram::accumulators::sum<common_type_t<T, U>>;
  140. };
  141. } // namespace std
  142. #endif
  143. #endif