3

示例代码(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的正确方法是什么?

UPD。解决方案:

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);
}
4

3 回答 3

8

您正在将 typeint的值与 type 的值进行比较float。运算符的操作数<=需要首先转换为通用类型来评估比较。

这属于通常的算术转换。在这种情况下, type 的值int被转换为 type float。并且由于所讨论的值 (2147483647) 不能完全表示为 a float,因此它会产生最接近的可表示值,在本例中为 2147483648。这与宏所表示的常量F转换为的值相匹配,因此比较是正确的。

关于Fto 类型的转换int,因为 的整数部分F超出了 an 的范围int,这会触发未定义的行为

C 标准的第 6.3.1.4 节规定了如何执行这些从整数到浮点以及从浮点到整数的转换:

1当实浮点类型的有限值转换为除 以外的整数类型_Bool时,小数部分被丢弃(即,该值被截断为 0)。如果整数部分的值不能用整数类型表示,则行为未定义。

2当整数类型的值转换为真正的浮点类型时,如果被转换的值可以在新类型中精确表示,则保持不变。如果要转换的值在可以表示但不能精确表示的值范围内,则结果是最接近的较高或最近的较低可表示值,以实现定义的方式选择。如果要转换的值超出可表示的值范围,则行为未定义。某些隐式转换的结果可能以比新类型所需的更大范围和精度表示(参见 6.3.1.8 和 6.8.6.4)

第 6.3.1.8p1 节规定了如何执行通常的算术转换:

首先,如果任一操作数的对应实类型为long double,则将另一个操作数转换为对应实类型为 的类型,而不改变类型域long double

否则,如果任一操作数的对应实类型为double,则将另一个操作数转换为对应实类型为 的类型,而不改变类型域double

否则,如果任一操作数的对应实类型为float,则将另一个操作数转换为对应实类型为 的类型,而不改变类型域float

至于在这种情况下如何避免未定义的行为,如果常量F没有后缀,即2147483600.0它有 type double。这种类型可以精确地表示任何 32 位整数值,因此给定值不会四舍五入,并且可以存储在int.

于 2021-08-18T12:17:13.850 回答
4

问题的根本原因是在进行比较时从INT_MAX文字到值的隐式转换。数据类型根本没有足够的精度来正确存储值,并且(它确实发生了)存储的值,而不是†。floatF <= INT_MAXfloat21474836472147483648

clang-cl 编译器对此发出警告:

警告:从“int”到“float”的隐式转换将值从 2147483647 更改为 2147483648 [-Wimplicit-const-int-float-conversion]

而且,您可以通过在代码中添加以下行来自己确认这一点:

printf("(float)IMAX  %f\n", (float)INT_MAX);

该行显示(float)IMAX 2147483648.000000在我的系统上(Windows 10、64 位、Visual Studio 2019 中的 clang-cl)。


†</sup>存储在这种情况下的实际值是实现定义的,正如dbush 的出色回答float中所指出的那样。

于 2021-08-18T12:19:37.943 回答
1

如果 F 打印为 2147483648.000000,那么为什么 F <= INT_MAX 为真?

如果 'float'<= INT_MAX 为真,那么为什么 (int)'float' 可能会触发未定义的行为?

#define F 2147483600.0f ... if ( F <= INT_MAX )是一个不充分的测试,因为它不精确。INT_MAXto的转换float通常会四舍五入。


在这里避免UB的正确方法是什么?

要测试 a是否可以毫无问题float地转换为 a ,请首先查看规范:int

当标准浮点类型的有限值被转换为除 以外的整数类型_Bool时,小数部分被丢弃(即,该值被截断为 0)。如果整数部分的值不能用整数类型表示,则行为未定义。C17博士

这意味着像 [-2,147,483,648.999... 到 2,147,483,647.999...] 这样的浮点值是可以接受的float- 或者使用扩展数学:(INT_MIN - 1 to INT_MAX + 1)。注[]诉()。

不需要更广泛的类型。

代码需要float精确地比较范围,如 。与梅森数
(INT_MAX/2 + 1) * 2.0f一样精确。 *1INT_MAX

// 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非数字。


*1 由于指数范围有限some_int_MAX,可能会出现问题或更多问题。UINT128_MAXfloat

于 2021-08-18T17:56:08.850 回答