如何将任意double转换为整数,同时避免未定义的行为? [英] How do I convert an arbitrary double to an integer while avoiding undefined behavior?

查看:126
本文介绍了如何将任意double转换为整数,同时避免未定义的行为?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

假设我有一个函数接受一个64位的整数,我想调用
它带有任意数值的 double 即,它可能在
幅度非常大,甚至是无穷大):

Let's say I've got a function that accepts a 64-bit integer, and I want to call it with a double with arbitrary numeric value (i.e. it may be very large in magnitude, or even infinite):

void DoSomething(int64_t x);

double d = [...];
DoSomething(d);

C ++ 11标准中[conv.fpint]的段落1说明:

Paragraph 1 of [conv.fpint] in the C++11 standard says this:


浮点类型的prvalue可以转换为
整数类型的prvalue。转换trun- cates;即,小数部分
被丢弃。

A prvalue of a floating point type can be converted to a prvalue of an integer type. The conversion trun- cates; that is, the fractional part is discarded. The behavior is undefined if the truncated value cannot be represented in the destination type.

因此,有很多值 d 将导致未定义的
行为。我想转换为饱和,以使值大于
std :: numeric_limits (称为 kint64max ),包括
无穷大,成为该值,类似地,最小可表示的
值。这似乎是一种自然的方法:

Therefore there are many values of d above that will cause undefined behavior. I would like conversion to saturate, so that values greater than std::numeric_limits<int64_t>::max() (called kint64max below), including infinity, become that value, and similarly with the minimum representable value. This seems the natural approach:

double clamped = std::min(d, static_cast<double>(kint64max));
clamped = std::max(clamped, static_cast<double>(kint64min));
DoSomething(clamped);

但是,标准中的下一段说:

But, the next paragraph in the standard says this:


整数类型或无范围枚举类型的prval值可以是
转换为浮点类型的prvalue。如果可能,结果是确切的
。如果要转换的值在可以表示的值
的范围内,但该值不能被准确表示,
它是下一个较低或
的可实现定义的选择

A prvalue of an integer type or of an unscoped enumeration type can be converted to a prvalue of a floating point type. The result is exact if possible. If the value being converted is in the range of values that can be represented but the value cannot be represented exactly, it is an implementation-defined choice of either the next lower or higher representable value.

因此夹紧可能仍然是 kint64max + 1 ,并且行为可能仍然是
未定义。

So clamped may still wind up being kint64max + 1, and behavior may still be undefined.

寻找?如果
它也会优雅地处理 NaN

更新 :更准确地说,我想要一个真实的
int64_t SafeCast(double)函数来解决这个问题:

Update: To be more precise, I would like the following to all be true of an int64_t SafeCast(double) function that solves this problem:


  1. 对于任何双重 d ,调用 SafeCast / code>根据标准不执行未定义的行为
    ,也不会抛出异常或中止。

  1. For any double d, calling SafeCast(d) does not perform undefined behavior according to the standard, nor does it throw an exception or otherwise abort.

[ - 2 ^ 63,2 ^ 63)
<$ c $ <$ c> SafeCast(d)== static_cast< int64_t>(d)。也就是说, SafeCast 同意C ++的
转换规则,只要定义后者。

For any double d in the range [-2^63, 2^63), SafeCast(d) == static_cast<int64_t>(d). That is, SafeCast agrees with C++'s conversion rules wherever the latter is defined.

对于任何双 d> = 2 ^ 63 SafeCast(d)== kint64max

对于任何double d < -2 ^ 63 SafeCast(d)== kint64min

For any double d < -2^63, SafeCast(d) == kint64min.

我怀疑这里真正的困难在于判断 d 是否在
范围 -2 ^ 63,2 ^ 63)。正如在问题和对其他
答案的评论中讨论的,我想使用 kint64max double 测试上层
bound是由于未定义的行为而导致的非初始化。它可能更有前途
使用 std :: pow(2,63),但我不知道这是否保证是正确的
2 ^ 63。

I suspect the true difficulty here is in figuring out whether d is in the range [-2^63, 2^63). As discussed in the question and in comments to other answers, I think using a cast of kint64max to double to test for the upper bound is a non-starter due to undefined behavior. It may be more promising to use std::pow(2, 63), but I don't know whether this is guaranteed to be exactly 2^63.

推荐答案

事实证明,这比我想象的要简单。感谢Michael O'Reilly
这个解决方案的基本想法。

It turns out this is simpler to do than I thought. Thanks to Michael O'Reilly for the basic idea of this solution.

问题的核心在于找出截断的double是否为
表示为 int64_t 。您可以使用 std :: frexp

The heart of the matter is figuring out whether the truncated double will be representable as an int64_t. You can do this easily using 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;
}

这篇关于如何将任意double转换为整数,同时避免未定义的行为?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆