这种行为在所有平台上都是定义好的吗?如何检测下溢/上溢?在类型转换之前放置min和max int的if语句是最佳解决方案吗?
在将浮点数转换为整数时,溢出会导致未定义的行为。根据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
是一个整数常量,可能没有精确的浮点表示。当与浮点数进行比较时,它可能会被四舍五入为最接近的高或低可表示浮点值(这是实现定义的)。例如,对于64位整数,INT_MAX
是2^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_MIN
或INT_MAX
的值。但对于大多数应用程序来说,这应该是可以接受的。
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提到的问题
float f = INT_MAX; f++; ConvertToInt(f)
,使用您上面的限制检查,但它不会溢出。 有什么不同吗? - Pittfallfloat
(除非是非常奇特的平台,否则都使用IEEE-754浮点数)有24个有效二进制数字。因此,当您将其设置为INT_MAX时,即2³¹-1(32位平台上的INT_MAX),最后一位是128。因此,如果您添加的任何内容小于128,则结果是原始数字,即(float)INT_MAX + 1.f == (float)INT_MAX
。对于具有比int
更多有效数字的double
,它将起作用。 - Jan HudecINT_MIN
和INT_MAX
没有精确的浮点数表示是无法保证的。例如,在使用64位整数时,INT_MAX
是2^63-1,而(double)INT_MAX
会四舍五入为2^63,所以如果my_double
为2 ^ 63,则此检查将无法检测到溢出。将检查改为my_double >= INT_MAX || my_double < INT_MIN
应该可以在使用二进制补码整数时正常工作,即使看起来是错误的。 - nwellnhofnextafter
的唯一问题是,如果一个等于 INT_MIN
的 double
被转换成 int
,那么它将被检测为溢出。除此之外,它是一个不错的、可移植的解决方案。 - nwellnhofint 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
// Fixed 2022
// if (x < INT_MIN) Handle_Underflow();
if (x - INT_MIN < -1.0) Handle_Underflow();
#endif
return (int) x;
}
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
值可以很好地转换为int
。以前的代码无法处理这些值。
*1 当int
精度大于double
时,INT_MIN - 1
也适用于此。虽然这很少见,但问题也很容易适用于long long
。请考虑以下两者之间的区别: if (x < LLONG_MIN - 1.0) Handle_Underflow(); // Bad
if (x - LLONG_MIN < -1.0) Handle_Underflow();// Good
使用二进制补码,some_int_type_MIN
是一个(负的)2次幂,并且可以精确地转换为double
。因此,在关注范围内,x - LLONG_MIN
是准确的,而LLONG_MIN - 1.0
在减法中可能会失去精度。
MAX_INT+1
的大小,肯定可以用double
表示。你能解释一下为什么吗?你的假设是什么?假设IEEE足够吗? - Kristian SpangsegeFLT_RADIX
是2,并且INT_MAX
远低于DBL_MAX
,除了语言标准所保证的内容之外没有其他内容。这是一个很棒和有创意的解决方案。我喜欢它。 - Kristian Spangsege int
范围的结果(+和 - )。 在口语中,我听过和读过“下溢”用于过度负面的结果,由于OP也是这样使用它,因此回答同样有意义。“负数溢出”是一个好术语,即使有点啰嗦。 - chux - Reinstate MonicaDBL_MANT_DIG <= DBL_MAX_EXP
,这样 ceil()
的结果总是可表示的。IEEE 类型满足此条件。我从 Linux 的 ceil()
手册中了解到这一点。 - Kristian Spangsege一个适用于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上使用了。
SafeInt<size_t> x = std :: numeric_limits<size_t>::max() + 100
(它不会抛出异常)。 - kizzx2SafeInt<int64_t> v = pow(2.0, 63.0)
不会抛出异常。 - nwellnhofnumeric_cast<int64_t>(pow(2.0, 63.0))
不会抛出异常。 - nwellnhofdouble d = 9223372036854775807L;
int i = (int)d;
printf("%d\n", (int) (double) (9223372036854775807L));
会打印出2147483647,但是double d = 9223372036854775807L; int i = (int) d; printf("%d", i);
会打印出-2147483648。在Windows上使用MinGW730_64。 - Xixiaxixi我不能确定它是否适用于所有平台,但在我使用的每个平台上,基本上就是这样发生的。除了在我的经验中,它会滚动。也就是说,如果double的值为INT_MAX + 2,则当强制转换的结果最终成为INT_MIN + 2时。
至于最佳处理方法,我真的不确定。我自己也遇到了这个问题,但尚未找到一种优雅的处理方式。我相信会有人回复并帮助我们两个。