在C中将双精度转换为整数时处理溢出

时间:2009-02-08 17:27:52

标签: c++ c casting floating-point

今天,我注意到当我将一个大于最大可能整数的double转换为整数时,我得到-2147483648。类似地,当我转换一个小于最小可能整数的double时,我也得到-2147483648。

是否为所有平台定义了此行为?
在/溢出下检测此问题的最佳方法是什么?在演示之前将if语句放入min和max int是最佳解决方案吗?

9 个答案:

答案 0 :(得分:15)

将浮点数转换为整数时,溢出会导致未定义的行为。从C99规范, 6.3.1.4实数浮动和整数

部分
  

当实数浮动类型的有限值被转换为除_Bool以外的整数类型时,小数部分被丢弃(即,该值被截断为零)。如果整数部分的值不能用整数类型表示,则行为是未定义的。

您必须手动检查范围,但不要使用

之类的代码
// DON'T use code like this!
if (my_double > INT_MAX || my_double < INT_MIN)
    printf("Overflow!");

INT_MAX是一个整数常量,可能没有精确的浮点表示。与float相比,它可以舍入到最接近的更高或最接近的可表示浮点值(这是实现定义的)。例如,对于64位整数,INT_MAX2^63 - 1,通常会舍入为2^63,因此检查基本上为my_double > INT_MAX + 1。如果my_double等于2^63,则无法检测到溢出。

例如在Linux上使用gcc 4.9.1,以下程序

#include <math.h>
#include <stdint.h>
#include <stdio.h>

int main() {
    double  d = pow(2, 63);
    int64_t i = INT64_MAX;
    printf("%f > %lld is %s\n", d, i, d > i ? "true" : "false");
    return 0;
}

打印

9223372036854775808.000000 > 9223372036854775807 is false

如果你事先不知道整数和双重类型的限制和内部表示,那么很难做到这一点。但是,例如,如果从double转换为int64_t,则可以使用精确双精度的浮点常量(假设两个补码和IEEE双精度数):

if (!(my_double >= -9223372036854775808.0   // -2^63
   && my_double <   9223372036854775808.0)  // 2^63
) {
    // Handle overflow.
}

构造!(A && B)也正确处理NaN。 int的便携,安全但略微不准确的版本是:

if (!(my_double > INT_MIN && my_double < INT_MAX)) {
    // Handle overflow.
}

这样做会引起警告,并会错误地拒绝等于INT_MININT_MAX的值。但对于大多数应用程序,这应该没问题。

答案 1 :(得分:12)

limits.h具有整数数据类型的最大和最小可能值的常量,您可以在转换之前检查双变量,例如

if (my_double > nextafter(INT_MAX, 0) || my_double < nextafter(INT_MIN, 0))
    printf("Overflow!");
else
    my_int = (int)my_double;

编辑:nextafter()将解决nwellnhof提到的问题

答案 2 :(得分:11)

回答你的问题:当你抛出范围浮动时的行为是未定义的或特定于实现的。

根据经验说明:我曾经在MIPS64系统上工作,根本没有实现这些类型的演员表。 CPU没有做出确定性的事情,而是抛出了CPU异常。应该模拟转换的异常处理程序,而不对结果做任何事情。

我最终得到了随机整数。猜猜追溯一个错误到这个原因需要多长时间。 :-)

如果您不确定该号码是否超出有效范围,您最好自行进行范围检查。

答案 3 :(得分:4)

C ++的可移植方式是使用SafeInt类:

http://www.codeplex.com/SafeInt

该实现将允许对C ++数字类型(包括强制转换)进行正常的加/减/等。每当检测到溢出情况时,它将抛出异常。

SafeInt<int> s1 = INT_MAX;
SafeInt<int> s2 = 42;
SafeInt<int> s3 = s1 + s2;  // throws

我强烈建议在溢出是一个重要场景的任何地方使用这个类。它使得很难避免无声地溢出。如果存在溢出的恢复方案,只需捕获SafeIntException并根据需要进行恢复。

SafeInt现在适用于GCC和Visual Studio

答案 4 :(得分:3)

  

在/溢出下检测此问题的最佳方法是什么?

double附近的截断INT_MIN,INT_MAX完全限制进行比较。

技巧完全将基于INT_MIN,INT_MAX的限制转换为double值。 double可能不完全代表INT_MAX,因为int 中的位数可能超过浮点数的精度。在这种情况下,INT_MAXdouble的转换会受到舍入的影响。 INT_MAX之后的数字是2的幂,当然可以表示为double2.0*(INT_MAX/2 + 1)生成的整数大于INT_MAX

同样适用于非2s补码机器上的INT_MIN

INT_MAX始终是2的力量 - 1 始终INT_MIN
    -INT_MAX(不是2&#39;补充)或
    -INT_MAX-1(2&#39;补充)

int double_to_int(double x) {
  x = trunc(x);
  if (x >= 2.0*(INT_MAX/2 + 1)) Handle_Overflow();
  #if -INT_MAX == INT_MIN
  if (x <= 2.0*(INT_MIN/2 - 1)) Handle_Underflow();
  #else
  if (x < INT_MIN) Handle_Underflow();
  #endif
  return (int) x;
}

要检测NaN而不使用trunc()

#define DBL_INT_MAXP1 (2.0*(INT_MAX/2+1)) 
#define DBL_INT_MINM1 (2.0*(INT_MIN/2-1)) 

int double_to_int(double x) {
  if (x < DBL_INT_MAXP1) {
    #if -INT_MAX == INT_MIN
    if (x > DBL_INT_MINM1) {
      return (int) x;
    }
    #else
    if (ceil(x) >= INT_MIN) {
      return (int) x;
    }
    #endif 
    Handle_Underflow();
  } else if (x > 0) {
    Handle_Overflow();
  } else {
    Handle_NaN();
  }
}

答案 5 :(得分:2)

另一种选择是使用boost::numeric_cast,它允许在数字类型之间进行任意转换。它会在转换数字类型时检测范围丢失,并在无法保留范围时抛出异常。

上面引用的网站还提供了一个小例子,可以快速概述如何使用这个模板。

当然,这不再是普通的C; - )

答案 6 :(得分:2)

我们遇到了同样的问题。如:

double d = 9223372036854775807L;
int i = (int)d;

在Linux /窗口中,i = -2147483648。但在AIX 5.3中i = 2147483647。

如果双倍超出整数范围。

  • Linux /窗口始终返回INT_MIN。
  • 如果double为postive,AIX将返回INT_MAX,将返回INT_MIN double是negetive。

答案 7 :(得分:0)

我不确定这一点,但我认为有可能为欠/溢出“打开”浮点异常...看看这个Dealing with Floating-point Exceptions in MSVC7\8所以你可能有一个替代if /别的检查。

答案 8 :(得分:-1)

我无法确定它是否为所有平台定义,但这几乎就是我使用的每个平台上发生的事情。除此之外,根据我的经验,它滚动。也就是说,如果double的值是INT_MAX + 2,那么当演员的结果最终为INT_MIN + 2时。

至于处理它的最好方法,我真的不确定。我自己也遇到了这个问题,并且尚未找到一种优雅的方式来处理它。我相信有人会回应,可以帮助我们。