Question:
I want to check if a boost::multiprecision::cpp_dec_float<100> value can be safely converted into a float/double or (u)int8,16,32,... without range problems
Background:
The cpp_dec_float is comming from an CSV import (i can't change that - its not my code) with constant values that "should" always fit (column dependent) into int8, int16, ...,double or float
some sort of:
bool can_be_converted_to_double(const cpp_dec_float<100>& ft)
double converted_to_double(const cpp_dec_float<100>& ft)
bool can_be_converted_to_int8(const cpp_dec_float<100>& ft)
int8_t converted_to_int8(const cpp_dec_float<100>& ft)
bool can_be_converted_to_uint8(const cpp_dec_float<100>& ft)
uint8_t converted_to_uint8(const cpp_dec_float<100>& ft)
its ok to loose precision when converting to float/double but the integral part value should fit
for integers it should not work if fractional part != 0 or the integral part does not fit
i tried to use .extract_signed_long_long and extract_part but that could fail if the values too large and there seem to be no convert_to helper available
how to test if the conversion can work and also do it?
Update
how to check if the fractional part is 0 - this way?
cpp_dec_float_100 value( "123.1" );
cpp_dec_float_100 int_part = value.backend().extract_integer_part();
cpp_dec_float_100 fractional_part = value - int_part;
bool has_fractional_zero = fractional_part.is_zero();
I suppose this would be a good start:
template <typename To, typename From>
bool can_be_converted_to(From const& value) {
return value >= std::numeric_limits<To>::min() &&
value <= std::numeric_limits<To>::max();
}
template <typename To, typename From> //
To converted_to(From const& value) {
if (not can_be_converted_to<To, From>(value))
throw std::range_error(__PRETTY_FUNCTION__);
return value.template convert_to<To>();
}
Here's some tests to verify your requirements:
#include <boost/multiprecision/cpp_dec_float.hpp>
#include <boost/core/demangle.hpp> // only for test output
#include <cassert>
#include <iostream>
using boost::multiprecision::cpp_dec_float_100;
template <typename To, typename From>
bool can_be_converted_to(From const& value) {
return value >= std::numeric_limits<To>::min() &&
value <= std::numeric_limits<To>::max();
}
template <typename To, typename From> //
To converted_to(From const& value) {
if (not can_be_converted_to<To, From>(value))
throw std::range_error(__PRETTY_FUNCTION__);
return value.template convert_to<To>();
}
template <typename To, typename From> void test(From const& value) {
auto display = [](auto v) {
if constexpr (sizeof(v)==1)
// avoid int8_t stupidly printing as char
return static_cast<int>(v);
else
return v;
};
try {
std::cout << "To " << boost::core::demangle(typeid(To).name())
<< " from " << value << ": ";
std::cout << display(converted_to<To>(value)) << "\n";
} catch(std::range_error const&) {
std::cout << "RANGE ERROR\n";
}
}
int main() {
cpp_dec_float_100 cases[]{
1, -1,
127, -127,
128, -128,
129, -129,
255, -255,
1e5, -1e5,
1e10, -1e10,
1e100, -1e100,
1e1000l, -1e1000l,
cpp_dec_float_100("1e10000"), cpp_dec_float_100("-1e10000"),
cpp_dec_float_100(std::numeric_limits<double>::max()),
cpp_dec_float_100(std::numeric_limits<long double>::max()),
cpp_dec_float_100(std::numeric_limits<double>::infinity()),
cpp_dec_float_100(-std::numeric_limits<double>::infinity()),
};
for (auto num : cases) test<int8_t>(num);
for (auto num : cases) test<uint8_t>(num);
for (auto num : cases) test<int32_t>(num);
for (auto num : cases) test<int64_t>(num);
for (auto num : cases) test<float>(num);
for (auto num : cases) test<double>(num);
for (auto num : cases) test<long double>(num);
// special cases
for (auto num :
{
cpp_dec_float_100{"inf"},
cpp_dec_float_100{"-inf"},
cpp_dec_float_100{"nan"},
}) //
{
test<long double>(num);
}
}
Prints
To signed char from 1: 1
To signed char from -1: -1
To signed char from 127: 127
To signed char from -127: -127
To signed char from 128: RANGE ERROR
To signed char from -128: -128
To signed char from 129: RANGE ERROR
To signed char from -129: RANGE ERROR
To signed char from 255: RANGE ERROR
To signed char from -255: RANGE ERROR
To signed char from 100000: RANGE ERROR
To signed char from -100000: RANGE ERROR
To signed char from 1e+10: RANGE ERROR
To signed char from -1e+10: RANGE ERROR
To signed char from 1e+100: RANGE ERROR
To signed char from -1e+100: RANGE ERROR
To signed char from 1e+1000: RANGE ERROR
To signed char from -1e+1000: RANGE ERROR
To signed char from 1e+10000: RANGE ERROR
To signed char from -1e+10000: RANGE ERROR
To signed char from 1.79769e+308: RANGE ERROR
To signed char from 1.18973e+4932: RANGE ERROR
To signed char from inf: RANGE ERROR
To signed char from -inf: RANGE ERROR
To unsigned char from 1: 1
To unsigned char from -1: RANGE ERROR
To unsigned char from 127: 127
To unsigned char from -127: RANGE ERROR
To unsigned char from 128: 128
To unsigned char from -128: RANGE ERROR
To unsigned char from 129: 129
To unsigned char from -129: RANGE ERROR
To unsigned char from 255: 255
To unsigned char from -255: RANGE ERROR
To unsigned char from 100000: RANGE ERROR
To unsigned char from -100000: RANGE ERROR
To unsigned char from 1e+10: RANGE ERROR
To unsigned char from -1e+10: RANGE ERROR
To unsigned char from 1e+100: RANGE ERROR
To unsigned char from -1e+100: RANGE ERROR
To unsigned char from 1e+1000: RANGE ERROR
To unsigned char from -1e+1000: RANGE ERROR
To unsigned char from 1e+10000: RANGE ERROR
To unsigned char from -1e+10000: RANGE ERROR
To unsigned char from 1.79769e+308: RANGE ERROR
To unsigned char from 1.18973e+4932: RANGE ERROR
To unsigned char from inf: RANGE ERROR
To unsigned char from -inf: RANGE ERROR
To int from 1: 1
To int from -1: -1
To int from 127: 127
To int from -127: -127
To int from 128: 128
To int from -128: -128
To int from 129: 129
To int from -129: -129
To int from 255: 255
To int from -255: -255
To int from 100000: 100000
To int from -100000: -100000
To int from 1e+10: RANGE ERROR
To int from -1e+10: RANGE ERROR
To int from 1e+100: RANGE ERROR
To int from -1e+100: RANGE ERROR
To int from 1e+1000: RANGE ERROR
To int from -1e+1000: RANGE ERROR
To int from 1e+10000: RANGE ERROR
To int from -1e+10000: RANGE ERROR
To int from 1.79769e+308: RANGE ERROR
To int from 1.18973e+4932: RANGE ERROR
To int from inf: RANGE ERROR
To int from -inf: RANGE ERROR
To long from 1: 1
To long from -1: -1
To long from 127: 127
To long from -127: -127
To long from 128: 128
To long from -128: -128
To long from 129: 129
To long from -129: -129
To long from 255: 255
To long from -255: -255
To long from 100000: 100000
To long from -100000: -100000
To long from 1e+10: 10000000000
To long from -1e+10: -10000000000
To long from 1e+100: RANGE ERROR
To long from -1e+100: RANGE ERROR
To long from 1e+1000: RANGE ERROR
To long from -1e+1000: RANGE ERROR
To long from 1e+10000: RANGE ERROR
To long from -1e+10000: RANGE ERROR
To long from 1.79769e+308: RANGE ERROR
To long from 1.18973e+4932: RANGE ERROR
To long from inf: RANGE ERROR
To long from -inf: RANGE ERROR
To float from 1: 1
To float from -1: RANGE ERROR
To float from 127: 127
To float from -127: RANGE ERROR
To float from 128: 128
To float from -128: RANGE ERROR
To float from 129: 129
To float from -129: RANGE ERROR
To float from 255: 255
To float from -255: RANGE ERROR
To float from 100000: 100000
To float from -100000: RANGE ERROR
To float from 1e+10: 1e+10
To float from -1e+10: RANGE ERROR
To float from 1e+100: RANGE ERROR
To float from -1e+100: RANGE ERROR
To float from 1e+1000: RANGE ERROR
To float from -1e+1000: RANGE ERROR
To float from 1e+10000: RANGE ERROR
To float from -1e+10000: RANGE ERROR
To float from 1.79769e+308: RANGE ERROR
To float from 1.18973e+4932: RANGE ERROR
To float from inf: RANGE ERROR
To float from -inf: RANGE ERROR
To double from 1: 1
To double from -1: RANGE ERROR
To double from 127: 127
To double from -127: RANGE ERROR
To double from 128: 128
To double from -128: RANGE ERROR
To double from 129: 129
To double from -129: RANGE ERROR
To double from 255: 255
To double from -255: RANGE ERROR
To double from 100000: 100000
To double from -100000: RANGE ERROR
To double from 1e+10: 1e+10
To double from -1e+10: RANGE ERROR
To double from 1e+100: 1e+100
To double from -1e+100: RANGE ERROR
To double from 1e+1000: RANGE ERROR
To double from -1e+1000: RANGE ERROR
To double from 1e+10000: RANGE ERROR
To double from -1e+10000: RANGE ERROR
To double from 1.79769e+308: 1.79769e+308
To double from 1.18973e+4932: RANGE ERROR
To double from inf: RANGE ERROR
To double from -inf: RANGE ERROR
To long double from 1: 1
To long double from -1: RANGE ERROR
To long double from 127: 127
To long double from -127: RANGE ERROR
To long double from 128: 128
To long double from -128: RANGE ERROR
To long double from 129: 129
To long double from -129: RANGE ERROR
To long double from 255: 255
To long double from -255: RANGE ERROR
To long double from 100000: 100000
To long double from -100000: RANGE ERROR
To long double from 1e+10: 1e+10
To long double from -1e+10: RANGE ERROR
To long double from 1e+100: 1e+100
To long double from -1e+100: RANGE ERROR
To long double from 1e+1000: 1e+1000
To long double from -1e+1000: RANGE ERROR
To long double from 1e+10000: RANGE ERROR
To long double from -1e+10000: RANGE ERROR
To long double from 1.79769e+308: 1.79769e+308
To long double from 1.18973e+4932: 1.18973e+4932
To long double from inf: RANGE ERROR
To long double from -inf: RANGE ERROR
To long double from inf: RANGE ERROR
To long double from -inf: RANGE ERROR
To long double from nan: RANGE ERROR
I had kinda missed the requirement to check for fractional parts. In my mind, rounding to integer is "just" precision loss, so nothing to worry about.
To also detect fractional parts, I'd suggest the simplest:
template <typename To, typename From> //
To converted_to(From const& value) {
if (not can_be_converted_to<To, From>(value))
throw std::range_error(__PRETTY_FUNCTION__);
auto result = value.template convert_to<To>();
if (result != value)
throw std::range_error(__PRETTY_FUNCTION__);
return result;
}
Using implementation details of the backend type might speed it up a little, at the expense of making it less generic/potentially more error-prone.
Extendeding the tests using it (choosing a "noise" value relative to the source value and significant digits of the decimal float type):
try {
std::cout << "To " << boost::core::demangle(typeid(To).name())
<< " from " << value << ": ";
std::cout << display(converted_to<To>(value)) << "\n";
try {
From noise = value / 1e-50;
/*auto should_fail =*/converted_to<To>(From{value + noise});
std::cout << " -- WARNING: Fractional noise not detected" << std::endl;
} catch (std::range_error const&) { }
} catch (std::range_error const&) {
std::cout << "RANGE ERROR\n";
}
Still prints the same output: Live On Coliru