【问题标题】:Why does a float when converted to an int be rounded off below in C?为什么浮点数在转换为 int 时会在 C 中四舍五入?
【发布时间】:2016-01-26 07:14:57
【问题描述】:

在一次采访中,有人问我如何看待以下代码:

#include <stdio.h>

int main()
{
    float f = 10.7;
    int a;
    a = f;
    printf ("%d\n", a);
}

我回答:

  • 当您将 float 更改为 int 而不进行强制转换时,编译器会发出警告。

  • int 将具有垃圾值,因为您没有使用强制转换。

然后,他们允许我在在线编译器上运行程序。我不知所措。我的两个假设都是错误的。编译器没有发出任何警告,int 的值为 10。即使我将float 更改为 10.9 或 10.3 之类的值,答案也是一样的。即使输入cast 也不会改变结果。

谁能告诉我为什么会发生这种情况以及在什么情况下结果会有所不同。

注意:编译时,面试官告诉我不要添加gcc标志。


编辑:现在我明白了,浮点数不会四舍五入,答案是 10。但是有人能解释一下为什么这样设计吗?为什么任何浮点数在转换为 int 时会在下面四舍五入?有什么具体原因吗?

【问题讨论】:

  • 对于警告,它取决于编译器及其设置。对于行为,它是指定的方式。您对为什么语言规范这么说感兴趣吗?
  • @user2864740,哎呀,我不知道。抱歉,那你能解释一下为什么会这样吗?
  • @juanchopanza,是的,我对语言为什么这么说很感兴趣。
  • 我不希望出现警告,因为将 float 表达式分配给 int 变量时必须发生的事情完全由 C 标准指定。编译器如何知道程序员并不打算发生这种情况?
  • 这里没有进行舍入。只是截断。小数部分被忽略,整数部分分配给int

标签: c casting floating-point int type-conversion


【解决方案1】:

这是标准 6.3.1.4 所说的:

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

未定义的行为部分与整数的大小和符号有关。如果您选择的整数太小而无法包含结果,则会调用未定义的行为。

int 将具有垃圾值,因为您没有使用强制转换

int 可能在溢出的情况下有一个垃圾值,但是有没有强制转换与它无关。

编译器永远不需要显示“警告”,警告不是 C 标准指定的内容,它只涉及 诊断(即某种消息,称之为错误或警告)。因此,您永远不能随意假设每个编译器都会给出任何类型的警告。

在这种隐式浮点到整数转换的情况下,编译器根本不需要显示任何形式的诊断。然而,好的编译器会。

对于 GCC,此类警告相当草率,您必须通过添加 -Wconversion-Wfloat-conversion 明确告诉它发出警告。这些是暗示的额外标志。

【讨论】:

    【解决方案2】:

    许多转换在 C 中是隐含的:

    • 在所有数字类型之间,
    • 在数据指针类型和void * 类型之间,
    • 从数组类型到其对应的指针类型(这称为衰减)
    • 从非 const 类型到等效的 const,

    编译器通常不会发出关于这些的诊断,因为这些行为是由标准定义的,但其中一些转换表明存在编程错误,例如double x = 1 / 2;。为了帮助程序员避免这些愚蠢的错误,可以指示编译器发出警告甚至错误。

    使用这些额外的警告是明智的。如果-Wall -Wextra -Wconversion -Wfloat-conversionclang 具有类似的设置clang -Wall -Weverything 调用,gcc 将执行此操作。您的第一个假设并非不切实际,但 gcc 默认情况下对草率的代码非常宽容,并且您被指示不要使用任何标志。更糟糕的是:为了与旧的 Makefile 保持兼容,gcc 默认为 c89,并且会抱怨使用了一些 c99 功能。

    请注意,语言在可以确定它们是必要时定义行为,但有时它不能:

    float f = 10.7;
    printf("%d\n", f);
    

    在这种情况下,标准指定 f 应作为 double 传递给可变参数函数 printf,但 printf 需要一个 int 参数作为说明符 %d。您的第二个假设在这里是正确的,需要使用 (int) 强制转换的显式转换。同样,好的编译器可以根据指示对这些错误发出诊断。

    此外,一些隐式转换可以在编译时确定,以丢失甚至调用未定义行为的信息,例如:

    char x = 300;
    int x = 1e99;
    

    如果编译器在没有严格选项的情况下对这些问题发出诊断,那将会有所帮助。

    最后,一些转换会丢失信息,但在一般情况下更难检测:

    double f = 10000000000000;
    char a = f;
    float f = d;
    int i = d;
    

    只有当接收类型对于整数部分足够大时,语言才会定义行为,否则即使使用显式强制转换,行为也是未定义的。在这些情况下,程序员是否需要警告是个人选择的问题。

    关于为什么从浮点类型到整数类型的转换定义为向0舍入,这样做可能更简单,您可以通过使用round()或在前面添加0.5来获得其他行为如果您知道该值为正,则进行转化。

    【讨论】:

    • 我认为-Wall -Wextra 不足以从 GCC 获得警告。看来您必须在上面明确添加-Wconversion-Wfloat-conversion。现在想象一下,如果 -Wall 真的启用了所有警告......它会有多大用处。
    • @Lundin:这可能是。我不知道你是不是在讽刺......我确实认为这很有用,Stackoverflow 上的 C 问题至少有一半!我个人使用-Wall -Wextra -Werror,只有很少的-Wno-xxx 调整。
    • 我在讽刺典型的“GNU 逻辑”:-Wall This enables all the warnings about constructions that some users consider questionable, and that are easy to avoid。英文:“这不会启用所有警告,而是启用我们主观选择的任意警告集合,而没有给出任何理由。”
    • @Lundin:这种自以为是的做法太适得其反了!没有人能免于拼写错误。
    【解决方案3】:

    根据编译器设置会有警告。

    但是根据 C 语言标准,行为是绝对定义好的,浮点值会向零舍入。 a 保证为 10。由于您的回答错误地表明世界上几乎每个 C 程序都被致命地破坏了,所以我不会雇用您从事 C 工作。

    【讨论】:

    • 不需要后面的评论。投入一万小时后,很容易(忘记地)回顾。
    • 这个怎么样:float f = 10000000000000; char a = f;
    • @juanchopanza 抱歉,从浮点到整数的转换中的溢出对于无符号类型也是未定义的行为,并且在 artm 的示例中是未定义的行为,除非 char 可以表示 10000000000000。blog.frama-c.com/index.php?post/2013/10/09/…跨度>
    • @PascalCuoq 确实如此!不错的博客顺便说一句。
    【解决方案4】:

    C 定义了几乎所有内容之间的转换,因此,除非您启用规范不需要的额外检查,否则几乎没有任何赋值会产生警告或错误。

    至于为什么它会向零舍入,好吧,如果它在截断之前添加(或减去)0.5 以进行舍入,那么如果您 wanted 是截断,所以你不会比你必须添加 0.5 来舍入更糟糕。

    此外,C 是一种实用语言,重要的是它的正常行为与其原始时代的实际机器上的可用指令一致。

    【讨论】:

      【解决方案5】:

      我认为你的推理非常有道理。然而,C 通常并不完全合理。浮点值隐式舍入为零。当像这样隐式转换类型时,您可以让编译器发出警告。

      /*test.c*/
      
      #include <stdio.h>
      
      int main()
      {
          double f;
          int a;
      
          f = 10.7;
          a = f;
          printf("%d\n", a);
          return 0;
      }
      

      对于 GCC,我们正在寻找的选项是 -Wconversion:

      $ c89 -pedantic -Wall -Wconversion test.c
      test.c: In function ‘main’:
      test.c:9:6: warning: conversion to ‘int’ from ‘double’ may alter its value [-Wfloat-conversion]
        a = f;
            ^
      

      【讨论】:

        猜你喜欢
        • 2011-10-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-04-10
        • 1970-01-01
        • 1970-01-01
        • 2011-02-08
        • 2014-06-02
        相关资源
        最近更新 更多