【问题标题】:If 'float'<= INT_MAX is true, then why (int)'float' may trigger undefined behavior?如果 'float'<= INT_MAX 为真,那么为什么 (int)'float' 可能会触发未定义的行为?
【发布时间】:2021-10-20 05:35:08
【问题描述】:

示例代码(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 &lt;= 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);
}

【问题讨论】:

  • float 意义不够。
  • 可以加printf("INT_MAX as float %f\n", (float)INT_MAX);吗?如果它打印出2147483648,那么您就知道为什么if 为真。
  • 请记住,float 类型(或任何非无限浮点类型)的有限精度可以应用于小数点的左侧和右侧。没有像 2147483600 这样的 float 数字。最接近的可表示 float 值是 2147483648。
  • 使用if ( F &lt;= nextafter(((float)INT_MAX) + 1.0f, -INFINITY) ),是nextafterf() 的意图,还是您建议使用double 函数? IAC,您的解决方案不属于问题,而是作为您自己的答案的帖子,可以对其进行正确评级。
  • if ( lrintf(F) &lt;= INT_MAX ) 是错误的,而 (float) INT_MAX 是准确的。例如16 位 int、64 位 float

标签: c type-conversion undefined-behavior c11 c17


【解决方案1】:

您将int 类型的值与float 类型的值进行比较。 &lt;= 运算符的操作数需要先转换为通用类型才能进行比较。

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

关于将F 转换为int,因为F 的整数部分超出了int 的范围,这会触发undefined behavior

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

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

2 当整数类型的值转换为真正的浮点类型时,如果被转换的值可以表示 正是在新类型中,它没有改变。 如果值是 转换在可以表示的值范围内 但不能准确表示,结果要么是最近的 较高或最接近的较低可表示值,在 实现定义的方式。 如果要转换的值是 在可以表示的值范围之外, 行为未定义。一些隐式转换的结果可能 以比这更大的范围和精度表示 新类型要求(见 6.3.1.8 和 6.8.6.4)

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

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

否则,如果任一操作数对应的实数类型 为double,转换其他操作数,不改变类型 域,对应的真实类型为double

否则,如果任一操作数对应的实数类型 为float,转换其他操作数,不改变类型 域,对应真实类型为float的类型。

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

【讨论】:

  • 谢谢!确实,我忘记了int 中的&lt;= 中的float。解决方法是使用lrintf():if (lrintf(F) &lt;= INT_MAX)
  • dbush,使用double 可能几乎可以解决int/float 问题,(F &lt;= 2147483647.0 是错误的测试,应该是F &lt; 2147483648.0 或仍然是float 数学F &lt; 2147483648.0f)但是乞求long long/double 的解决方案。我们可以利用some_integer_type_MAX is a is a mersine number形成exact FP limit,然后就不需要依赖更广泛的类型了。
  • @pmor lrintf(F) &lt;= INT_MAX 不是最好的,因为它不适用于 [INT_MAX + 0.5 .... INT_MAX + 1.0) 范围内的值。
【解决方案2】:

问题的根本原因是在进行 F &lt;= INT_MAX 比较时,从 INT_MAX 文字到 float 值的隐式转换。 float 数据类型根本没有足够的精度来正确存储 2147483647 值,并且(确实如此)存储了 2147483648 的值,而不是†。

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)。


† 在这种情况下,存储在float 中的实际 值是实现定义的,正如the excellent answer by dbush 中所指出的那样。

【讨论】:

    【解决方案3】:

    如果 F 打印为 2147483648.000000,那么为什么 F

    如果 'float'

    #define F 2147483600.0f ... if ( F &lt;= INT_MAX ) 是一个不充分的测试,因为它不精确。 INT_MAXfloat 的转换通常会发生舍入。


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

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

    当标准浮点类型的有限值转换为_Bool 以外的整数类型时,小数部分被丢弃(即,该值被截断为零)。如果整数部分的值不能用整数类型表示,则行为未定义。 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 捕获为非数字。


    *1some_int_MAX 可能是UINT128_MAX 或更多的问题,因为float 的指数范围有限。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-02-09
      • 1970-01-01
      • 1970-01-01
      • 2011-06-11
      • 2012-01-02
      • 2011-12-20
      • 2021-11-28
      相关资源
      最近更新 更多