jac*_*bsa 13 c++ type-conversion undefined-behavior language-lawyer
假设我有一个接受64位整数的函数,我想用一个double带有任意数值的函数调用它(即它的幅度非常大,甚至无限大):
void DoSomething(int64_t x);
double d = [...];
DoSomething(d);
Run Code Online (Sandbox Code Playgroud)
C++ 11标准中[conv.fpint]的第1段说明了这一点:
可以将浮点类型的prvalue转换为整数类型的prvalue.转换转发; 也就是说,丢弃小数部分.如果截断的值无法在目标类型中表示,则行为未定义.
因此,d上面有许多值会导致未定义的行为.我希望转换为饱和,因此大于std::numeric_limits<int64_t>::max()(kint64max在下面称为)的值
(包括无穷大)将成为该值,并且与最小可表示值类似.这似乎是一种自然的方法:
double clamped = std::min(d, static_cast<double>(kint64max));
clamped = std::max(clamped, static_cast<double>(kint64min));
DoSomething(clamped);
Run Code Online (Sandbox Code Playgroud)
但是,标准的下一段说明了这一点:
可以将整数类型或无范围枚举类型的prvalue转换为浮点类型的prvalue.如果可能,结果是准确的.如果要转换的值在可以表示的值范围内,但该值无法准确表示,则它是实现定义的下一个较低或较高可表示值的选择.
所以clamped可能仍然存在kint64max + 1,行为可能仍未定义.
什么是最简单的便携式方式来做我正在寻找的东西?奖励积分,如果它也优雅地处理NaNs.
更新:更确切地说,我希望以下内容对于int64_t SafeCast(double)解决此问题的
函数都是如此:
对于任何double d,调用SafeCast(d)不会根据标准执行未定义的行为,也不会抛出异常或以其他方式中止.
对于d范围内的任何双倍[-2^63, 2^63),
SafeCast(d) == static_cast<int64_t>(d).也就是说,SafeCast在定义后者的任何地方都同意C++的转换规则.
任何双d >= 2^63,SafeCast(d) == kint64max.
任何双d < -2^63,SafeCast(d) == kint64min.
我怀疑这里的真正困难在于弄清楚是否d在范围内[-2^63, 2^63).正如在问题和对其他答案的评论中所讨论的那样,我认为使用kint64maxto double来测试上限是由于未定义的行为而导致的.它可能更有希望使用std::pow(2, 63),但我不知道这是否保证正好是2 ^ 63.
事实证明,这比我想象的要简单。感谢 Michael O'Reilly 提供此解决方案的基本思想。
问题的核心是弄清楚截断的双精度值是否可以表示为int64_t. 您可以使用以下方法轻松完成此操作std::frexp:
#include <cmath>
#include <limits>
static constexpr int64_t kint64min = std::numeric_limits<int64_t>::min();
static constexpr int64_t kint64max = std::numeric_limits<int64_t>::max();
int64_t SafeCast(double d) {
// We must special-case NaN, for which the logic below doesn't work.
if (std::isnan(d)) {
return 0;
}
// Find that exponent exp such that
// d == x * 2^exp
// for some x with abs(x) in [0.5, 1.0). Note that this implies that the
// magnitude of d is strictly less than 2^exp.
//
// If d is infinite, the call to std::frexp is legal but the contents of exp
// are unspecified.
int exp;
std::frexp(d, &exp);
// If the magnitude of d is strictly less than 2^63, the truncated version
// of d is guaranteed to be representable. The only representable integer
// for which this is not the case is kint64min, but it is covered by the
// logic below.
if (std::isfinite(d) && exp <= 63) {
return d;
}
// Handle infinities and finite numbers with magnitude >= 2^63.
return std::signbit(d) ? kint64min : kint64max;
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
1438 次 |
| 最近记录: |