【发布时间】:2010-10-06 06:42:45
【问题描述】:
今天,我注意到当我将大于最大可能整数的双精度数转换为整数时,我得到 -2147483648。同样,当我转换一个小于最小可能整数的双精度时,我也会得到 -2147483648。
是否为所有平台定义了这种行为?
检测这种下溢/溢出的最佳方法是什么?将 min 和 max int 的 if 语句放在强制转换之前是否是最佳解决方案?
【问题讨论】:
标签: c++ c casting floating-point
今天,我注意到当我将大于最大可能整数的双精度数转换为整数时,我得到 -2147483648。同样,当我转换一个小于最小可能整数的双精度时,我也会得到 -2147483648。
是否为所有平台定义了这种行为?
检测这种下溢/溢出的最佳方法是什么?将 min 和 max int 的 if 语句放在强制转换之前是否是最佳解决方案?
【问题讨论】:
标签: c++ c casting floating-point
将浮点数转换为整数时,溢出会导致未定义的行为。来自 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。 ints 的便携、安全但略微不准确的版本是:
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),它不会溢出。有什么不同?
float 有(除了非常奇特的平台都使用 IEEE-754 浮点数)有 24 个有效二进制数字。因此,当您将其设置为 INT_MAX,即 2³¹-1(32 位平台上的 INT_MAX)时,最后一位是 128s。因此,如果您添加任何小于 128 的值,则结果是原始数字,即 (float)INT_MAX + 1.f == (float)INT_MAX。使用double,它的有效数字比int 多。
INT_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 实际上应该可以使用二进制补码整数,即使它看起来是错误的。
nextafter 的唯一问题是等于INT_MIN 的double 将被检测为溢出,尽管它可以转换为int。除此之外,它还是一个不错的便携解决方案。
回答您的问题:当您投射超出范围的浮点数时的行为是未定义的或特定于实现的。
从经验来看:我曾在 MIPS64 系统上工作过,它根本没有实现这些类型的转换。 CPU 没有做一些确定性的事情,而是抛出了一个 CPU 异常。应该模拟强制转换的异常处理程序返回而不对结果做任何事情。
我最终得到了随机整数。猜猜需要多长时间才能将错误追溯到此原因。 :-)
如果你不确定数字不能超出有效范围,你最好自己做范围检查。
【讨论】:
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(它不会抛出)。
SafeInt<int64_t> v = pow(2.0, 63.0) 不扔。
检测这种下溢/溢出的最佳方法是什么?
将截断的double 与INT_MIN,INT_MAX 附近的精确 限制进行比较。
技巧是准确将基于INT_MIN,INT_MAX 的限制转换为double 值。 double 可能不完全代表 INT_MAX,因为 int 中的位数可能超过该浮点的精度。*1 在这种情况下,转换的INT_MAX 到double 遭受舍入。 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
// Fixed 2022
// if (x < INT_MIN) Handle_Underflow();
if (x - INT_MIN < -1.0) 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();
}
}
[Edit 2022] 6 年后更正拐角错误。
double 范围内的值 (INT_MIN - 1.0 ... INT_MIN)(不包含端点)可以很好地转换为 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
使用 2 的补码,some_int_type_MIN 是 2 的(负)幂并且完全转换为 double。因此x - LLONG_MIN 在关注范围内是精确的,而LLONG_MIN - 1.0 可能会在减法中遭受精度损失。
【讨论】:
MAX_INT+1的大小,当然可以表示为double。你能解释一下为什么吗?你的假设是什么?假设 IEEE 就够了吗?
FLT_RADIX 是 2 并且 INT_MAX 远低于 DBL_MAX,除了语言标准所保证的之外没有其他任何内容。这是一个奇妙而有创意的解决方案。我喜欢它。
int 范围的结果(+ 和-)。在白话中,我听到并阅读了 underflow 的使用结果过于负面,并且由于 OP 以这种方式使用它,因此同样回答也是有意义的。 负溢出是个好词——即使有点冗长。
DBL_MANT_DIG <= DBL_MAX_EXP使得ceil()的结果总是可表示的。 IEEE 类型满足此条件。我从ceil() 的 Linux 手册页中找到了这个。
我们遇到了同样的问题。如:
double d = 9223372036854775807L;
int i = (int)d;
在 Linux/window 中,i = -2147483648。但在 AIX 5.3 中 i = 2147483647。
如果双精度超出整数范围。
【讨论】:
printf("%d\n", (int) (double) (9223372036854775807L));prints 2147483647,但 double d = 9223372036854775807L; int i = (int) d; printf("%d", i); 打印出 -2147483648。 Windows 上的 MinGW730_64
另一个选项是使用boost::numeric_cast,它允许在数字类型之间进行任意转换。它会在数字类型转换时检测范围丢失,如果范围不能保留,则抛出异常。
上面引用的网站还提供了一个小示例,可以快速概述如何使用此模板。
当然,这不再是普通的 C 了;-)
【讨论】:
numeric_cast<int64_t>(pow(2.0, 63.0)) 不扔。
我不确定这一点,但我认为有可能“打开”下/溢出的浮点异常......看看这个Dealing with Floating-point Exceptions in MSVC7\8,这样你就可以替代 if/else 检查.
【讨论】:
我无法确定它是否适用于所有平台,但这几乎是我使用过的每个平台上发生的情况。除了,根据我的经验,它滚动。也就是说,如果 double 的值为 INT_MAX + 2,那么当强制转换的结果最终为 INT_MIN + 2 时。
至于处理它的最佳方法,我真的不确定。我自己也遇到过这个问题,但还没有找到一种优雅的方式来处理它。我相信有人会做出回应,对我们俩都有帮助。
【讨论】: