如果 boost::multiprecision::cpp_dec_floa 可以 "safely" 转换为 int、double、float,如何 check/and 转换?

how to check/and convert if a boost::multiprecision::cpp_dec_floa can be "safely" converted to int,double,float?

问题:

我想检查 boost::multiprecision::cpp_dec_float<100> 值是否可以安全 转换成 float/double 或 (u)int8,16,32,... 没有范围问题

背景:

cpp_dec_float 来自 CSV 导入(我无法更改它 - 这不是我的代码),其常量值“应该”始终适合(依赖于列)到 int8、int16、.. .,double 或 float

某种:

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)

转换为 float/double 时可以降低精度 但整数部分值应该适合

对于整数,如果小数部分 != 0 则不起作用 或者积分部分不合适

我尝试使用 .extract_signed_long_long 和 extract_part 但那可能 如果值太大并且似乎没有 convert_to 可用的助手

则失败

如何测试转换是否可以正常工作?

更新

如何检查小数部分是否为 0 - 这种方式?

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();

我想这会是一个好的开始:

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>();
}

这里有一些测试来验证您的要求:

Live On Coliru

#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);
    }
}

版画

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

更新

我有点错过了检查小数部分的要求。在我看来,四舍五入为整数“只是”精度损失,因此无需担心。

为了同时检测小数部分,我建议使用最简单的方法:

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;
}

使用后端类型的实现细节可能会稍微加快速度,但代价是减少 generic/potentially 更容易出错。

使用它扩展测试(选择相对于源值和十进制浮点类型的有效数字的“噪声”值):

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";
}

仍然打印相同的输出:Live On Coliru