我们说我有一个接受64位整数的函数,我想打电话
它带有double
具有任意数值(即它可能非常大)
幅度,甚至是无限的):
void DoSomething(int64_t x);
double d = [...];
DoSomething(d);
C ++ 11标准中[conv.fpint]的第1段说明了这一点:
浮点类型的prvalue可以转换为a的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);
但是,标准的下一段说明了这一点:
整数类型或无范围枚举类型的prvalue可以是 转换为浮点类型的prvalue。结果是准确的 如果可能的话。如果转换的值在值的范围内 可以表示,但价值不能准确表示, 它是一个实现定义的选择下一个较低或 更高的代表值。
所以clamped
可能仍然是kint64max + 1
,行为可能仍然存在
未定义。
什么是最简单的便携式方式来做我正在寻找的东西?奖金积分如果
它还优雅地处理NaN
s。
更新:更确切地说,我希望以下内容都适用于
解决此问题的int64_t SafeCast(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)
。正如问题和对其他人的评论中所讨论的那样
答案,我认为使用kint64max
到double
的演员来测试鞋面
由于未定义的行为,bound是非启动的。它可能更有希望
使用std::pow(2, 63)
,但我不知道这是否保证完全正确
2 ^ 63
答案 0 :(得分:2)
事实证明这比我想象的要简单。感谢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;
}
答案 1 :(得分:1)
这是一个不符合所有标准的解决方案,以及分析 为什么不。有关更好的答案,请参阅the accepted answer。
// Define constants from the question.
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) {
// Handle NaN specially.
if (std::isnan(d)) return 0;
// Handle out of range below.
if (d <= kint64min) return kint64min;
// Handle out of range above.
if (d >= kint64max) return kint64max;
// At this point we know that d is in range.
return d;
}
我相信这可以避免未定义的行为。没有什么可以警惕的
在范围检查中将整数转换为双精度数。假设道路上的理智
转换不可表示的整数(特别是映射
是单调的),到范围检查过去时,我们可以确定d
在[-2^63, 2^63)
中,根据隐含的强制转换的要求
功能
我也相信这会正确地限制超出范围值。
问题是从我的问题更新的标准#2。考虑一下
kint64max
无法表示为double的实现,但是
kint64max - 1
是。此外,假设这是一个实现
将kint64max
转换为double会产生下一个较低的可表示值,
即kint64max - 1
。设d
为2 ^ 63 - 2(即kint64max - 1
)。然后
SafeCast(d)
为kint64max
,因为范围检查会将kint64max
转换为
一个double,产生一个等于d
的值。但是static_cast<int64_t>(d)
是
kint64max - 1
。
尽我所能,我找不到解决这个问题的方法。我甚至不能写一个 检查我的标准的单元测试,没有单元测试执行undefined 行为。我觉得这里有更深刻的教训 - 有些东西 关于检测系统中的动作是否会导致的不可能性 来自系统内部的未定义行为,不会导致未定义 行为。
答案 2 :(得分:-1)
怎么样:
constexpr uint64_t weird_high_limit = (double)kint64max == (double)(kint64max-1);
int64_t clamped = (d >= weird_high_limit + kint64max)? kint64max: (d <= kint64min)? kint64min: int64_t(d);
我认为这可以解决所有边缘情况。如果是d < (double)kint64max
,那么(exact)d <= (exact)kint64max
。证据的进行与(double)kint64max
是下一个更高或更低的可表示值的事实相矛盾。
答案 3 :(得分:-1)