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

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

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

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提到的问题

将浮点数转换为整数时,溢出会导致未定义的行为。 从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  #include  #include  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)也正确处理NaNs。 适用于int的便携式,安全但略微不准确的版本是:

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

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

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

从经验上讲:我曾经在MIPS64系统上工作,根本没有实现这种类型的演员表。 CPU没有做出确定性的事情,而是抛出了CPUexception。 应该模拟转换的exception处理程序在不对结果做任何事情的情况下返回。

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

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

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

http://www.codeplex.com/SafeInt

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

 SafeInt s1 = INT_MAX; SafeInt s2 = 42; SafeInt s3 = s1 + s2; // throws 

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

SafeInt现在可以在GCC和Visual Studio上运行

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

将截断的double INT_MIN,INT_MAX限制与INT_MIN,INT_MAX附近的精确限制进行比较。

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

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

INT_MAX始终是2 – 1的幂。
INT_MIN总是:
-INT_MAX (不是2的补码)或
-INT_MAX-1 (2的补码)

 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(); } } 

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

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

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

如果双数超出整数范围。

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

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

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

当然,这不再是普通的C 😉

我不确定这一点,但我认为有可能为欠/溢出“打开”浮点exception…看看这个处理MSVC7 \ 8中的浮点exception所以你可能有一个替代if / else检查。

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

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