blob: 8de5833141733b290ddfa2ba27ee80f4d6703d29 [file] [log] [blame]
/** @file
Scaled integral values.
In many situations it is desirable to define scaling factors or base units (a "metric"). This template
enables this to be done in a type and scaling safe manner where the defined factors carry their scaling
information as part of the type.
@section license License
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#pragma once
#include <cstdint>
#include <ratio>
#include <ostream>
#include <type_traits>
#include "tscore/BufferWriter.h"
namespace tag
{
struct generic;
}
namespace ts
{
template <intmax_t N, typename C, typename T> class Scalar;
namespace detail
{
// @internal - although these conversion methods look bulky, in practice they compile down to
// very small amounts of code due to the conditions being compile time constant - the non-taken
// clauses are dead code and eliminated by the compiler.
// The general case where neither N nor S are a multiple of the other seems a bit long but this
// minimizes the risk of integer overflow. I need to validate that under -O2 the compiler will
// only do 1 division to get both the quotient and remainder for (n/N) and (n%N). In cases where
// N,S are powers of 2 I have verified recent GNU compilers will optimize to bit operations.
/// Convert a count @a c that is scale @s S to the corresponding count for scale @c N
template <intmax_t N, intmax_t S, typename C>
C
scale_conversion_round_up(C c)
{
typedef std::ratio<N, S> R;
if (N == S) {
return c;
} else if (R::den == 1) {
return c / R::num + (0 != c % R::num); // N is a multiple of S.
} else if (R::num == 1) {
return c * R::den; // S is a multiple of N.
} else {
return (c / R::num) * R::den + ((c % R::num) * R::den) / R::num + (0 != (c % R::num));
}
}
/// Convert a count @a c that is scale @s S to scale @c N
template <intmax_t N, intmax_t S, typename C>
C
scale_conversion_round_down(C c)
{
typedef std::ratio<N, S> R;
if (N == S) {
return c;
} else if (R::den == 1) {
return c / R::num; // N = k S
} else if (R::num == 1) {
return c * R::den; // S = k N
} else {
return (c / R::num) * R::den + ((c % R::num) * R::den) / R::num;
}
}
/* Helper classes for @c Scalar
These wrap values to capture extra information for @c Scalar methods. This includes whether to
round up or down when converting and, when the wrapped data is also a @c Scalar, the scale.
These are not intended for direct use but by the @c round_up and @c round_down free functions
which capture the information about the argument and construct an instance of one of these
classes to pass it on to a @c Scalar method.
Scale conversions between @c Scalar instances are handled in these classes via the templated
methods @c scale_conversion_round_up and @c scale_conversion_round_down.
Conversions between scales and types for the scalar helpers is done inside the helper classes
and a user type conversion operator exists so the helper can be converted by the compiler to
the correct type. For the units bases conversion this is done in @c Scalar because the
generality of the needed conversion is too broad to be easily used. It can be done but there is
some ugliness due to the fact that in some cases two user conversions which is difficult to
deal with. I have tried it both ways and overall this seems a cleaner implementation.
Much of this is driven by the fact that the assignment operator, in some cases, can not be
templated and therefore to have a nice interface for assignment this split is needed.
Note - the key point is the actual conversion is not done when the wrapper instance is created
but when the wrapper instance is assigned. That is what enables the conversion to be done in
the context of the destination, which is not otherwise possible.
*/
// Unit value, to be rounded up.
template <typename C> struct scalar_unit_round_up_t {
C _n;
template <intmax_t N, typename I>
constexpr I
scale() const
{
return static_cast<I>(_n / N + (0 != (_n % N)));
}
};
// Unit value, to be rounded down.
template <typename C> struct scalar_unit_round_down_t {
C _n;
// template <typename I> operator scalar_unit_round_down_t<I>() { return {static_cast<I>(_n)}; }
template <intmax_t N, typename I>
constexpr I
scale() const
{
return static_cast<I>(_n / N);
}
};
// Scalar value, to be rounded up.
template <intmax_t N, typename C, typename T> struct scalar_round_up_t {
C _n;
template <intmax_t S, typename I> constexpr operator Scalar<S, I, T>() const
{
return Scalar<S, I, T>(scale_conversion_round_up<S, N>(_n));
}
};
// Scalar value, to be rounded down.
template <intmax_t N, typename C, typename T> struct scalar_round_down_t {
C _n;
template <intmax_t S, typename I> constexpr operator Scalar<S, I, T>() const
{
return Scalar<S, I, T>(scale_conversion_round_down<S, N>(_n));
}
};
} // namespace detail
/// Mark a unit value to be scaled, rounding down.
template <typename C>
constexpr detail::scalar_unit_round_up_t<C>
round_up(C n)
{
return {n};
}
/// Mark a @c Scalar value to be scaled, rounding up.
template <intmax_t N, typename C, typename T>
constexpr detail::scalar_round_up_t<N, C, T>
round_up(Scalar<N, C, T> v)
{
return {v.count()};
}
/// Mark a unit value to be scaled, rounding down.
template <typename C>
constexpr detail::scalar_unit_round_down_t<C>
round_down(C n)
{
return {n};
}
/// Mark a @c Scalar value, to be rounded down.
template <intmax_t N, typename C, typename T>
constexpr detail::scalar_round_down_t<N, C, T>
round_down(Scalar<N, C, T> v)
{
return {v.count()};
}
/** A class to hold scaled values.
Instances of this class have a @a count and a @a scale. The "value" of the instance is @a
count * @a scale. The scale is stored in the compiler in the class symbol table and so only
the count is a run time value. An instance with a large scale can be assign to an instance
with a smaller scale and the conversion is done automatically. Conversions from a smaller to
larger scale must be explicit using @c round_up and @c round_down. This prevents
inadvertent changes in value. Because the scales are not the same these conversions can be
lossy and the two conversions determine whether, in such a case, the result should be rounded
up or down to the nearest scale value.
@a N sets the scale. @a C is the type used to hold the count, which is in units of @a N.
@a T is a "tag" type which is used only to distinguish the base metric for the scale. Scalar
types that have different tags are not interoperable although they can be converted manually by
converting to units and then explicitly constructing a new Scalar instance. This is by
design. This can be ignored - if not specified then it defaults to a "generic" tag. The type can
be (and usually is) defined in name only).
@note This is modeled somewhat on @c std::chrono and serves a similar function for different
and simpler cases (where the ratio is always an integer, never a fraction).
@see round_up
@see round_down
*/
template <intmax_t N, typename C = int, typename T = tag::generic> class Scalar
{
typedef Scalar self; ///< Self reference type.
public:
/// Scaling factor - make it external accessible.
constexpr static intmax_t SCALE = N;
typedef C Counter; ///< Type used to hold the count.
typedef T Tag; ///< Make tag accessible.
static_assert(N > 0, "The scaling factor (1st template argument) must be a positive integer");
static_assert(std::is_integral<C>::value, "The counter type (2nd template argument) must be an integral type");
constexpr Scalar(); ///< Default constructor.
///< Construct to have @a n scaled units.
explicit constexpr Scalar(Counter n);
/// Copy constructor.
constexpr Scalar(self const &that); /// Copy constructor.
/// Copy constructor for same scale.
template <typename I> constexpr Scalar(Scalar<N, I, T> const &that);
/// Direct conversion constructor.
/// @note Requires that @c S be an integer multiple of @c SCALE.
template <intmax_t S, typename I> constexpr Scalar(Scalar<S, I, T> const &that);
/// Conversion constructor.
constexpr Scalar(detail::scalar_round_up_t<N, C, T> const &that);
/// Conversion constructor.
constexpr Scalar(detail::scalar_round_down_t<N, C, T> const &that);
/// Conversion constructor.
template <typename I> constexpr Scalar(detail::scalar_unit_round_up_t<I> v);
/// Conversion constructor.
template <typename I> constexpr Scalar(detail::scalar_unit_round_down_t<I> v);
/// Assignment operator.
/// The value @a that is scaled appropriately.
/// @note Requires the scale of @a that be an integer multiple of the scale of @a this. If this isn't the case then
/// the @c round_up or @c round_down must be used to indicate the rounding direction.
template <intmax_t S, typename I> self &operator=(Scalar<S, I, T> const &that);
/// Assignment from same scale.
self &operator=(self const &that);
// Conversion assignments.
template <typename I> self &operator=(detail::scalar_unit_round_up_t<I> n);
template <typename I> self &operator=(detail::scalar_unit_round_down_t<I> n);
self &operator =(detail::scalar_round_up_t<N, C, T> v);
self &operator =(detail::scalar_round_down_t<N, C, T> v);
/// Direct assignment.
/// The count is set to @a n.
self &assign(Counter n);
/// The value @a that is scaled appropriately.
/// @note Requires the scale of @a that be an integer multiple of the scale of @a this. If this isn't the case then
/// the @c round_up or @c round_down must be used to indicate the rounding direction.
template <intmax_t S, typename I> self &assign(Scalar<S, I, T> const &that);
// Conversion assignments.
template <typename I> self &assign(detail::scalar_unit_round_up_t<I> n);
template <typename I> self &assign(detail::scalar_unit_round_down_t<I> n);
self &assign(detail::scalar_round_up_t<N, C, T> v);
self &assign(detail::scalar_round_down_t<N, C, T> v);
/// The number of scale units.
constexpr Counter count() const;
/// The scaled value.
constexpr intmax_t value() const;
/// User conversion to scaled value.
constexpr operator intmax_t() const;
/// Addition operator.
/// The value is scaled from @a that to @a this.
/// @note Requires the scale of @a that be an integer multiple of the scale of @a this. If this isn't the case then
/// the @c scale_up or @c scale_down casts must be used to indicate the rounding direction.
self &operator+=(self const &that);
template <intmax_t S, typename I> self &operator+=(Scalar<S, I, T> const &that);
template <typename I> self &operator+=(detail::scalar_unit_round_up_t<I> n);
template <typename I> self &operator+=(detail::scalar_unit_round_down_t<I> n);
self &operator+=(detail::scalar_round_up_t<N, C, T> v);
self &operator+=(detail::scalar_round_down_t<N, C, T> v);
/// Increment - increase count by 1.
self &operator++();
/// Increment - increase count by 1.
self operator++(int);
/// Decrement - decrease count by 1.
self &operator--();
/// Decrement - decrease count by 1.
self operator--(int);
/// Increment by @a n.
self &inc(Counter n);
/// Decrement by @a n.
self &dec(Counter n);
/// Subtraction operator.
/// The value is scaled from @a that to @a this.
/// @note Requires the scale of @a that be an integer multiple of the scale of @a this. If this isn't the case then
/// the @c scale_up or @c scale_down casts must be used to indicate the rounding direction.
self &operator-=(self const &that);
template <intmax_t S, typename I> self &operator-=(Scalar<S, I, T> const &that);
template <typename I> self &operator-=(detail::scalar_unit_round_up_t<I> n);
template <typename I> self &operator-=(detail::scalar_unit_round_down_t<I> n);
self &operator-=(detail::scalar_round_up_t<N, C, T> v);
self &operator-=(detail::scalar_round_down_t<N, C, T> v);
/// Multiplication - multiple the count by @a n.
self &operator*=(C n);
/// Division - divide (rounding down) the count by @a n.
self &operator/=(C n);
/// Utility overload of the function operator to create instances at the same scale.
self operator()(Counter n) const;
/// Return a value at the same scale with a count increased by @a n.
self plus(Counter n) const;
/// Return a value at the same scale with a count decreased by @a n.
self minus(Counter n) const;
/// Run time access to the scale (template arg @a N).
static constexpr intmax_t scale();
protected:
Counter _n; ///< Number of scale units.
};
template <intmax_t N, typename C, typename T> constexpr Scalar<N, C, T>::Scalar() : _n() {}
template <intmax_t N, typename C, typename T> constexpr Scalar<N, C, T>::Scalar(Counter n) : _n(n) {}
template <intmax_t N, typename C, typename T> constexpr Scalar<N, C, T>::Scalar(self const &that) : _n(that._n) {}
template <intmax_t N, typename C, typename T>
template <typename I>
constexpr Scalar<N, C, T>::Scalar(Scalar<N, I, T> const &that) : _n(static_cast<C>(that.count()))
{
}
template <intmax_t N, typename C, typename T>
template <intmax_t S, typename I>
constexpr Scalar<N, C, T>::Scalar(Scalar<S, I, T> const &that) : _n(std::ratio<S, N>::num * that.count())
{
static_assert(std::ratio<S, N>::den == 1,
"Construction not permitted - target scale is not an integral multiple of source scale.");
}
template <intmax_t N, typename C, typename T>
constexpr Scalar<N, C, T>::Scalar(detail::scalar_round_up_t<N, C, T> const &v) : _n(v._n)
{
}
template <intmax_t N, typename C, typename T>
constexpr Scalar<N, C, T>::Scalar(detail::scalar_round_down_t<N, C, T> const &v) : _n(v._n)
{
}
template <intmax_t N, typename C, typename T>
template <typename I>
constexpr Scalar<N, C, T>::Scalar(detail::scalar_unit_round_up_t<I> v) : _n(v.template scale<N, C>())
{
}
template <intmax_t N, typename C, typename T>
template <typename I>
constexpr Scalar<N, C, T>::Scalar(detail::scalar_unit_round_down_t<I> v) : _n(v.template scale<N, C>())
{
}
template <intmax_t N, typename C, typename T>
constexpr auto
Scalar<N, C, T>::count() const -> Counter
{
return _n;
}
template <intmax_t N, typename C, typename T>
constexpr intmax_t
Scalar<N, C, T>::value() const
{
return _n * SCALE;
}
template <intmax_t N, typename C, typename T> constexpr Scalar<N, C, T>::operator intmax_t() const
{
return _n * SCALE;
}
template <intmax_t N, typename C, typename T>
inline auto
Scalar<N, C, T>::assign(Counter n) -> self &
{
_n = n;
return *this;
}
template <intmax_t N, typename C, typename T>
inline auto
Scalar<N, C, T>::operator=(self const &that) -> self &
{
_n = that._n;
return *this;
}
template <intmax_t N, typename C, typename T>
inline auto
Scalar<N, C, T>::operator=(detail::scalar_round_up_t<N, C, T> v) -> self &
{
_n = v._n;
return *this;
}
template <intmax_t N, typename C, typename T>
inline auto
Scalar<N, C, T>::assign(detail::scalar_round_up_t<N, C, T> v) -> self &
{
_n = v._n;
return *this;
}
template <intmax_t N, typename C, typename T>
inline auto
Scalar<N, C, T>::operator=(detail::scalar_round_down_t<N, C, T> v) -> self &
{
_n = v._n;
return *this;
}
template <intmax_t N, typename C, typename T>
inline auto
Scalar<N, C, T>::assign(detail::scalar_round_down_t<N, C, T> v) -> self &
{
_n = v._n;
return *this;
}
template <intmax_t N, typename C, typename T>
template <typename I>
inline auto
Scalar<N, C, T>::operator=(detail::scalar_unit_round_up_t<I> v) -> self &
{
_n = v.template scale<N, C>();
return *this;
}
template <intmax_t N, typename C, typename T>
template <typename I>
inline auto
Scalar<N, C, T>::assign(detail::scalar_unit_round_up_t<I> v) -> self &
{
_n = v.template scale<N, C>();
return *this;
}
template <intmax_t N, typename C, typename T>
template <typename I>
inline auto
Scalar<N, C, T>::operator=(detail::scalar_unit_round_down_t<I> v) -> self &
{
_n = v.template scale<N, C>();
return *this;
}
template <intmax_t N, typename C, typename T>
template <typename I>
inline auto
Scalar<N, C, T>::assign(detail::scalar_unit_round_down_t<I> v) -> self &
{
_n = v.template scale<N, C>();
return *this;
}
template <intmax_t N, typename C, typename T>
template <intmax_t S, typename I>
auto
Scalar<N, C, T>::operator=(Scalar<S, I, T> const &that) -> self &
{
typedef std::ratio<S, N> R;
static_assert(R::den == 1, "Assignment not permitted - target scale is not an integral multiple of source scale.");
_n = that.count() * R::num;
return *this;
}
template <intmax_t N, typename C, typename T>
template <intmax_t S, typename I>
auto
Scalar<N, C, T>::assign(Scalar<S, I, T> const &that) -> self &
{
typedef std::ratio<S, N> R;
static_assert(R::den == 1, "Assignment not permitted - target scale is not an integral multiple of source scale.");
_n = that.count() * R::num;
return *this;
}
template <intmax_t N, typename C, typename T>
constexpr inline intmax_t
Scalar<N, C, T>::scale()
{
return SCALE;
}
// --- Compare operators
// These optimize nicely because if R::num or R::den is 1 the compiler will drop it.
template <intmax_t N, typename C1, intmax_t S, typename I, typename T>
bool
operator<(Scalar<N, C1, T> const &lhs, Scalar<S, I, T> const &rhs)
{
typedef std::ratio<N, S> R;
return lhs.count() * R::num < rhs.count() * R::den;
}
template <intmax_t N, typename C1, intmax_t S, typename I, typename T>
bool
operator==(Scalar<N, C1, T> const &lhs, Scalar<S, I, T> const &rhs)
{
typedef std::ratio<N, S> R;
return lhs.count() * R::num == rhs.count() * R::den;
}
template <intmax_t N, typename C1, intmax_t S, typename I, typename T>
bool
operator<=(Scalar<N, C1, T> const &lhs, Scalar<S, I, T> const &rhs)
{
typedef std::ratio<N, S> R;
return lhs.count() * R::num <= rhs.count() * R::den;
}
// Derived compares.
template <intmax_t N, typename C, intmax_t S, typename I, typename T>
bool
operator>(Scalar<N, C, T> const &lhs, Scalar<S, I, T> const &rhs)
{
return rhs < lhs;
}
template <intmax_t N, typename C, intmax_t S, typename I, typename T>
bool
operator>=(Scalar<N, C, T> const &lhs, Scalar<S, I, T> const &rhs)
{
return rhs <= lhs;
}
// Arithmetic operators
template <intmax_t N, typename C, typename T>
auto
Scalar<N, C, T>::operator+=(self const &that) -> self &
{
_n += that._n;
return *this;
}
template <intmax_t N, typename C, typename T>
template <intmax_t S, typename I>
auto
Scalar<N, C, T>::operator+=(Scalar<S, I, T> const &that) -> self &
{
typedef std::ratio<S, N> R;
static_assert(R::den == 1, "Addition not permitted - target scale is not an integral multiple of source scale.");
_n += that.count() * R::num;
return *this;
}
template <intmax_t N, typename C, typename T>
template <typename I>
auto
Scalar<N, C, T>::operator+=(detail::scalar_unit_round_up_t<I> v) -> self &
{
_n += v.template scale<N, C>();
return *this;
}
template <intmax_t N, typename C, typename T>
template <typename I>
auto
Scalar<N, C, T>::operator+=(detail::scalar_unit_round_down_t<I> v) -> self &
{
_n += v.template scale<N, C>();
return *this;
}
template <intmax_t N, typename C, typename T>
auto
Scalar<N, C, T>::operator+=(detail::scalar_round_up_t<N, C, T> v) -> self &
{
_n += v._n;
return *this;
}
template <intmax_t N, typename C, typename T>
auto
Scalar<N, C, T>::operator+=(detail::scalar_round_down_t<N, C, T> v) -> self &
{
_n += v._n;
return *this;
}
template <intmax_t N, typename C, intmax_t S, typename I, typename T>
auto
operator+(Scalar<N, C, T> lhs, Scalar<S, I, T> const &rhs) -> typename std::common_type<Scalar<N, C, T>, Scalar<S, I, T>>::type
{
return typename std::common_type<Scalar<N, C, T>, Scalar<S, I, T>>::type(lhs) += rhs;
}
template <intmax_t N, typename C, typename T>
Scalar<N, C, T>
operator+(Scalar<N, C, T> const &lhs, Scalar<N, C, T> const &rhs)
{
return Scalar<N, C, T>(lhs) += rhs;
}
template <intmax_t N, typename C, typename T, typename I>
Scalar<N, C, T>
operator+(detail::scalar_unit_round_up_t<I> lhs, Scalar<N, C, T> const &rhs)
{
return Scalar<N, C, T>(rhs) += lhs;
}
template <intmax_t N, typename C, typename T, typename I>
Scalar<N, C, T>
operator+(Scalar<N, C, T> const &lhs, detail::scalar_unit_round_up_t<I> rhs)
{
return Scalar<N, C, T>(lhs) += rhs;
}
template <intmax_t N, typename C, typename T, typename I>
Scalar<N, C, T>
operator+(detail::scalar_unit_round_down_t<I> lhs, Scalar<N, C, T> const &rhs)
{
return Scalar<N, C, T>(rhs) += lhs;
}
template <intmax_t N, typename C, typename T, typename I>
Scalar<N, C, T>
operator+(Scalar<N, C, T> const &lhs, detail::scalar_unit_round_down_t<I> rhs)
{
return Scalar<N, C, T>(lhs) += rhs;
}
template <intmax_t N, typename C, typename T>
Scalar<N, C, T>
operator+(detail::scalar_round_up_t<N, C, T> lhs, Scalar<N, C, T> const &rhs)
{
return Scalar<N, C, T>(rhs) += lhs._n;
}
template <intmax_t N, typename C, typename T>
Scalar<N, C, T>
operator+(Scalar<N, C, T> const &lhs, detail::scalar_round_up_t<N, C, T> rhs)
{
return Scalar<N, C, T>(lhs) += rhs._n;
}
template <intmax_t N, typename C, typename T>
Scalar<N, C, T>
operator+(detail::scalar_round_down_t<N, C, T> lhs, Scalar<N, C, T> const &rhs)
{
return Scalar<N, C, T>(rhs) += lhs._n;
}
template <intmax_t N, typename C, typename T>
Scalar<N, C, T>
operator+(Scalar<N, C, T> const &lhs, detail::scalar_round_down_t<N, C, T> rhs)
{
return Scalar<N, C, T>(lhs) += rhs._n;
}
template <intmax_t N, typename C, typename T>
auto
Scalar<N, C, T>::operator-=(self const &that) -> self &
{
_n -= that._n;
return *this;
}
template <intmax_t N, typename C, typename T>
template <intmax_t S, typename I>
auto
Scalar<N, C, T>::operator-=(Scalar<S, I, T> const &that) -> self &
{
typedef std::ratio<S, N> R;
static_assert(R::den == 1, "Subtraction not permitted - target scale is not an integral multiple of source scale.");
_n -= that.count() * R::num;
return *this;
}
template <intmax_t N, typename C, typename T>
template <typename I>
auto
Scalar<N, C, T>::operator-=(detail::scalar_unit_round_up_t<I> v) -> self &
{
_n -= v.template scale<N, C>();
return *this;
}
template <intmax_t N, typename C, typename T>
template <typename I>
auto
Scalar<N, C, T>::operator-=(detail::scalar_unit_round_down_t<I> v) -> self &
{
_n -= v.template scale<N, C>();
return *this;
}
template <intmax_t N, typename C, typename T>
auto
Scalar<N, C, T>::operator-=(detail::scalar_round_up_t<N, C, T> v) -> self &
{
_n -= v._n;
return *this;
}
template <intmax_t N, typename C, typename T>
auto
Scalar<N, C, T>::operator-=(detail::scalar_round_down_t<N, C, T> v) -> self &
{
_n -= v._n;
return *this;
}
template <intmax_t N, typename C, intmax_t S, typename I, typename T>
auto
operator-(Scalar<N, C, T> lhs, Scalar<S, I, T> const &rhs) -> typename std::common_type<Scalar<N, C, T>, Scalar<S, I, T>>::type
{
return typename std::common_type<Scalar<N, C, T>, Scalar<S, I, T>>::type(lhs) -= rhs;
}
template <intmax_t N, typename C, typename T>
Scalar<N, C, T>
operator-(Scalar<N, C, T> const &lhs, Scalar<N, C, T> const &rhs)
{
return Scalar<N, C, T>(lhs) -= rhs;
}
template <intmax_t N, typename C, typename T, typename I>
Scalar<N, C, T>
operator-(detail::scalar_unit_round_up_t<I> lhs, Scalar<N, C, T> const &rhs)
{
return Scalar<N, C, T>(lhs.template scale<N, C>()) -= rhs;
}
template <intmax_t N, typename C, typename T, typename I>
Scalar<N, C, T>
operator-(Scalar<N, C, T> const &lhs, detail::scalar_unit_round_up_t<I> rhs)
{
return Scalar<N, C, T>(lhs) -= rhs;
}
template <intmax_t N, typename C, typename T, typename I>
Scalar<N, C, T>
operator-(detail::scalar_unit_round_down_t<I> lhs, Scalar<N, C, T> const &rhs)
{
return Scalar<N, C, T>(lhs.template scale<N, C>()) -= rhs;
}
template <intmax_t N, typename C, typename T, typename I>
Scalar<N, C, T>
operator-(Scalar<N, C, T> const &lhs, detail::scalar_unit_round_down_t<I> rhs)
{
return Scalar<N, C, T>(lhs) -= rhs;
}
template <intmax_t N, typename C, typename T>
Scalar<N, C, T>
operator-(detail::scalar_round_up_t<N, C, T> lhs, Scalar<N, C, T> const &rhs)
{
return Scalar<N, C, T>(lhs._n) -= rhs;
}
template <intmax_t N, typename C, typename T>
Scalar<N, C, T>
operator-(Scalar<N, C, T> const &lhs, detail::scalar_round_up_t<N, C, T> rhs)
{
return Scalar<N, C, T>(lhs) -= rhs._n;
}
template <intmax_t N, typename C, typename T>
Scalar<N, C, T>
operator-(detail::scalar_round_down_t<N, C, T> lhs, Scalar<N, C, T> const &rhs)
{
return Scalar<N, C, T>(lhs._n) -= rhs;
}
template <intmax_t N, typename C, typename T>
Scalar<N, C, T>
operator-(Scalar<N, C, T> const &lhs, detail::scalar_round_down_t<N, C, T> rhs)
{
return Scalar<N, C, T>(lhs) -= rhs._n;
}
template <intmax_t N, typename C, typename T>
auto
Scalar<N, C, T>::operator++() -> self &
{
++_n;
return *this;
}
template <intmax_t N, typename C, typename T>
auto
Scalar<N, C, T>::operator++(int) -> self
{
self zret(*this);
++_n;
return zret;
}
template <intmax_t N, typename C, typename T>
auto
Scalar<N, C, T>::operator--() -> self &
{
--_n;
return *this;
}
template <intmax_t N, typename C, typename T>
auto
Scalar<N, C, T>::operator--(int) -> self
{
self zret(*this);
--_n;
return zret;
}
template <intmax_t N, typename C, typename T>
auto
Scalar<N, C, T>::inc(Counter n) -> self &
{
_n += n;
return *this;
}
template <intmax_t N, typename C, typename T>
auto
Scalar<N, C, T>::dec(Counter n) -> self &
{
_n -= n;
return *this;
}
template <intmax_t N, typename C, typename T>
auto
Scalar<N, C, T>::operator*=(C n) -> self &
{
_n *= n;
return *this;
}
template <intmax_t N, typename C, typename T>
Scalar<N, C, T>
operator*(Scalar<N, C, T> const &lhs, C n)
{
return Scalar<N, C, T>(lhs) *= n;
}
template <intmax_t N, typename C, typename T>
Scalar<N, C, T>
operator*(C n, Scalar<N, C, T> const &rhs)
{
return Scalar<N, C, T>(rhs) *= n;
}
template <intmax_t N, typename C, typename T>
Scalar<N, C, T>
operator*(Scalar<N, C, T> const &lhs, int n)
{
return Scalar<N, C, T>(lhs) *= n;
}
template <intmax_t N, typename C, typename T>
Scalar<N, C, T>
operator*(int n, Scalar<N, C, T> const &rhs)
{
return Scalar<N, C, T>(rhs) *= n;
}
template <intmax_t N>
Scalar<N, int>
operator*(Scalar<N, int> const &lhs, int n)
{
return Scalar<N, int>(lhs) *= n;
}
template <intmax_t N>
Scalar<N, int>
operator*(int n, Scalar<N, int> const &rhs)
{
return Scalar<N, int>(rhs) *= n;
}
template <intmax_t N, typename C, typename T>
auto
Scalar<N, C, T>::operator/=(C n) -> self &
{
_n /= n;
return *this;
}
template <intmax_t N, typename C, intmax_t S, typename I, typename T>
auto
operator/(Scalar<N, C, T> lhs, Scalar<S, I, T> rhs) -> typename std::common_type<C, I>::type
{
using R = std::ratio<N, S>;
return (lhs.count() * R::num) / (rhs.count() * R::den);
}
template <intmax_t N, typename C, typename T, typename I>
Scalar<N, C, T>
operator/(Scalar<N, C, T> lhs, I n)
{
static_assert(std::is_integral<I>::value, "Scalar division only support integral types.");
return Scalar<N, C, T>(lhs) /= n;
}
template <intmax_t N, typename C, typename T>
auto
Scalar<N, C, T>::operator()(Counter n) const -> self
{
return self{n};
}
template <intmax_t N, typename C, typename T>
auto
Scalar<N, C, T>::plus(Counter n) const -> self
{
return {_n + n};
}
template <intmax_t N, typename C, typename T>
auto
Scalar<N, C, T>::minus(Counter n) const -> self
{
return {_n - n};
}
template <intmax_t N, typename C>
C
round_up(C value)
{
return N * detail::scale_conversion_round_up<N, 1>(value);
}
template <intmax_t N, typename C>
C
round_down(C value)
{
return N * detail::scale_conversion_round_down<N, 1>(value);
}
namespace detail
{
// These classes exist only to create distinguishable overloads.
struct tag_label_A {
};
struct tag_label_B : public tag_label_A {
};
// The purpose is to print a label for a tagged type only if the tag class defines a member that
// is the label. This creates a base function that always works and does nothing. The second
// function creates an overload if the tag class has a member named 'label' that has an stream IO
// output operator. When invoked with a second argument of B then the second overload exists and
// is used, otherwise only the first exists and that is used. The critical technology is the use
// of 'auto' and 'decltype' which effectively checks if the code inside 'decltype' compiles.
template <typename T>
inline std::ostream &
tag_label(std::ostream &s, tag_label_A const &)
{
return s;
}
template <typename T>
inline BufferWriter &
tag_label(BufferWriter &w, BWFSpec const &, tag_label_A const &)
{
return w;
}
template <typename T>
inline auto
tag_label(std::ostream &s, tag_label_B const &) -> decltype(s << T::label, s)
{
return s << T::label;
}
template <typename T>
inline auto
tag_label(BufferWriter &w, BWFSpec const &spec, tag_label_B const &) -> decltype(bwformat(w, spec, T::label), w)
{
return bwformat(w, spec, T::label);
}
} // namespace detail
template <intmax_t N, typename C, typename T>
BufferWriter &
bwformat(BufferWriter &w, BWFSpec const &spec, Scalar<N, C, T> const &x)
{
static constexpr ts::detail::tag_label_B b{};
bwformat(w, spec, x.value());
return ts::detail::tag_label<T>(w, spec, b);
}
} // namespace ts
namespace std
{
template <intmax_t N, typename C, typename T>
ostream &
operator<<(ostream &s, ts::Scalar<N, C, T> const &x)
{
static ts::detail::tag_label_B b; // Can't be const or the compiler gets upset.
s << x.value();
return ts::detail::tag_label<T>(s, b);
}
/// Compute common type of two scalars.
/// In `std` to overload the base definition. This yields a type that has the common type of the
/// counter type and a scale that is the GCF of the input scales.
template <intmax_t N, typename C, intmax_t S, typename I, typename T> struct common_type<ts::Scalar<N, C, T>, ts::Scalar<S, I, T>> {
typedef std::ratio<N, S> R;
typedef ts::Scalar<N / R::num, typename common_type<C, I>::type, T> type;
};
} // namespace std