如果 '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

问题:

  1. 如果F打印成2147483648.000000,那为什么F <= INT_MAX是真的?
  2. 这里避免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_MAXfloat 的转换通常会四舍五入。


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.0fINT_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 或更多的问题。