From 2befea8be4228bf0ef67c308135a23847e10cf37 Mon Sep 17 00:00:00 2001 From: "Alan M. Carroll" Date: Thu, 29 Dec 2016 15:39:21 -0600 Subject: [PATCH] Scalar: Add Scalar (scaled value) support class. --- lib/ts/Makefile.am | 4 +- lib/ts/Scalar.h | 1178 +++++++++++++++++++++++++++++++++++++++++ lib/ts/test_Scalar.cc | 348 ++++++++++++ 3 files changed, 1529 insertions(+), 1 deletion(-) create mode 100644 lib/ts/Scalar.h create mode 100644 lib/ts/test_Scalar.cc diff --git a/lib/ts/Makefile.am b/lib/ts/Makefile.am index ba0a952c7b4..ac7d2ba12e8 100644 --- a/lib/ts/Makefile.am +++ b/lib/ts/Makefile.am @@ -23,7 +23,7 @@ library_includedir=$(includedir)/ts library_include_HEADERS = apidefs.h noinst_PROGRAMS = mkdfa CompileParseRules -check_PROGRAMS = test_tsutil test_arena test_atomic test_freelist test_geometry test_List test_Map test_Vec test_X509HostnameValidator test_MemView +check_PROGRAMS = test_tsutil test_arena test_atomic test_freelist test_geometry test_List test_Map test_Vec test_X509HostnameValidator test_MemView test_Scalar TESTS_ENVIRONMENT = LSAN_OPTIONS=suppressions=suppression.txt @@ -240,6 +240,8 @@ test_tsutil_SOURCES = \ test_MemView_SOURCES = test_MemView.cc test_MemView_LDADD = libtsutil.la +test_Scalar_SOURCES = test_Scalar.cc Scalar.h + CompileParseRules_SOURCES = CompileParseRules.cc clean-local: diff --git a/lib/ts/Scalar.h b/lib/ts/Scalar.h new file mode 100644 index 00000000000..ad72b9d1b7f --- /dev/null +++ b/lib/ts/Scalar.h @@ -0,0 +1,1178 @@ +/** @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. + */ + +#if !defined(TS_SCALAR_H) +#define TS_SCALAR_H + +#include +#include +#include +#include + +namespace tag +{ +struct generic; +} + +namespace ApacheTrafficServer +{ +template class Scalar; + +namespace detail +{ + // @internal - althought these conversion methods look bulky, in practice they compile down to + // very small amounts of code due to dead code elimination and that all of the conditions are + // compile time constants. + + // 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 scale @c N + template + intmax_t + scale_conversion_round_up(intmax_t c) + { + typedef std::ratio 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 + scale_conversion_round_down(intmax_t c) + { + typedef std::ratio 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 untis 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 case, can not be + templated and therefore to have a nice interace for assignment this split is needed. + */ + + // Unit value, to be rounded up. + template struct scalar_unit_round_up_t { + C _n; + // template constexpr operator scalar_unit_round_up_t() { return {static_cast(_n)}; } + template + constexpr I + scale() const + { + return static_cast(_n / N + (0 != (_n % N))); + } + }; + // Unit value, to be rounded down. + template struct scalar_unit_round_down_t { + C _n; + // template operator scalar_unit_round_down_t() { return {static_cast(_n)}; } + template + constexpr I + scale() const + { + return static_cast(_n / N); + } + }; + // Scalar value, to be rounded up. + template struct scalar_round_up_t { + C _n; + template constexpr operator Scalar() const + { + return Scalar(scale_conversion_round_up(_n)); + } + }; + // Scalar value, to be rounded down. + template struct scalar_round_down_t { + C _n; + template constexpr operator Scalar() const + { + return Scalar(scale_conversion_round_down(_n)); + } + }; +} + +/// Mark a unit value to be scaled, rounding down. +template +constexpr detail::scalar_unit_round_up_t +round_up(C n) +{ + return {n}; +} +/// Mark a @c Scalar value to be scaled, rounding up. +template +constexpr detail::scalar_round_up_t +round_up(Scalar v) +{ + return {v.count()}; +} +/// Mark a unit value to be scaled, rounding down. +template +constexpr detail::scalar_unit_round_down_t +round_down(C n) +{ + return {n}; +} +/// Mark a @c Scalar value, to be rounded down. +template +constexpr detail::scalar_round_down_t +round_down(Scalar 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 class Scalar +{ + typedef Scalar self; ///< Self reference type. + +public: + /// Scaling factor - make it external accessible. + constexpr static intmax_t SCALE = N; + typedef C Count; ///< 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::value, "The counter type (2nd template argument) must be an integral type"); + + constexpr Scalar(); ///< Default contructor. + ///< Construct to have @a n scaled units. + explicit constexpr Scalar(Count n); + /// Copy constructor. + constexpr Scalar(self const &that); /// Copy constructor. + /// Copy constructor for same scale. + template constexpr Scalar(Scalar const &that); + /// Direct conversion constructor. + /// @note Requires that @c S be an integer multiple of @c SCALE. + template constexpr Scalar(Scalar const &that); + /// Conversion constructor. + constexpr Scalar(detail::scalar_round_up_t const &that); + /// Conversion constructor. + constexpr Scalar(detail::scalar_round_down_t const &that); + /// Conversion constructor. + template constexpr Scalar(detail::scalar_unit_round_up_t v); + /// Conversion constructor. + template constexpr Scalar(detail::scalar_unit_round_down_t 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 self &operator=(Scalar const &that); + /// Assignment from same scale. + self &operator=(self const &that); + // Conversion assignments. + template self &operator=(detail::scalar_unit_round_up_t n); + template self &operator=(detail::scalar_unit_round_down_t n); + self &operator =(detail::scalar_round_up_t v); + self &operator =(detail::scalar_round_down_t v); + + /// Direct assignment. + /// The count is set to @a n. + self &assign(Count 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 self &assign(Scalar const &that); + // Conversion assignments. + template self &assign(detail::scalar_unit_round_up_t n); + template self &assign(detail::scalar_unit_round_down_t n); + self &assign(detail::scalar_round_up_t v); + self &assign(detail::scalar_round_down_t v); + + /// The number of scale units. + constexpr Count count() const; + /// The scaled value. + constexpr Count units() const; + /// User conversion to scaled value. + constexpr operator Count() 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. + template self &operator+=(Scalar const &that); + /// Addition - add @a n as a number of scaled units. + self &operator+=(C n); + /// Addition - add @a n as a number of scaled units. + self &operator+=(self const &that); + template self &operator+=(detail::scalar_unit_round_up_t n); + template self &operator+=(detail::scalar_unit_round_down_t n); + self &operator+=(detail::scalar_round_up_t v); + self &operator+=(detail::scalar_round_down_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); + + /// 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. + template self &operator-=(Scalar const &that); + /// Subtraction - subtract @a n as a number of scaled units. + self &operator-=(C n); + /// Subtraction - subtract @a n as a number of scaled units. + self &operator-=(self const &that); + + template self &operator-=(detail::scalar_unit_round_up_t n); + template self &operator-=(detail::scalar_unit_round_down_t n); + self &operator-=(detail::scalar_round_up_t v); + self &operator-=(detail::scalar_round_down_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); + + /// Run time access to the scale (template arg @a N). + static constexpr intmax_t scale(); + +protected: + Count _n; ///< Number of scale units. +}; + +template constexpr Scalar::Scalar() : _n() +{ +} +template constexpr Scalar::Scalar(Count n) : _n(n) +{ +} +template constexpr Scalar::Scalar(self const &that) : _n(that._n) +{ +} +template +template +constexpr Scalar::Scalar(Scalar const &that) : _n(static_cast(that.count())) +{ +} +template +template +constexpr Scalar::Scalar(Scalar const &that) : _n(std::ratio::num * that.count()) +{ + static_assert(std::ratio::den == 1, + "Construction not permitted - target scale is not an integral multiple of source scale."); +} +template +constexpr Scalar::Scalar(detail::scalar_round_up_t const &v) : _n(v._n) +{ +} +template +constexpr Scalar::Scalar(detail::scalar_round_down_t const &v) : _n(v._n) +{ +} +template +template +constexpr Scalar::Scalar(detail::scalar_unit_round_up_t v) : _n(v.template scale()) +{ +} +template +template +constexpr Scalar::Scalar(detail::scalar_unit_round_down_t v) : _n(v.template scale()) +{ +} + +template +constexpr auto +Scalar::count() const -> Count +{ + return _n; +} +template +constexpr auto +Scalar::units() const -> Count +{ + return _n * SCALE; +} +template constexpr Scalar::operator C() const +{ + return _n * SCALE; +} + +template +inline auto +Scalar::assign(Count n) -> self & +{ + _n = n; + return *this; +} +template +inline auto +Scalar::operator=(self const &that) -> self & +{ + _n = that._n; + return *this; +} +template +inline auto +Scalar::operator=(detail::scalar_round_up_t v) -> self & +{ + _n = v._n; + return *this; +} +template +inline auto +Scalar::assign(detail::scalar_round_up_t v) -> self & +{ + _n = v._n; + return *this; +} +template +inline auto +Scalar::operator=(detail::scalar_round_down_t v) -> self & +{ + _n = v._n; + return *this; +} +template +inline auto +Scalar::assign(detail::scalar_round_down_t v) -> self & +{ + _n = v._n; + return *this; +} +template +template +inline auto +Scalar::operator=(detail::scalar_unit_round_up_t v) -> self & +{ + _n = v.template scale(); + return *this; +} +template +template +inline auto +Scalar::assign(detail::scalar_unit_round_up_t v) -> self & +{ + _n = v.template scale(); + return *this; +} +template +template +inline auto +Scalar::operator=(detail::scalar_unit_round_down_t v) -> self & +{ + _n = v.template scale(); + return *this; +} +template +template +inline auto +Scalar::assign(detail::scalar_unit_round_down_t v) -> self & +{ + _n = v.template scale(); + return *this; +} +template +template +auto +Scalar::operator=(Scalar const &that) -> self & +{ + typedef std::ratio 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 +template +auto +Scalar::assign(Scalar const &that) -> self & +{ + typedef std::ratio 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 +constexpr inline intmax_t +Scalar::scale() +{ + return SCALE; +} + +// --- Compare operators +// These optimize nicely due to dead code elimination. + +template +bool +operator<(Scalar const &lhs, Scalar const &rhs) +{ + typedef std::ratio R; + if (N == S) + return lhs.count() < rhs.count(); + else if (R::den == 1) + return lhs.count() * R::num < rhs.count(); + else if (R::num == 1) + return lhs.count() < rhs.count() * R::den; + else + return lhs.units() < rhs.units(); +} + +template +bool +operator==(Scalar const &lhs, Scalar const &rhs) +{ + typedef std::ratio R; + if (N == S) + return lhs.count() == rhs.count(); + else if (R::den == 1) + return lhs.count() * R::num == rhs.count(); + else if (R::num == 1) + return lhs.count() == rhs.count() * R::den; + else + return lhs.units() == rhs.units(); +} + +template +bool +operator<=(Scalar const &lhs, Scalar const &rhs) +{ + typedef std::ratio R; + if (N == S) + return lhs.count() <= rhs.count(); + else if (R::den == 1) + return lhs.count() * R::num <= rhs.count(); + else if (R::num == 1) + return lhs.count() <= rhs.count() * R::den; + else + return lhs.units() <= rhs.units(); +} + +// Derived compares. + +template +bool +operator>(Scalar const &lhs, Scalar const &rhs) +{ + return rhs < lhs; +} + +template +bool +operator>=(Scalar const &lhs, Scalar const &rhs) +{ + return rhs <= lhs; +} + +// Do the integer compares. +// A bit ugly to handle the issue that integers without explicit type are . Therefore suppport +// must be provided for comparison not just to the counter type C but also explicitly , otherwise +// function template argument deduction may fail (because it can't figure out what to use for ). + +template +bool +operator<(Scalar const &lhs, C n) +{ + return lhs.count() < n; +} +template +bool +operator<(C n, Scalar const &rhs) +{ + return n < rhs.count(); +} +template +bool +operator<(Scalar const &lhs, int n) +{ + return lhs.count() < static_cast(n); +} +template +bool +operator<(int n, Scalar const &rhs) +{ + return static_cast(n) < rhs.count(); +} +template +bool +operator<(Scalar const &lhs, int n) +{ + return lhs.count() < n; +} +template +bool +operator<(int n, Scalar const &rhs) +{ + return n < rhs.count(); +} + +template +bool +operator==(Scalar const &lhs, C n) +{ + return lhs.count() == n; +} +template +bool +operator==(C n, Scalar const &rhs) +{ + return n == rhs.count(); +} +template +bool +operator==(Scalar const &lhs, int n) +{ + return lhs.count() == static_cast(n); +} +template +bool +operator==(int n, Scalar const &rhs) +{ + return static_cast(n) == rhs.count(); +} +template +bool +operator==(Scalar const &lhs, int n) +{ + return lhs.count() == n; +} +template +bool +operator==(int n, Scalar const &rhs) +{ + return n == rhs.count(); +} + +template +bool +operator>(Scalar const &lhs, C n) +{ + return lhs.count() > n; +} +template +bool +operator>(C n, Scalar const &rhs) +{ + return n > rhs.count(); +} +template +bool +operator>(Scalar const &lhs, int n) +{ + return lhs.count() > static_cast(n); +} +template +bool +operator>(int n, Scalar const &rhs) +{ + return static_cast(n) > rhs.count(); +} +template +bool +operator>(Scalar const &lhs, int n) +{ + return lhs.count() > n; +} +template +bool +operator>(int n, Scalar const &rhs) +{ + return n > rhs.count(); +} + +template +bool +operator<=(Scalar const &lhs, C n) +{ + return lhs.count() <= n; +} +template +bool +operator<=(C n, Scalar const &rhs) +{ + return n <= rhs.count(); +} +template +bool +operator<=(Scalar const &lhs, int n) +{ + return lhs.count() <= static_cast(n); +} +template +bool +operator<=(int n, Scalar const &rhs) +{ + return static_cast(n) <= rhs.count(); +} +template +bool +operator<=(Scalar const &lhs, int n) +{ + return lhs.count() <= n; +} +template +bool +operator<=(int n, Scalar const &rhs) +{ + return n <= rhs.count(); +} + +template +bool +operator>=(Scalar const &lhs, C n) +{ + return lhs.count() >= n; +} +template +bool +operator>=(C n, Scalar const &rhs) +{ + return n >= rhs.count(); +} +template +bool +operator>=(Scalar const &lhs, int n) +{ + return lhs.count() >= static_cast(n); +} +template +bool +operator>=(int n, Scalar const &rhs) +{ + return static_cast(n) >= rhs.count(); +} +template +bool +operator>=(Scalar const &lhs, int n) +{ + return lhs.count() >= n; +} +template +bool +operator>=(int n, Scalar const &rhs) +{ + return n >= rhs.count(); +} + +// Arithmetic operators +template +template +auto +Scalar::operator+=(Scalar const &that) -> self & +{ + typedef std::ratio 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 +auto +Scalar::operator+=(self const &that) -> self & +{ + _n += that._n; + return *this; +} +template +auto +Scalar::operator+=(C n) -> self & +{ + _n += n; + return *this; +} +template +template +auto +Scalar::operator+=(detail::scalar_unit_round_up_t v) -> self & +{ + _n += v.template scale(); + return *this; +} +template +template +auto +Scalar::operator+=(detail::scalar_unit_round_down_t v) -> self & +{ + _n += v.template scale(); + return *this; +} +template +auto +Scalar::operator+=(detail::scalar_round_up_t v) -> self & +{ + _n += v._n; + return *this; +} +template +auto +Scalar::operator+=(detail::scalar_round_down_t v) -> self & +{ + _n += v._n; + return *this; +} + +template +auto +operator+(Scalar lhs, Scalar const &rhs) -> typename std::common_type, Scalar>::type +{ + return typename std::common_type, Scalar>::type(lhs) += rhs; +} + +template +Scalar +operator+(Scalar const &lhs, Scalar const &rhs) +{ + return Scalar(lhs) += rhs; +} +template +Scalar +operator+(Scalar const &lhs, C n) +{ + return Scalar(lhs) += n; +} +template +Scalar +operator+(C n, Scalar const &rhs) +{ + return Scalar(rhs) += n; +} +template +Scalar +operator+(Scalar const &lhs, int n) +{ + return Scalar(lhs) += n; +} +template +Scalar +operator+(int n, Scalar const &rhs) +{ + return Scalar(rhs) += n; +} +template +Scalar +operator+(Scalar const &lhs, int n) +{ + return Scalar(lhs) += n; +} +template +Scalar +operator+(int n, Scalar const &rhs) +{ + return Scalar(rhs) += n; +} +template +Scalar +operator+(detail::scalar_unit_round_up_t lhs, Scalar const &rhs) +{ + return Scalar(rhs) += lhs.template scale(); +} +template +Scalar +operator+(Scalar const &lhs, detail::scalar_unit_round_up_t rhs) +{ + return Scalar(lhs) += rhs.template scale(); +} +template +Scalar +operator+(detail::scalar_unit_round_down_t lhs, Scalar const &rhs) +{ + return Scalar(rhs) += lhs.template scale(); +} +template +Scalar +operator+(Scalar const &lhs, detail::scalar_unit_round_down_t rhs) +{ + return Scalar(lhs) += rhs.template scale(); +} +template +Scalar +operator+(detail::scalar_round_up_t lhs, Scalar const &rhs) +{ + return Scalar(rhs) += lhs._n; +} +template +Scalar +operator+(Scalar const &lhs, detail::scalar_round_up_t rhs) +{ + return Scalar(lhs) += rhs._n; +} +template +Scalar +operator+(detail::scalar_round_down_t lhs, Scalar const &rhs) +{ + return Scalar(rhs) += lhs._n; +} +template +Scalar +operator+(Scalar const &lhs, detail::scalar_round_down_t rhs) +{ + return Scalar(lhs) += rhs._n; +} + +template +template +auto +Scalar::operator-=(Scalar const &that) -> self & +{ + typedef std::ratio 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 +auto +Scalar::operator-=(self const &that) -> self & +{ + _n -= that._n; + return *this; +} +template +auto +Scalar::operator-=(C n) -> self & +{ + _n -= n; + return *this; +} +template +template +auto +Scalar::operator-=(detail::scalar_unit_round_up_t v) -> self & +{ + _n -= v.template scale(); + return *this; +} +template +template +auto +Scalar::operator-=(detail::scalar_unit_round_down_t v) -> self & +{ + _n -= v.template scale(); + return *this; +} +template +auto +Scalar::operator-=(detail::scalar_round_up_t v) -> self & +{ + _n -= v._n; + return *this; +} +template +auto +Scalar::operator-=(detail::scalar_round_down_t v) -> self & +{ + _n -= v._n; + return *this; +} + +template +auto +operator-(Scalar lhs, Scalar const &rhs) -> typename std::common_type, Scalar>::type +{ + return typename std::common_type, Scalar>::type(lhs) -= rhs; +} + +template +Scalar +operator-(Scalar const &lhs, Scalar const &rhs) +{ + return Scalar(lhs) -= rhs; +} +template +Scalar +operator-(Scalar const &lhs, C n) +{ + return Scalar(lhs) -= n; +} +template +Scalar +operator-(C n, Scalar const &rhs) +{ + return Scalar(n) -= rhs; +} +template +Scalar +operator-(Scalar const &lhs, int n) +{ + return Scalar(lhs) -= n; +} +template +Scalar +operator-(int n, Scalar const &rhs) +{ + return Scalar(n) -= rhs; +} +template +Scalar +operator-(Scalar const &lhs, int n) +{ + return Scalar(lhs) -= n; +} +template +Scalar +operator-(int n, Scalar const &rhs) +{ + return Scalar(n) -= rhs; +} +template +Scalar +operator-(detail::scalar_unit_round_up_t lhs, Scalar const &rhs) +{ + return Scalar(lhs.template scale()) -= rhs; +} +template +Scalar +operator-(Scalar const &lhs, detail::scalar_unit_round_up_t rhs) +{ + return Scalar(lhs) -= rhs.template scale(); +} +template +Scalar +operator-(detail::scalar_unit_round_down_t lhs, Scalar const &rhs) +{ + return Scalar(lhs.template scale()) -= rhs; +} +template +Scalar +operator-(Scalar const &lhs, detail::scalar_unit_round_down_t rhs) +{ + return Scalar(lhs) -= rhs.template scale(); +} +template +Scalar +operator-(detail::scalar_round_up_t lhs, Scalar const &rhs) +{ + return Scalar(lhs._n) -= rhs; +} +template +Scalar +operator-(Scalar const &lhs, detail::scalar_round_up_t rhs) +{ + return Scalar(lhs) -= rhs._n; +} +template +Scalar +operator-(detail::scalar_round_down_t lhs, Scalar const &rhs) +{ + return Scalar(lhs._n) -= rhs; +} +template +Scalar +operator-(Scalar const &lhs, detail::scalar_round_down_t rhs) +{ + return Scalar(lhs) -= rhs._n; +} + +template auto Scalar::operator++() -> self & +{ + ++_n; + return *this; +} + +template auto Scalar::operator++(int) -> self +{ + self zret(*this); + ++_n; + return zret; +} + +template auto Scalar::operator--() -> self & +{ + --_n; + return *this; +} + +template auto Scalar::operator--(int) -> self +{ + self zret(*this); + --_n; + return zret; +} + +template +auto +Scalar::operator*=(C n) -> self & +{ + _n *= n; + return *this; +} + +template Scalar operator*(Scalar const &lhs, C n) +{ + return Scalar(lhs) *= n; +} +template Scalar operator*(C n, Scalar const &rhs) +{ + return Scalar(rhs) *= n; +} +template Scalar operator*(Scalar const &lhs, int n) +{ + return Scalar(lhs) *= n; +} +template Scalar operator*(int n, Scalar const &rhs) +{ + return Scalar(rhs) *= n; +} +template Scalar operator*(Scalar const &lhs, int n) +{ + return Scalar(lhs) *= n; +} +template Scalar operator*(int n, Scalar const &rhs) +{ + return Scalar(rhs) *= n; +} + +template +auto +Scalar::operator/=(C n) -> self & +{ + _n /= n; + return *this; +} + +template +Scalar +operator/(Scalar const &lhs, C n) +{ + return Scalar(lhs) /= n; +} +template +Scalar +operator/(Scalar const &lhs, int n) +{ + return Scalar(lhs) /= n; +} +template +Scalar +operator/(Scalar const &lhs, int n) +{ + return Scalar(lhs) /= n; +} + +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 + inline std::ostream & + tag_label(std::ostream &s, tag_label_A const &) + { + return s; + } + template + inline auto + tag_label(std::ostream &s, tag_label_B const &) -> decltype(s << T::label, s) + { + return s << T::label; + } +} // detail + +} // namespace + +namespace std +{ +template +ostream & +operator<<(ostream &s, ApacheTrafficServer::Scalar const &x) +{ + static ApacheTrafficServer::detail::tag_label_B b; // Can't be const or the compiler gets upset. + s << x.units(); + return ApacheTrafficServer::detail::tag_label(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 +struct common_type, ApacheTrafficServer::Scalar> { + typedef std::ratio R; + typedef ApacheTrafficServer::Scalar::type, T> type; +}; +} +#endif // TS_SCALAR_H diff --git a/lib/ts/test_Scalar.cc b/lib/ts/test_Scalar.cc new file mode 100644 index 00000000000..2c17e8aa8ff --- /dev/null +++ b/lib/ts/test_Scalar.cc @@ -0,0 +1,348 @@ +/** @file + + Intrusive pointer test. + + @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. +*/ + +#include +#include +#include +#include + +namespace ts +{ +using namespace ApacheTrafficServer; +} + +struct TestBox { + typedef TestBox self; ///< Self reference type. + + std::string _name; + + static int _count; + static int _fail; + + TestBox(char const *name) : _name(name) {} + TestBox(std::string const &name) : _name(name) {} + bool check(bool result, char const *fmt, ...) __attribute__((format(printf, 3, 4))); + + static void + print_summary() + { + printf("Tests: %d of %d passed - %s\n", (_count - _fail), _count, _fail ? "FAIL" : "SUCCESS"); + } +}; + +int TestBox::_count = 0; +int TestBox::_fail = 0; + +bool +TestBox::check(bool result, char const *fmt, ...) +{ + ++_count; + + if (!result) { + static constexpr size_t N = 1 << 16; + size_t n = N; + size_t x; + char *s; + char buffer[N]; // just stack, go big. + + s = buffer; + x = snprintf(s, n, "%s: ", _name.c_str()); + n -= x; + s += x; + + va_list ap; + va_start(ap, fmt); + vsnprintf(s, n, fmt, ap); + va_end(ap); + printf("%s\n", buffer); + ++_fail; + } + return result; +} + +// Extremely simple test. +void +Test_1() +{ + constexpr static int SCALE = 4096; + typedef ts::Scalar PageSize; + + TestBox test("TS Scalar basic"); + PageSize pg1(1); + + test.check(pg1.count() == 1, "Count wrong, got %d expected %d", pg1.count(), 1); + test.check(pg1.units() == SCALE, "Units wrong, got %d expected %d", pg1.units(), SCALE); +} + +// Test multiples. +void +Test_2() +{ + constexpr static int SCALE_1 = 8192; + constexpr static int SCALE_2 = 512; + + typedef ts::Scalar Size_1; + typedef ts::Scalar Size_2; + + TestBox test("TS Scalar Conversion of scales of multiples"); + Size_2 sz_a(2); + Size_2 sz_b(57); + Size_2 sz_c(SCALE_1 / SCALE_2); + Size_2 sz_d(29 * SCALE_1 / SCALE_2); + + Size_1 sz = ts::round_up(sz_a); + test.check(sz.count() == 1, "[1] Rounding up, got %d expected %d", sz.count(), 1); + sz = ts::round_down(sz_a); + test.check(sz.count() == 0, "[2] Rounding down: got %d expected %d", sz.count(), 0); + + sz = ts::round_up(sz_b); + test.check(sz.count() == 4, "[3] Rounding up, got %d expected %d", sz.count(), 4); + sz = ts::round_down(sz_b); + test.check(sz.count() == 3, "[4] Rounding down, got %d expected %d", sz.count(), 3); + + sz = ts::round_up(sz_c); + test.check(sz.count() == 1, "[5] Rounding up, got %d expected %d", sz.count(), 1); + sz = ts::round_down(sz_c); + test.check(sz.count() == 1, "[6] Rounding down, got %d expected %d", sz.count(), 1); + + sz = ts::round_up(sz_d); + test.check(sz.count() == 29, "[7] Rounding up, got %d expected %d", sz.count(), 29); + sz = ts::round_down(sz_d); + test.check(sz.count() == 29, "[8] Rounding down, got %d expected %d", sz.count(), 29); + + sz.assign(119); + sz_b = sz; // Should be OK because SCALE_1 is an integer multiple of SCALE_2 + // sz = sz_b; // Should not compile. + test.check(sz_b.count() == 119 * (SCALE_1 / SCALE_2), "Integral conversion, got %d expected %d", sz_b.count(), + 119 * (SCALE_1 / SCALE_2)); +} + +// Test common factor. +void +Test_3() +{ + constexpr static int SCALE_1 = 30; + constexpr static int SCALE_2 = 20; + + typedef ts::Scalar Size_1; + typedef ts::Scalar Size_2; + + TestBox test("TS Scalar common factor conversions"); + Size_2 sz_a(2); + Size_2 sz_b(97); + + Size_1 sz = round_up(sz_a); + test.check(sz.count() == 2, "Rounding up, got %d expected %d", sz.count(), 2); + sz = round_down(sz_a); + test.check(sz.count() == 1, "Rounding down: got %d expected %d", sz.count(), 0); + + sz = ts::round_up(sz_b); + test.check(sz.count() == 65, "Rounding up, got %d expected %d", sz.count(), 65); + sz = ts::round_down(sz_b); + test.check(sz.count() == 64, "Rounding down, got %d expected %d", sz.count(), 64); +} + +void +Test_4() +{ + TestBox test("TS Scalar: relatively prime tests"); + + ts::Scalar<9> m_9; + ts::Scalar<4> m_4, m_test; + + m_9.assign(95); + // m_4 = m_9; // Should fail to compile with static assert. + // m_9 = m_4; // Should fail to compile with static assert. + + m_4 = ts::round_up(m_9); + test.check(m_4.count() == 214, "Rounding down, got %d expected %d", m_4.count(), 214); + m_4 = ts::round_down(m_9); + test.check(m_4.count() == 213, "Rounding down, got %d expected %d", m_4.count(), 213); + + m_4.assign(213); + m_9 = ts::round_up(m_4); + test.check(m_9.count() == 95, "Rounding down, got %d expected %d", m_9.count(), 95); + m_9 = ts::round_down(m_4); + test.check(m_9.count() == 94, "Rounding down, got %d expected %d", m_9.count(), 94); + + m_test = m_4; // Verify assignment of identical scale values compiles. + test.check(m_test.count() == 213, "Assignment got %d expected %d", m_4.count(), 213); +} + +void +Test_5() +{ + TestBox test("TS Scalar: arithmetic operator tests"); + + typedef ts::Scalar<1024> KBytes; + typedef ts::Scalar<1024, long int> KiBytes; + typedef ts::Scalar<1, int64_t> Bytes; + typedef ts::Scalar<1024 * KBytes::SCALE> MBytes; + + Bytes bytes(96); + KBytes kbytes(2); + MBytes mbytes(5); + + Bytes z1 = bytes + 128; + test.check(z1.count() == 224, "[1] Addition got %ld expected %d", z1.count(), 224); + KBytes z2 = kbytes + 3; + test.check(z2.count() == 5, "[2] Addition got %d expected %d", z2.count(), 5); + Bytes z3(bytes); + z3 += kbytes; + test.check(z3.units() == 2048 + 96, "[3] Addition got %ld expected %d", z3.units(), 2048 + 96); + MBytes z4 = mbytes; + z4 += 5; + z2 += z4; + test.check(z2.units() == ((10 << 20) + (5 << 10)), "[4] Addition got %d expected %d", z2.units(), (10 << 20) + (2 << 10)); + + z1 += 128; + test.check(z1.count() == 352, "[5] Addition got %ld expected %d", z1.count(), 352); + + z2.assign(2); + z1 = 3 * z2; + test.check(z1.count() == 6144, "[6] Addition got %ld expected %d", z1.count(), 6144); + z1 *= 5; + test.check(z1.count() == 30720, "[7] Addition got %ld expected %d", z1.count(), 30720); + z1 /= 3; + test.check(z1.count() == 10240, "[8] Addition got %ld expected %d", z1.count(), 10240); + + z2.assign(3148); + auto x = z2 + MBytes(1); + test.check(x.scale() == z2.scale(), "[9] Common type addition yielded bad scale %ld - expected %ld", x.scale(), z2.scale()); + test.check(x.count() == 4172, "[10] Common type addition yielded bad count %d - expected %d", x.count(), 4172); + + z2 = ts::round_down(262150); + test.check(z2.count() == 256, "[11] Unit scale down assignment bad count %d - expected %d", z2.count(), 256); + + z2 = ts::round_up(262150); + test.check(z2.count() == 257, "[12] Unit scale up assignment bad count %d - expected %d", z2.count(), 257); + + KBytes q(ts::round_down(262150)); + test.check(q.count() == 256, "[13] Unit scale down constructor bad count %d - expected %d", q.count(), 256); + + z2 += ts::round_up(97384); + test.check(z2.count() == 353, "[14] Unit scale down += bad count %d - expected %d", z2.count(), 353); + + decltype(z2) a = z2 + ts::round_down(167229); + test.check(a.count() == 516, "[15] Unit scale down += bad count %d - expected %d", a.count(), 516); + + KiBytes k(3148); + auto kx = k + MBytes(1); + test.check(kx.scale() == k.scale(), "[9] Common type addition yielded bad scale %ld - expected %ld", kx.scale(), k.scale()); + test.check(kx.count() == 4172, "[10] Common type addition yielded bad count %ld - expected %d", kx.count(), 4172); + + k = ts::round_down(262150); + test.check(k.count() == 256, "[11] Unit scale down assignment bad count %ld - expected %d", k.count(), 256); + + k = ts::round_up(262150); + test.check(k.count() == 257, "[12] Unit scale up assignment bad count %ld - expected %d", k.count(), 257); + + KBytes kq(ts::round_down(262150)); + test.check(kq.count() == 256, "[13] Unit scale down constructor bad count %d - expected %d", kq.count(), 256); + + k += ts::round_up(97384); + test.check(k.count() == 353, "[14] Unit scale down += bad count %ld - expected %d", k.count(), 353); + + decltype(k) ka = k + ts::round_down(167229); + test.check(ka.count() == 516, "[15] Unit scale down += bad count %ld - expected %d", ka.count(), 516); +} + +// test comparisons +void +Test_6() +{ + using ts::Scalar; + typedef Scalar<1024, ssize_t> KB; + typedef Scalar MB; + typedef Scalar<8 * KB::SCALE, ssize_t> StoreBlocks; + typedef Scalar<127 * MB::SCALE, ssize_t> SpanBlocks; + + TestBox test("TS Scalar: comparison operator tests"); + + StoreBlocks a(80759700); + SpanBlocks b(4968); + SpanBlocks delta(1); + + test.check(a < b, "[1] Less than incorrect %ld < %ld", a.units(), b.units()); + test.check(b < (a + delta), "[2] Less than incorrect %ld < %ld", b.units(), (a + delta).units()); +} + +struct KBytes_tag { + static std::string const label; +}; +std::string const KBytes_tag::label(" bytes"); + +void +Test_IO() +{ + typedef ts::Scalar<1024, long int, KBytes_tag> KBytes; + typedef ts::Scalar<1024, int> KiBytes; + + KBytes x(12); + KiBytes y(12); + + std::cout << "Testing" << std::endl; + std::cout << "x is " << x << std::endl; + std::cout << "y is " << y << std::endl; +} + +void +test_Compile() +{ + // These tests aren't normally run, they exist to detect compiler issues. + + typedef ts::Scalar<1024, short> KBytes; + typedef ts::Scalar<1024, int> KiBytes; + int delta = 10; + + KBytes x(12); + KiBytes y(12); + + if (x > 12) + std::cout << "Operator > works" << std::endl; + if (y > 12) + std::cout << "Operator > works" << std::endl; + + (void)(x += 10); + (void)(x += static_cast(10)); + (void)(x += static_cast(10)); + (void)(x += delta); + (void)(y += 10); + (void)(y += static_cast(10)); + (void)(y += static_cast(10)); + (void)(y += delta); +} + +int +main(int, char **) +{ + Test_1(); + Test_2(); + Test_3(); + Test_4(); + Test_5(); + Test_6(); + Test_IO(); + TestBox::print_summary(); + return 0; +}