I've written some classes to encapsulate the limits given for datasheet parameters, e.g. for the classic [741 op amp](http://www.ti.com/lit/ds/symlink/lm741.pdf): [![741 datasheet examples][1]][1] As the above example datasheet snippet shows, there are some challenges for representing the values in the "min", "typ" (typical), and "max" columns -- including cases where there is no typical value, and single-sided limits (i.e. there is no minimum and/or no maximum). Additionally, I need to support limits which require exact integer values (e.g. for representing the limits for an [analog-to-digital converter (ADC)](https://en.wikipedia.org/wiki/Analog-to-digital_converter), which outputs discrete values). Consequently, I've written my classes as templates to support both integral and floating point types (the former is better suited to digital parameters, the latter to analog). I also provided support for [guardbands](https://metrology.wordpress.com/statistical-methods-index/basic-theory-of-measurement-and-error/conformity-assessment-introduction/guard-banding/) for tightening the limits in a test/quality control environment. My compiler at work is the aging VS2005 compiler, so I don't have some of the useful C++11 `std` utilities like `std::numeric_limits<T>::lowest()`. I've implemented a few that I need: /** \file mycpp11std.h */ #include <limits> // std::numeric_limits namespace mycpp11std { /** Implementation of enable_if from C++11 `std`, taken from http://en.cppreference.com/w/cpp/types/enable_if. */ template<bool B, typename T = void> struct enable_if {}; /** Specialization of enable_if from C++11 `std` for `B = true`. Taken from http://en.cppreference.com/w/cpp/types/enable_if. */ template<class T> struct enable_if<true, T> { typedef T type; }; /** Implementation of numeric_limits<T>::lowest from C++11 `std`. Returns the most negative value that can be represented by the type T. */ template <typename T, typename Enable = void> struct numeric_limits { static T lowest() { return -std::numeric_limits<T>::max(); } }; /** Specialization of numeric_limits<T>::lowest for integers. */ template <typename T> struct numeric_limits<T, typename enable_if<std::numeric_limits<T>::is_integer >::type> { static T lowest() { return std::numeric_limits<T>::min(); } }; } // end namespace mycpp11std On to the actual class templates: /** \file range.h */ #include <limits> // std::numeric_limits #include "mycpp11std.h" namespace range { /** \brief Encapsulates a range of acceptable values by setting a lower and upper limit. The lower limit may be greater than the upper limit. Single-sided limits can also be represented by using the `static` functions `no_minimum()` and `no_maximum()`, which return the minimum and maximum values, respectively, that can be represented by the type `T`. Guardbands are also supported. */ template <typename T> class Range { T lgb; /**< \brief the lower guardband */ T ugb; /**< \brief the upper guardband */ /** \brief Make sure lower guardband is positive */ void validate_lgb() { lgb = std::abs(lgb); } /** \brief Make sure upper guardband is positive */ void validate_ugb() { ugb = std::abs(ugb); } public: T lowerlimit; /**< \brief the specified lower limit */ T upperlimit; /**< \brief the specified upper limit */ /** \brief Set `lowerlimit` to this value in order to indicate that there is no lower limit */ static T no_minimum() { /* std::numeric_limits<T>::min() is the smallest value, not necessarily the most negative. */ return mycpp11std::numeric_limits<T>::lowest(); } /** \brief Set `upperlimit` to this value in order to indicate that there is no upper limit */ static T no_maximum() { return std::numeric_limits<T>::max(); } /** \brief Construct a `Range` with lower and upper limits, and zero guardbands. */ Range(T lowerlimit, T upperlimit) : lowerlimit(lowerlimit), upperlimit(upperlimit), lgb(0), ugb(0) {} /** \brief Construct a `Range` with lower and upper limits as well as guardbands. */ Range(T lowerlimit, T upperlimit, T lowerguardband, T upperguardband) : lowerlimit(lowerlimit), upperlimit(upperlimit), lgb(lowerguardband), ugb(upperguardband) { validate_lgb(); validate_ugb(); } /** \brief The minimum passing value, defined as the lower limit plus the lower guardband. */ T min() const { return lowerlimit + lgb; } /** \brief The maximum passing value, defined as the upper limit minus the upper guardband. */ T max() const { return upperlimit - ugb; } /** \brief Returns the currently set lower guardband value */ T lowerguardband() const { return lgb; } /** \brief Returns the currently set upper guardband value */ T upperguardband() const { return ugb; } /** \brief Sets the lower guardband. \param[in] lgb the value to set the lower guardband to. The lower guardband will be set to the absolute value of `lgb` if it is not positive. */ void lowerguardband(T lgb) { this->lgb = lgb; validate_lgb(); } /** \brief Sets the upper guardband. \param[in] ugb the value to set the upper guardband to. The upper guardband will be set to the absolute value of `ugb` if it is not positive. */ void upperguardband(T ugb) { this->ugb = ugb; validate_ugb(); } }; /** \brief Encapsulates a range of acceptable values by setting a lower and upper limit, as well as a typical value for a parameter. */ template <typename T> class Parameter : public Range<T> { public: T typical; /**< \brief the typical value of the Parameter */ /** \brief Construct a `Parameter` with lower and upper limits and a typical value, but zero guardbands. */ Parameter(T lowerlimit, T typical, T upperlimit) : Range(lowerlimit, upperlimit), typical(typical) {} /** \brief Construct a `Parameter` with lower and upper limits and a typical value, as well as guardbands. */ Parameter(T lowerlimit, T typical, T upperlimit, T lower_guardband, T upper_guardband) : Range(lowerlimit, upperlimit, lower_guardband, upper_guardband), typical(typical) {} }; /** \brief Determines if `value` is within the (possibly guardbanded) limits (inclusive) set by `range`. */ template <typename T> bool pass(T value, const Range<T>& range) { return (value >= range.min()) && (value <= range.max()); } } // end namespace range The `Range` class doesn't need to do sanity checking on the limits (e.g. to ensure `lowerlimit <= upperlimit`) so I left those members `public` to avoid having to implement pointless getters and setters. I did make the guardband members `lgb` and `ugb` private so that I could ensure they are positive (which is necessary to make sure the guardbands always tighten the limits). The `Range` member functions `min()` and `max()` provide the (possibly guardbanded) limits. To support single-sided limits, I provided the `static` member functions `no_minimum()` and `no_maximum()`, and this is where I needed to implement a few C++11 utilities. The idea is to set `lowerlimit` and/or `upperlimit` to the lowest or maximum value, respectively, that could be represented by the type `T`. The `Parameter` class just inherits from `Range` and adds the `public` member `typical`. There's no meaningful value for `typical` for datasheet parameters which don't have a typical value specified, so such parameters would use `Range`. Datasheet parameters which do have a typical value would use `Parameter`. I've also provided a function template `pass()` to determine if a value is within the limits of a `Range` or `Parameter`. Here's some code to demonstrate the use of `Range` and `Parameter`: #include <iostream> #include <fstream> #include "range.h" using namespace range; void check_guardbands(std::ostream& os) { Parameter<double> Isc(10, 25, 35); os << "Isc guardbands: " << Isc.lowerguardband() << " and " << Isc.upperguardband() << "\n"; os << "Isc min/max: " << Isc.min() << " and " << Isc.max() << "\n"; Isc.lowerguardband(-1); Isc.upperguardband(-1); os << "New Isc guardbands: " << Isc.lowerguardband() << " and " << Isc.upperguardband() << "\n"; os << "New Isc min/max: " << Isc.min() << " and " << Isc.max() << "\n"; os << "Isc typical: " << Isc.typical << "\n"; os << "\n"; } void check_singlesided(std::ostream& os) { Parameter<int> CMRR(80, 95, Parameter<int>::no_maximum()); Parameter<int> Power(Parameter<int>::no_minimum(), 80, 150); Range<unsigned int> Power_hot(Range<unsigned int>::no_minimum(), 135); Parameter<double> Isupply(Parameter<double>::no_minimum(), 1.7, 2.8); os << "CMRR (int type, no max):\n"; os << "minimum = " << CMRR.min() << ", "; os << "typical = " << CMRR.typical << ", "; os << "maximum = " << CMRR.max() << "\n"; os << "\n"; os << "Power consumption, ambient (int type, no min):\n"; os << "minimum = " << Power.min() << ", "; os << "typical = " << Power.typical << ", "; os << "maximum = " << Power.max() << "\n"; os << "\n"; os << "Power consumption, hot (unsigned int type, no min):\n"; os << "minimum = " << Power_hot.min() << ", "; os << "maximum = " << Power_hot.max() << "\n"; os << "\n"; os << "Isupply (double type, no minimum):\n"; os << "minimum = " << Isupply.min() << ", "; os << "typical = " << Isupply.typical << ", "; os << "maximum = " << Isupply.max() << "\n"; os << "\n"; } void check_pass(std::ostream& os) { const Range<double> Isc_temp(10, 40); const Range<int> zero(0, 0); Parameter<double> Isc(10, 25, 35); Parameter<double> Isupply(Parameter<double>::no_minimum(), 1.7, 2.8); Parameter<int> CMRR(80, 95, Parameter<int>::no_maximum()); os << "Testing pass():\n"; double val = 38; os << val << " is between " << Isc_temp.min() << " and " << Isc_temp.max() << "?: " << pass<double>(val, Isc_temp) << "\n"; os << val << " is between " << Isc.min() << " and " << Isc.max() << "?: " << pass<double>(val, Isc) << "\n"; val = 5; os << val << " is between " << Isc.min() << " and " << Isc.max() << "?: " << pass<double>(val, Isc) << "\n"; val = 10; os << val << " is between " << Isc.min() << " and " << Isc.max() << "?: " << pass<double>(val, Isc) << "\n"; val = 2.81; os << val << " is between " << Isupply.min() << " and " << Isupply.max() << "?: " << pass<double>(val, Isupply) << "\n"; val = 0.1; os << val << " is between " << Isupply.min() << " and " << Isupply.max() << "?: " << pass<double>(val, Isupply) << "\n"; val = 0; os << val << " is between " << Isupply.min() << " and " << Isupply.max() << "?: " << pass<double>(val, Isupply) << "\n"; val = -1e3; os << val << " is between " << Isupply.min() << " and " << Isupply.max() << "?: " << pass<double>(val, Isupply) << "\n"; val = 80; os << val << " is between " << CMRR.min() << " and " << CMRR.max() << "?: " << pass<int>((int)val, CMRR) << "\n"; val = 0.1; os << val << " (double cast to int) is between " << zero.min() << " and " << zero.max() << "?: " << pass<int>((int)val, zero) << "\n"; val = 0; os << val << " (double cast to int) is between " << zero.min() << " and " << zero.max() << "?: " << pass<int>((int)val, zero) << "\n"; int intval = 0; os << intval << " (int type) is between " << zero.min() << " and " << zero.max() << "?: " << pass<int>(intval, zero) << "\n"; os << "\n"; } int main() { std::fstream fs("output.txt"); std::ostream& os = fs; // or std::cout os << std::boolalpha; check_guardbands(os); check_singlesided(os); check_pass(os); fs.close(); return 0; } The output is: > <!-- language: lang-none --> > > Isc guardbands: 0 and 0 > Isc min/max: 10 and 35 > New Isc guardbands: 1 and 1 > New Isc min/max: 11 and 34 > Isc typical: 25 > > CMRR (int type, no max): > minimum = 80, typical = 95, maximum = 2147483647 > > Power consumption, ambient (int type, no min): > minimum = -2147483648, typical = 80, maximum = 150 > > Power consumption, hot (unsigned int type, no min): > minimum = 0, maximum = 135 > > Isupply (double type, no minimum): > minimum = -1.79769e+308, typical = 1.7, maximum = 2.8 > > Testing pass(): > 38 is between 10 and 40?: true > 38 is between 10 and 35?: false > 5 is between 10 and 35?: false > 10 is between 10 and 35?: true > 2.81 is between -1.79769e+308 and 2.8?: false > 0.1 is between -1.79769e+308 and 2.8?: true > 0 is between -1.79769e+308 and 2.8?: true > -1000 is between -1.79769e+308 and 2.8?: true > 80 is between 80 and 2147483647?: true > 0.1 (double cast to int) is between 0 and 0?: true > 0 (double cast to int) is between 0 and 0?: true > 0 (int type) is between 0 and 0?: true Some specific questions: 1. Any suggestions for better names? I struggled to come up with good names, especially for `Range` and `Parameter`, and I'm still not totally happy with what I came up with. I also considered `Interval`, `Limit`, or `Limits` for `Range`, and `Specification` for `Parameter`. 2. Was my decision to make `lowerlimit` and `upperlimit` public the correct one? 3. Did I handle the implementation of `numeric_limits<T>::lowest()` correctly? [1]: https://i.sstatic.net/YfR20.png