假设我有一个接受64位整数的函数,我想用一个double
来调用它,这个double
可以是任意的数值(也就是说,它的大小可能非常大,甚至是无穷大):
void DoSomething(int64_t x);
double d = [...];
DoSomething(d);
C++11标准中[conv.fpint]的第1段是这样说的:
浮点型的纯右值可以转换为整型的纯右值。转换截断;也就是说,小数部分被丢弃。2如果被截断的值不能在目标类型中表示,则该行为是未定义的。
因此,d
的很多值会导致未定义的行为,我希望转换为saturate,这样大于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);
但是,标准中的下一段是这样说的:
整数类型或无作用域枚举类型的纯右值可以转换为浮点类型的纯右值。如果可能,结果将是精确的。如果要转换的值在可以表示的值范围内,但无法精确表示该值,则将由实现定义选择下一个较低或较高的可表示值。
所以clamped
可能仍然是kint64max + 1
,行为可能仍然没有定义。
什么是最简单的便携方式来做我正在寻找的?加分,如果它也优雅地处理NaN
的。
更新:更准确地说,我希望解决此问题的int64_t SafeCast(double)
函数满足以下条件:
1.对于任何双精度d
,调用SafeCast(d)
不会执行标准中未定义的行为,也不会引发异常或中止。
1.对于[-2^63, 2^63)
,SafeCast(d) == static_cast<int64_t>(d)
范围内的任何双精度值d
。也就是说,SafeCast
符合C++的转换规则,无论后者定义在何处。
1.对于任何双d >= 2^63
和SafeCast(d) == kint64max
。
1.对于任何双d < -2^63
和SafeCast(d) == kint64min
。
我怀疑这里真正的困难是弄清楚d
是否在[-2^63, 2^63)
的范围内。正如在问题和其他答案的注解中所讨论的,我认为使用kint64max
到double
的强制转换来测试上限是不可行的,因为行为未定义。使用std::pow(2, 63)
可能更有希望。但我不知道这是否能保证正好是2^63
5条答案
按热度按时间a64a0gku1#
事实证明,这比我想象的要简单得多。感谢Michael O'Reilly提出了这个解决方案的基本思想。
问题的核心是判断截断的双精度型是否可以表示为
int64_t
,使用std::frexp
可以很容易地做到这一点:a11xaf1n2#
这里有一个不符合所有标准的解决方案,沿着原因分析。请参阅the accepted answer以获得更好的答案。
我相信这可以避免未定义的行为。在范围检查中将整数转换为双精度型没有什么需要警惕的。假设转换不可表示整数的方式是合理的(特别是Map是单调的),那么在范围检查结束时,我们可以确定
d
在[-2^63, 2^63)
中,这是函数末尾隐式转换所需要的。我也确信这会正确地钳制超出范围的值。
问题是我的问题的更新中的标准#2。考虑
kint64max
不能表示为双精度型,但kint64max - 1
可以的实现。此外,假设这是一个将kint64max
转换为双精度型会产生下一个较低的可表示值的实现。例如kint64max - 1
,令d
为2^63 - 2(即kint64max - 1
),则SafeCast(d)
为kint64max
,因为范围检查将kint64max
转换为双精度,得到的值等于d
,但static_cast<int64_t>(d)
为kint64max - 1
。尽管我可能尝试过,但我找不到解决这个问题的方法。我甚至不能编写一个检查我的标准的单元测试,而单元测试不执行未定义的行为。我觉得这里有一个更深层次的教训要学习--关于不可能检测系统中的一个操作是否会在系统本身内部导致未定义的行为,而不导致未定义的行为。
7vux5j2d3#
这里有一个不使用
std::frexp
的解决方案,它利用uint64_t
来处理棘手的情况。rn0zuynd4#
不如这样:
我认为这考虑到了所有的边缘情况,如果
d < (double)kint64max
,那么(exact)d <= (exact)kint64max
,证明通过(double)kint64max
是下一个更高或更低的可表示值这一事实的矛盾来进行。yrefmtwq5#
boost::numeric_cast
,就是这样。http://www.boost.org/doc/libs/1_56_0/libs/numeric/conversion/doc/html/boost_numericconversion/improved_numeric_cast__.html