C++语言提供了将浮点数转换为整数的功能。但是,如果要转换的浮点数超出了整数的表示范围,会得到怎样的结果呢?这种情况在标准中是未定义行为(隐式转换 – cppreference.com)。例如,在x64架构上运行以下测试代码:
std::cout
<< static_cast<int32_t>(std::pow(2, 31))
<< std::endl;
运行结果为-2147483648,与我们的原始浮点数的数值差了十万八千里。为了避免这种现象发生,我们似乎可以在进行转换前先使用std::clamp
函数限制浮点数的范围。然而这真的有用吗?让我们试试下面这段测试代码:
double source = std::pow(2, 100);
double clamped = std::clamp(source,
std::numeric_limits<int64_t>::min(),
std::numeric_limits<int64_t>::max());
int64_t converted = clamped;
std::cout << converted << std::endl;
不出意外的话,是要发生意外了。输出结果为-9223372036854775808。为什么会发生这种事情呢?在调试器中不难发现,变量clamped
实际值为9223372036854775808,比int64_t
类型可以表示的最大值9223372036854775807恰好大了1。这是因为double
类型的精度并不足以准确存储这个最大值(通常来说如此,实际上C++语言标准并未规定浮点数类型的具体表示形式和精度),在x64架构上将它转换为double
类型时会自动舍入到最近的double
值,而这个近似值恰好比原本的值大了一点点,超出了int64_t
的表示范围。于是,把clamped
转换为int64_t
时就出现了未定义行为。
C++语言标准规定,在将浮点数转换为整数时,会直接丢弃小数部分(隐式转换 – cppreference.com)。不难发现,无论浮点类型精度如何,只要数值小于263且大于等于-263,在舍弃小数部分之后就在int64_t
的表示范围内,而常见的计算机架构中采用二进制浮点数(即:std::numeric_limits<double>::radix == 2
),在不溢出的前提下能够准确表示2的幂,所以我们可以利用这段示例代码来安全地将double
类型浮点数转换为最接近的int64_t
数值:
std::optional<int64_t> saturated_to_int64(double source)
{
if (std::isnan(source))
{
return std::nullopt;
}
else if (source < -std::exp2(63))
{
return std::numeric_limits<int64_t>::min();
}
else if (source >= std::exp2(63))
{
return std::numeric_limits<int64_t>::max();
}
else
{
return source;
}
}
或者,我们还可以使用一种与double
类型的具体表示完全无关的方法:利用标准库llrint
函数以及浮点异常功能。llrint
函数调用时,若参数超出了long long
类型的表示范围,则会引发FE_INVALID
浮点异常(std::rint – cppreference.com)。示例代码:
std::optional<int64_t> saturated_to_int64(double source)
{
static_assert(std::is_same_v<long long, int64_t>);
if (std::isnan(source))
{
return std::nullopt;
}
fenv_t fenv;
std::fegetenv(&fenv);
std::feclearexcept(FE_ALL_EXCEPT);
std::fesetround(FE_TOWARDZERO);
int64_t result = std::llrint(source);
if (std::fetestexcept(FE_INVALID))
{
if (source > 0)
{
result = std::numeric_limits<int64_t>::max();
}
else
{
result = std::numeric_limits<int64_t>::min();
}
}
std::fesetenv(&fenv);
return result;
}
该方法不需要假设double
类型的具体精度和二进制表示形式,可以有效防止由于超出范围导致的未定义行为。
留言
有想法?请给我们留言!您的留言不会直接显示在网站内。