I'm looking for a C++ feature/code, which I feel is very basic and demanded for generic programming: an ability to tell if some value of given numeric type (integer or float) is exactly representable by another given numeric type.
Long story short, I have a number of template classes, which operate on arbitrary numeric types and they have to interoperate properly. For instance, a memory stream may work with 32bit size type, but file stream has 64bit size type and passing data between them may result in overflow. Another example is geometry classes, one parametrized to work with 64bit floats, another parametrized to work with 64bit integers: passing data between them may result both in rounding errors (integer can't represent fractions) and overflow (integer has bigger value range than same-sized float).
So I wonder if should do my own implementation, or a solution is readily available (unfortunately, I can't use heavyweight libraries like Boost)?
Update: while typing my question, I decided to roll out my own implementation:
#include <limits>
#include <array>
enum class NumberClass {
UNKNOWN, INTEGER, FLOAT
};
template <typename T> struct NumberClassResolver {
static const NumberClass value = std::numeric_limits<T>::is_integer ? NumberClass::INTEGER : (std::numeric_limits<T>::is_iec559 ? NumberClass::FLOAT : NumberClass::UNKNOWN);
};
template <typename From, typename To> struct StaticIntegerCastCheck {
static const bool value = std::is_same<From, To>::value || (sizeof(From) < sizeof(To)) || ((std::numeric_limits<From>::is_signed == std::numeric_limits<To>::is_signed) && (sizeof(From) <= sizeof(To)));
};
template <typename From, typename To> struct StaticFloatCastCheck {
static const bool value = std::is_same<From, To>::value || (sizeof(From) < sizeof(To)); // NOTE Here we rely on the fact, that floats are IEEE-754 compliant and bigger ones has both bigger significand and exponent
};
template <NumberClass FromClass, NumberClass ToClass, typename From, typename To> struct StaticNumberCastCheckHelper;
template <typename From, typename To> struct StaticNumberCastCheckHelper<NumberClass::INTEGER, NumberClass::INTEGER, From, To> {
static const bool value = StaticIntegerCastCheck<From, To>::value;
};
template <typename From, typename To> struct StaticNumberCastCheckHelper<NumberClass::INTEGER, NumberClass::FLOAT, From, To> {
static const bool value = sizeof(To) > sizeof(From); // NOTE Here we rely on assumption, that sizes are POTs and significand part of a float never takes up less that half of it's size
};
template <typename From, typename To> struct StaticNumberCastCheckHelper<NumberClass::FLOAT, NumberClass::INTEGER, From, To> {
static const bool value = false;
};
template <typename From, typename To> struct StaticNumberCastCheckHelper<NumberClass::FLOAT, NumberClass::FLOAT, From, To> {
static const bool value = StaticFloatCastCheck<From, To>::value;
};
template <typename From, typename To> struct StaticNumberCastCheck {
static const bool value = StaticNumberCastCheckHelper<NumberClassResolver<From>::value, NumberClassResolver<To>::value, From, To>::value;
};
template <bool isFromSigned, bool isToSigned, typename From, typename To> struct RuntimeIntegerCastCheckHelper {
static bool check(From value) {
return (value >= std::numeric_limits<To>::min()) && (value <= std::numeric_limits<To>::max()); // NOTE Compiler must eliminate comparisson to zero for unsigned types
}
};
template <bool isStaticallySafeCastable, NumberClass FromClass, NumberClass ToClass, typename From, typename To> struct RuntimeNumberCastCheckHelper {
static bool check(From value) {
return false;
}
};
template <NumberClass FromClass, NumberClass ToClass, typename From, typename To> struct RuntimeNumberCastCheckHelper<true, FromClass, ToClass, From, To> {
static bool check(From value) {
return true;
}
};
template <typename From, typename To> struct RuntimeNumberCastCheckHelper<false, NumberClass::INTEGER, NumberClass::INTEGER, From, To> {
static bool check(From value) {
return RuntimeIntegerCastCheckHelper<std::numeric_limits<From>::is_signed, std::numeric_limits<To>::is_signed, From, To>::check(value);
}
};
template <typename From, typename To> struct RuntimeNumberCastCheckHelper<false, NumberClass::FLOAT, NumberClass::FLOAT, From, To> {
static bool check(From value) {
To toValue = static_cast<To>(value);
From fromValue = static_cast<From>(toValue);
return value == fromValue;
}
};
template <typename From, typename To> struct RuntimeNumberCastCheckHelper<false, NumberClass::INTEGER, NumberClass::FLOAT, From, To> {
static bool check(From value) {
To toValue = static_cast<To>(value);
From fromValue = static_cast<From>(toValue);
return value == fromValue;
}
};
template <typename From, typename To> struct RuntimeNumberCastCheckHelper<false, NumberClass::FLOAT, NumberClass::INTEGER, From, To> {
static bool check(From value) {
To toValue = static_cast<To>(value);
From fromValue = static_cast<From>(toValue);
return value == fromValue;
}
};
template <typename From, typename To> struct RuntimeNumberCastCheck {
static bool check(From value) {
return RuntimeNumberCastCheckHelper<StaticNumberCastCheck<From, To>::value, NumberClassResolver<From>::value, NumberClassResolver<To>::value, From, To>::check(value);
}
};
Intended usage:
StaticNumberCastCheck<uint8_t, float>::value; // can we safely cast all 8-bit unsigned integers to float?
RuntimeNumberCastCheck<float, uint8_t>::check(42); // can we safely cast 42 from float to 8-bit unsigned integer?
It's based on some broad assumptions, so I also would be glad to hear if I'm missing something here (false positives, optimizations, etc).
Aucun commentaire:
Enregistrer un commentaire