如果 'float'<= INT_MAX 为真,那么为什么 (int)'float' 可能会触发未定义的行为?
If 'float'<= INT_MAX is true, then why (int)'float' may trigger undefined behavior?
示例代码(t0.c):
#include <stdio.h>
#include <limits.h>
#define F 2147483600.0f
int main(void)
{
printf("F %f\n", F);
printf("INT_MAX %d\n", INT_MAX);
printf("F <= INT_MAX %d\n", F <= INT_MAX);
if ( F <= INT_MAX )
{
printf("(int)F %d\n", (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的正确方法是什么?
更新。解决方案:
if ( lrintf(F) <= INT_MAX )
{
printf("(int)F %d\n", (int)F);
}
UPD2。更好的解决方案:
if ( F <= nextafter(((float)INT_MAX) + 1.0f, -INFINITY) )
{
printf("(int)F %d\n", (int)F);
}
您正在比较 int
类型的值与 float
类型的值。 <=
运算符的操作数需要先转换为普通类型才能进行比较。
这属于通常的算术转换。在这种情况下,类型 int
的值将转换为类型 float
。并且因为所讨论的值 (2147483647) 不能完全表示为 float
,它会产生最接近的可表示值,在本例中为 2147483648。这与宏 F
表示的常量转换为的内容相匹配, 所以比较是正确的。
关于将 F
转换为类型 int
,因为 F
的整数部分超出了 int
的范围,这会触发 undefined behavior.
C standard 的第 6.3.1.4 节规定了如何执行从整数到浮点数以及从浮点数到整数的这些转换:
1 When a finite value of real floating type is converted to an integer type other than _Bool
, the fractional part is discarded
(i.e., the value is truncated toward zero). If the value of
the integral part cannot be represented by the integer type, the
behavior is undefined.
2 When a value of integer type is converted to a real floating type, if the value being converted can be represented
exactly in the new type, it is unchanged. If the value being
converted is in the range of values that can be represented
but cannot be represented exactly, the result is either the nearest
higher or nearest lower representable value, chosen in an
implementation-defined manner. If the value being converted is
outside the range of values that can be represented, the
behavior is undefined. Results of some implicit conversions may
be represented in greater range and precision than that
required by the new type (see 6.3.1.8 and 6.8.6.4)
第 6.3.1.8p1 节规定了通常的算术转换是如何执行的:
First, if the corresponding real type of either operand is long double
, the other operand is converted, without change of type domain,
to a type whose corresponding real type is long double
.
Otherwise, if the corresponding real type of either operand
is double
, the other operand is converted, without change of type
domain, to a type whose corresponding real type is double
.
Otherwise, if the corresponding real type of either operand
is float
, the other operand is converted, without change of type
domain, to a type whose corresponding real type is float
.
至于在这种情况下如何避免未定义的行为,如果常量 F
没有后缀,即 2147483600.0
那么它的类型是 double
。这种类型可以准确地表示任何 32 位整数值,因此给定值不会四舍五入,可以存储在 int
.
中
问题的根本原因是在进行 F <= INT_MAX
比较时,从 INT_MAX
文字到 float
值的隐式转换。 float
数据类型根本没有足够的精度来正确存储 2147483647
值,并且(碰巧)存储了 2147483648
的值,而不是 † .
clang-cl 编译器对此发出警告:
warning : implicit conversion from 'int' to 'float' changes value from
2147483647 to 2147483648 [-Wimplicit-const-int-float-conversion]
并且,您可以通过在代码中添加以下行来自行确认:
printf("(float)IMAX %f\n", (float)INT_MAX);
该行在我的系统上显示 (float)IMAX 2147483648.000000
(Windows 10、64 位、clang-cl in Visual Studio 2019)。
† 在这种情况下,存储在 float
中的 actual 值是实现定义的,如 中指出的那样。
If F is printed as 2147483648.000000, then why F <= INT_MAX is true?
If 'float'<= INT_MAX is true, then why (int)'float' may trigger undefined behavior?
#define F 2147483600.0f ... if ( F <= INT_MAX )
是一个不充分的测试,因为它不精确。 INT_MAX
到 float
的转换通常会四舍五入。
What is the correct way to avoid UB here?
要测试 float
是否可以毫无问题地转换为 int
,请先查看规范:
When a finite value of standard floating type is converted to an integer type other than _Bool
, the fractional part is discarded (i.e., the value is truncated toward zero). If the value of the integral part cannot be represented by the integer type, the behavior is undefined. C17dr
这意味着像 [-2,147,483,648.999... 到 2,147,483,647.999...] 这样的浮点值是可以接受的 float
- 或者扩展数学:(INT_MIN - 1 to INT_MAX + 1)
。注[] v.().
不需要更宽的类型。
代码需要比较范围,准确地说是float
。
(INT_MAX/2 + 1) * 2.0f
与 INT_MAX
完全相同 Mersenne Number。 *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
捕获为非数字。
由于 float
指数范围有限,*1 some_int_MAX
可能是 UINT128_MAX
或更多的问题。
示例代码(t0.c):
#include <stdio.h>
#include <limits.h>
#define F 2147483600.0f
int main(void)
{
printf("F %f\n", F);
printf("INT_MAX %d\n", INT_MAX);
printf("F <= INT_MAX %d\n", F <= INT_MAX);
if ( F <= INT_MAX )
{
printf("(int)F %d\n", (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的正确方法是什么?
更新。解决方案:
if ( lrintf(F) <= INT_MAX )
{
printf("(int)F %d\n", (int)F);
}
UPD2。更好的解决方案:
if ( F <= nextafter(((float)INT_MAX) + 1.0f, -INFINITY) )
{
printf("(int)F %d\n", (int)F);
}
您正在比较 int
类型的值与 float
类型的值。 <=
运算符的操作数需要先转换为普通类型才能进行比较。
这属于通常的算术转换。在这种情况下,类型 int
的值将转换为类型 float
。并且因为所讨论的值 (2147483647) 不能完全表示为 float
,它会产生最接近的可表示值,在本例中为 2147483648。这与宏 F
表示的常量转换为的内容相匹配, 所以比较是正确的。
关于将 F
转换为类型 int
,因为 F
的整数部分超出了 int
的范围,这会触发 undefined behavior.
C standard 的第 6.3.1.4 节规定了如何执行从整数到浮点数以及从浮点数到整数的这些转换:
1 When a finite value of real floating type is converted to an integer type other than
_Bool
, the fractional part is discarded (i.e., the value is truncated toward zero). If the value of the integral part cannot be represented by the integer type, the behavior is undefined.2 When a value of integer type is converted to a real floating type, if the value being converted can be represented exactly in the new type, it is unchanged. If the value being converted is in the range of values that can be represented but cannot be represented exactly, the result is either the nearest higher or nearest lower representable value, chosen in an implementation-defined manner. If the value being converted is outside the range of values that can be represented, the behavior is undefined. Results of some implicit conversions may be represented in greater range and precision than that required by the new type (see 6.3.1.8 and 6.8.6.4)
第 6.3.1.8p1 节规定了通常的算术转换是如何执行的:
First, if the corresponding real type of either operand is
long double
, the other operand is converted, without change of type domain, to a type whose corresponding real type islong double
.Otherwise, if the corresponding real type of either operand is
double
, the other operand is converted, without change of type domain, to a type whose corresponding real type isdouble
.Otherwise, if the corresponding real type of either operand is
float
, the other operand is converted, without change of type domain, to a type whose corresponding real type isfloat
.
至于在这种情况下如何避免未定义的行为,如果常量 F
没有后缀,即 2147483600.0
那么它的类型是 double
。这种类型可以准确地表示任何 32 位整数值,因此给定值不会四舍五入,可以存储在 int
.
问题的根本原因是在进行 F <= INT_MAX
比较时,从 INT_MAX
文字到 float
值的隐式转换。 float
数据类型根本没有足够的精度来正确存储 2147483647
值,并且(碰巧)存储了 2147483648
的值,而不是 † .
clang-cl 编译器对此发出警告:
warning : implicit conversion from 'int' to 'float' changes value from 2147483647 to 2147483648 [-Wimplicit-const-int-float-conversion]
并且,您可以通过在代码中添加以下行来自行确认:
printf("(float)IMAX %f\n", (float)INT_MAX);
该行在我的系统上显示 (float)IMAX 2147483648.000000
(Windows 10、64 位、clang-cl in Visual Studio 2019)。
† 在这种情况下,存储在 float
中的 actual 值是实现定义的,如
If F is printed as 2147483648.000000, then why F <= INT_MAX is true?
If 'float'<= INT_MAX is true, then why (int)'float' may trigger undefined behavior?
#define F 2147483600.0f ... if ( F <= INT_MAX )
是一个不充分的测试,因为它不精确。 INT_MAX
到 float
的转换通常会四舍五入。
What is the correct way to avoid UB here?
要测试 float
是否可以毫无问题地转换为 int
,请先查看规范:
When a finite value of standard floating type is converted to an integer type other than
_Bool
, the fractional part is discarded (i.e., the value is truncated toward zero). If the value of the integral part cannot be represented by the integer type, the behavior is undefined. C17dr
这意味着像 [-2,147,483,648.999... 到 2,147,483,647.999...] 这样的浮点值是可以接受的 float
- 或者扩展数学:(INT_MIN - 1 to INT_MAX + 1)
。注[] v.().
不需要更宽的类型。
代码需要比较范围,准确地说是float
。
(INT_MAX/2 + 1) * 2.0f
与 INT_MAX
完全相同 Mersenne Number。 *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
捕获为非数字。
由于
float
指数范围有限,*1 some_int_MAX
可能是 UINT128_MAX
或更多的问题。