// Copyright 2015-2018 Hans Dembinski // // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt // or copy at http://www.boost.org/LICENSE_1_0.txt) #ifndef BOOST_HISTOGRAM_AXIS_INTEGER_HPP #define BOOST_HISTOGRAM_AXIS_INTEGER_HPP #include #include #include #include #include #include #include #include #include #include #include namespace boost { namespace histogram { namespace axis { /** Axis for an interval of integer values with unit steps. Binning is a O(1) operation. This axis bins faster than a regular axis. @tparam Value input value type. Must be integer or floating point. @tparam MetaData type to store meta data. @tparam Options see boost::histogram::axis::option (all values allowed). */ template class integer : public iterator_mixin> { static_assert(std::is_integral::value || std::is_floating_point::value, "integer axis requires type floating point or integral type"); using value_type = Value; using local_index_type = std::conditional_t::value, index_type, real_index_type>; using metadata_type = detail::replace_default; using options_type = detail::replace_default; static_assert(!options_type::test(option::circular) || std::is_floating_point::value || !options_type::test(option::overflow), "integer axis with integral type cannot have overflow"); public: constexpr integer() = default; /** Construct over semi-open integer interval [start, stop). * * \param start first integer of covered range. * \param stop one past last integer of covered range. * \param meta description of the axis. */ integer(value_type start, value_type stop, metadata_type meta = {}) : size_meta_(static_cast(stop - start), std::move(meta)), min_(start) { if (stop <= start) BOOST_THROW_EXCEPTION(std::invalid_argument("bins > 0 required")); } /// Constructor used by algorithm::reduce to shrink and rebin. integer(const integer& src, index_type begin, index_type end, unsigned merge) : integer(src.value(begin), src.value(end), src.metadata()) { if (merge > 1) BOOST_THROW_EXCEPTION(std::invalid_argument("cannot merge bins for integer axis")); if (options_type::test(option::circular) && !(begin == 0 && end == src.size())) BOOST_THROW_EXCEPTION(std::invalid_argument("cannot shrink circular axis")); } /// Return index for value argument. index_type index(value_type x) const noexcept { return index_impl(std::is_floating_point(), x); } /// Returns index and shift (if axis has grown) for the passed argument. auto update(value_type x) noexcept { auto impl = [this](long x) { const auto i = x - min_; if (i >= 0) { const auto k = static_cast(i); if (k < size()) return std::make_pair(k, 0); const auto n = k - size() + 1; size_meta_.first() += n; return std::make_pair(k, -n); } const auto k = static_cast( detail::static_if>( [](auto x) { return std::floor(x); }, [](auto x) { return x; }, i)); min_ += k; size_meta_.first() -= k; return std::make_pair(0, -k); }; return detail::static_if>( [this, impl](auto x) { if (std::isfinite(x)) return impl(static_cast(std::floor(x))); // this->size() is workaround for gcc-5 bug return std::make_pair(x < 0 ? -1 : this->size(), 0); }, impl, x); } /// Return value for index argument. value_type value(local_index_type i) const noexcept { if (!options_type::test(option::circular)) { if (i < 0) return detail::lowest(); if (i > size()) { return detail::highest(); } } return min_ + i; } /// Return bin for index argument. decltype(auto) bin(index_type idx) const noexcept { return detail::static_if>( [this](auto idx) { return interval_view(*this, idx); }, [this](auto idx) { return this->value(idx); }, idx); } /// Returns the number of bins, without over- or underflow. index_type size() const noexcept { return size_meta_.first(); } /// Returns the options. static constexpr unsigned options() noexcept { return options_type::value; } /// Returns reference to metadata. metadata_type& metadata() noexcept { return size_meta_.second(); } /// Returns reference to const metadata. const metadata_type& metadata() const noexcept { return size_meta_.second(); } template bool operator==(const integer& o) const noexcept { return size() == o.size() && detail::relaxed_equal(metadata(), o.metadata()) && min_ == o.min_; } template bool operator!=(const integer& o) const noexcept { return !operator==(o); } template void serialize(Archive&, unsigned); private: index_type index_impl(std::false_type, int x) const noexcept { const auto z = x - min_; if (options_type::test(option::circular)) return static_cast(z - std::floor(float(z) / size()) * size()); if (z < size()) return z >= 0 ? z : -1; return size(); } template index_type index_impl(std::true_type, T x) const noexcept { // need to handle NaN, cannot simply cast to int and call int-implementation const auto z = x - min_; if (options_type::test(option::circular)) { if (std::isfinite(z)) return static_cast(std::floor(z) - std::floor(z / size()) * size()); } else if (z < size()) { return z >= 0 ? static_cast(z) : -1; } return size(); } detail::compressed_pair size_meta_{0}; value_type min_{0}; template friend class integer; }; #if __cpp_deduction_guides >= 201606 template integer(T, T)->integer>; template integer(T, T, const char*)->integer>; template integer(T, T, M)->integer, M>; #endif } // namespace axis } // namespace histogram } // namespace boost #endif