示例代码(t0.c):
#include <stdio.h>
#include <limits.h>
#define F 2147483600.0f
int main(void)
{
printf("F %fn", F);
printf("INT_MAX %dn", INT_MAX);
printf("F <= INT_MAX %dn", F <= INT_MAX);
if ( F <= INT_MAX )
{
printf("(int)F %dn", (int)F);
}
return 0;
}
调用:
$ gcc t0.c && ./a.exe
F 2147483648.000000
INT_MAX 2147483647
F <= INT_MAX 1
(int)F 2147483647
$ clang t0.c && ./a.exe
F 2147483648.000000
INT_MAX 2147483647
F <= INT_MAX 1
(int)F 0
问题:
- 如果
F
打印为2147483648.000000
,那么为什么F <= INT_MAX
为真 - 在这里避免UB的正确方法是什么
UPD。解决方案:
if ( lrintf(F) <= INT_MAX )
{
printf("(int)F %dn", (int)F);
}
UPD2.更好的解决方案:
if ( F <= nextafter(((float)INT_MAX) + 1.0f, -INFINITY) )
{
printf("(int)F %dn", (int)F);
}
您正在比较int
类型的值和float
类型的值。<=
运算符的操作数需要首先转换为通用类型以评估比较。
这属于常用算术转换。在这种情况下,类型int
的值被转换为类型float
。由于所讨论的值(2147483647)不能精确地表示为float
,因此它会产生最接近的可表示值,在本例中为2147483648。这与宏F
所表示的常数转换为的值相匹配,因此比较为真。
关于F
到类型int
的强制转换,因为F
的整数部分在int
的范围之外,所以这会触发未定义的行为。
C标准第6.3.1.4节规定了如何执行从整数到浮点以及从浮点到整数的转换:
1当实数浮点类型的有限值转换为
_Bool
以外的整数类型时,小数部分被丢弃(即,该值被截断为零)如果整数部分不能用整数类型表示行为未定义2当整数类型的值转换为实浮点类型时,如果可以表示转换的值正是在新类型中,它没有改变如果值为converted在可以表示的值范围内但无法准确表示,结果要么是最接近的在实现定义的方式如果要转换的值是在可表示的值范围之外行为未定义。某些隐式转换的结果可能以更大的范围和精度表示新型要求(见6.3.1.8和6.8.6.4)
第6.3.1.8p1节规定了如何执行通常的算术转换:
首先,如果任一操作数的对应实数类型为
long double
,则转换另一个操作数,而不更改类型域,其对应的实际类型是CCD_ 17。否则,如果任一操作数的对应实数类型为
double
,则转换另一个操作数,而不更改类型域转换为对应的实类型为double
的类型。否则,如果任一操作数的对应实数类型为
float
,则转换另一个操作数,不更改类型域转换为对应的实类型为float
的类型
至于在这种情况下如何避免未定义的行为,如果常量F
没有后缀,即2147483600.0
,则它具有类型double
。这种类型可以精确地表示任何32位整数值,因此给定的值不取整,可以存储在int
中。
问题的根本原因是在进行F <= INT_MAX
比较时,从INT_MAX
文字隐式转换为float
值。float
数据类型根本没有足够的精度来正确存储2147483647
值,并且(碰巧),2147483648
的值被存储,而†。
clang-cl编译器对此发出警告:
警告:从"int"到"float"的隐式转换会更改2147483647到2147483648[-Wimplicit const int float conversion]
而且,您可以通过在代码中添加以下行来确认这一点:
printf("(float)IMAX %fn", (float)INT_MAX);
这一行在我的系统上显示(float)IMAX 2147483648.000000
(Visual Studio 2019中的Windows 10,64位,clang cl)。
†在这种情况下,存储在float
中的实际值是实现定义的,正如dbush的精彩回答中所指出的。
如果F打印为2147483648.000000,那么为什么F<=INT_MAX是真的吗?
如果'float'<=INT_MAX为真,那么为什么(INT)"float"可能会触发未定义的行为?
#define F 2147483600.0f ... if ( F <= INT_MAX )
是一个不充分的测试,因为它不精确。CCD_ 35到CCD_ 36的转换通常经历四舍五入。
在这里避免UB的正确方法是什么?
要测试float
是否可以毫无问题地转换为int
,请首先查看规范:
当标准浮动类型的有限值转换为
_Bool
以外的整数类型时,小数部分被丢弃(即,该值被截断为零)。如果整数部分的值不能用整数类型表示,则行为是未定义的。C17d
这意味着像[-2147483648.999…到2147483647.999…]这样的浮点值是可接受的float
,或者具有扩展数学:(INT_MIN - 1 to INT_MAX + 1)
。注[]v.()。
不需要更广泛的类型。
代码需要精确地比较范围,如float
(INT_MAX/2 + 1) * 2.0f
是精确的,因为INT_MAX
是梅森数*1
// Form INT_MAX_PLUS1 as a float
#define F_INT_MAX_PLUS1 ((INT_MAX/2 + 1) * 2.0f)
// With 2's complement, INT_MIN is exact as a float.
if (some_float < F_INT_MAX_PLUS1 && (some_float - INT_MIN) > -1.0f)) {
int i = (int) some_float; // no problem
} else {
puts("Conversion problem");
}
提示:形成如上所述的测试,也可以将some_float
捕获为非数字。
*1some_int_MAX
可能是UINT128_MAX
或更多的问题,因为float
指数范围有限。