在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_MAX
为2^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_MIN
或INT_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_MAX
到double
的转换会受到舍入的影响。 INT_MAX
之后的INT_MAX
是2的幂,并且当然可以表示为double
。 2.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时。
至于处理它的最好方法,我真的不确定。 我自己也遇到了这个问题,并且尚未找到一种优雅的方式来处理它。 我相信有人会回应,可以帮助我们。