【问题标题】:C++ : Implicit type conversionC++:隐式类型转换
【发布时间】:2012-09-12 21:00:33
【问题描述】:

我对隐式类型转换有点困惑。给定以下程序

   float x = 4.23423451;
   double y = 4.23423451;

   float z = 101.9876;

   float res1 = x * z;
   float res2 = y * z;

   std::cout << "res1 & res2 " << res1 << "  & " << res2 << std::endl;
   std::cout << "equality " << (res1 == res2) << std::endl;

输出是

   res1 & res2 431.839  & 431.839
   equality 1

我的问题是“对于 x、y 和 z (x = y) 的任何值以及任何编译器来说,相等性是否总是正确的?”

res2 = y * z;

变量“y”会被类型转换为浮点数还是变量“z”被类型转换为双精度?

【问题讨论】:

  • 这是明确定义的。 z 的中间表达式将扩展为 double,因此 y * z 将是 double 表达式。然后隐式缩小转换会将其转换为float 以存储在res2 中。同样适用于res1
  • 话虽如此,res1 不一定等价于res2 -- 它高度依赖于环境中floatdouble 的精度。这两个文字甚至可能不相等——4.23423451f 不需要等同于 4.23423451
  • 如果您使用的是 Visual Studio:转到项目属性 -> C/C++ -> 常规 -> 警告级别。将其设置为 3 级。在错误窗口的警告部分中,它将显示不是由您手动投射的投射。我确定另一个 IDE 也会有类似的东西。希望有帮助
  • @QuantumKarl - 关于术语的一点:cast 是您在源代码中编写的内容。它告诉编译器进行转换。在某些情况下,编译器会在没有 cast 的情况下进行 conversion。这些称为隐式转换
  • @Pete - 谢谢指出,它仍在以任何一种方式进行转换。随着警告设置的增加,编译器将警告您有关您未手动告诉编译器进行强制转换的任何转换。上面的代码在 res2 = y * z 上生成此“警告 C4244:'initializing':从 'double' 转换为 'float',可能丢失数据”;告诉你 y 正在转换为浮点数。

标签: c++


【解决方案1】:

my comments

这是明确定义的。 z 的中间表达式将扩展为 double,因此 y * z 将是 double 表达式。然后隐式缩小转换会将其转换为float 以存储在res2 中。同样的缩小范围适用于res1

C++11 标准的 §5¶9 表达式 [expr] 反映了这一点。

许多期望算术或枚举类型的操作数的二元运算符会导致转换并以类似的方式产生结果类型。目的是产生一个通用类型,这也是结果的类型。这种模式称为常用算术转换,定义如下:

...

  • 否则,如果任一操作数为double,则另一个应转换为double
  • 否则,如果任一操作数为float,则另一个应转换为float

...

然而,这并不能确定等式是否成立。

话虽如此,res1 不一定等同于res2——它高度依赖于环境中floatdouble 的精度。这两个文字甚至可能不相等——4.23423451f 甚至不需要等同于4.23423451。您不能确定static_cast&lt;double&gt;(static_cast&lt;float&gt;(4.23423451)) 是否等于4.23423451

参见 §5.17¶3 赋值和复合赋值运算符 [expr.ass]

如果左操作数不是类类型,则表达式被隐式转换(第 4 条)为左操作数的 cv 非限定类型。

§4 标准转换 [conv] 状态如下:

标准转换是具有内置含义的隐式转换。第 4 条列举了所有此类转换。 标准转换序列是按以下顺序进行的标准转换序列:

...

  • 以下集合中的零个或一个转换:整数提升、浮点提升、整数转换、浮点转换、浮点整数转换、指针转换、指向成员的指针转换和布尔转换。

§4.6 浮点提升 [conv.fpprom] 中所述,

  1. float 类型的纯右值可以转换为double 类型的纯右值。值不变。
  2. 这种转换称为浮点提升

... 和 §4.8 浮点转换 [conv.double],

  1. 浮点类型的纯右值可以转换为另一种浮点类型的纯右值。如果源值可以在目标类型中精确表示,则转换的结果就是该精确表示。如果源值介于两个相邻的目标值之间,则转换的结果是实现定义的对这些值中的任何一个的选择。否则,行为未定义。

  2. 允许作为浮点提升的转换从浮点转换集中排除。

这里的问题是,我们有多种情况,我们的转换不是提升,而是缩小到可能精度较低的类型(doublefloat)。

基本上,任何时候将double 转换为float,都可能会丢失精度。

【讨论】:

    【解决方案2】:

    您应该从不比较浮点值是否相等。

    【讨论】:

    • 除非您应该这样做,例如在手头的问题中。 &lt;g&gt;
    • @PeteBecker:你能解释一下为什么你应该在上面的代码中比较相等吗?有很多事情可以使结果略有不同,即使值相同,相等性测试也会失败。例如,某些架构的浮点寄存器比double(80 对 64)更大,如果您计算一个操作并将值写入内存,您执行相同的计算,然后将结果寄存器中的值与值进行比较从内存中读取它们可能不同。
    • 浮点变量是非常好的公民。如果你说float x = 1.1; float y = x;,那么绝对可以保证x == y 是真的。您应该做的是对涉及它们的计算做出假设。
    • 因为问题是这两个值是否永远相等。这是关于浮点转换的影响。
    • @DavidRodríguez-dribeas:我很确定相等运算符也是语义的。我们不是在这里写memcmp 代码!关键是浮点类型本质上并不是“不稳定的”、“模糊的”或“破碎的”。这真的只是关于计算0.1 始终是 0.1(作为类型化值),但 2 * 0.10.2 相同...
    【解决方案3】:

    不,这不能保证。 xy 不一定具有相同的值。 确实在表达式x * zy * z中两者都被提升为double,但是将x提升为double的结果不需要等于y的值。x * z 被评估为float,表达式y * zz 提升为加倍,并且乘法的结果不必相等,因此转换回更窄的类型可能会导致不同的值.

    【讨论】:

    • 问题中xz都是float类型。
    • @JesseGood - 我认为关键是xyx 被提升为双倍时具有不同的值。
    • @PeteBecker:为什么x被提升为double?它仅用于表达式float res1 = x * z;,其中所有值都是floats。
    • @JesseGood - x 未提升为双倍。
    • @PeteBecker:嗯,你的评论说when x is promoted to double。我指出答案是promoted to double in the expressions x * z,这是不正确的,因为xzfloats。
    【解决方案4】:

    演员阵容应该保持不变;但是,我已经看到处理器和操作系统以高精度影响实际数学。

    但是,除此之外,请使用 static_cast 明确:

    float res2 = static_cast<float>(y * static_cast<double>(z));
    

    这样,每个人都知道你的意思以及你想要表达的意思。

    【讨论】:

    • 原始代码中没有casts。问题是关于隐式转换,使用强制转换使其显式不会影响它们的工作。当然,转换为错误的类型会弄乱结果...
    • @PB 我不知道为什么,但是即使预览显示了尖括号,网页也吃掉了尖括号。我用它们来展示为了明确而明确的替代方案,如帖子中所列。尽管有“每个人都应该知道一切”的理论,但我经常发现人们在浏览时并不知道某些事情,所以在这种情况下,我更喜欢直言不讳。
    • 是的,论坛软件可以骗人。他们现在在那里。不过,我不会使用强制转换进行隐式转换,因为我可能会弄错,而且因为在将来的维护中类型可能会改变并且强制转换会出错。
    • 这是一场有趣的辩论;然而,static_cast 被创建为对阅读器和文本扫描器都显而易见。当更改为 C++11 时,您可能会检查的一件事是此类规则是否已更改。如果他们这样做了,请使用 grep 进行静态强制转换。
    • 浮点数学的规则在 C++11 中没有改变,static_cast 的规则也没有改变。但是强制转换是为了告诉编译器当它不能或不会做你想做的事情时要做什么。如果您想要文档,请编写 cmets,而不是强制转换。
    猜你喜欢
    • 2011-07-01
    • 1970-01-01
    • 2011-12-25
    • 1970-01-01
    • 2013-07-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多