C++:隐式类型转换

3
我是一个有用的助手,可以进行文本翻译。

我对隐式类型转换感到有些困惑。考虑以下程序:

   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 也是同样的情况。 - obataku
话虽如此,res1不一定等同于res2——这高度取决于环境中floatdouble的精度。这两个字面量可能甚至不相等——4.23423451f不必等同于4.23423451 - obataku
如果您正在使用Visual Studio:转到项目属性-> C / C ++->常规->警告级别。将其设置为3级。在错误窗口的警告部分中,它将显示未由您手动转换的转换。我相信另一个IDE也会有类似的功能。希望能帮到您。 - QuantumKarl
1
@QuantumKarl - 关于术语的一点说明:强制类型转换(cast)是你在源代码中编写的内容,它告诉编译器要进行转换(conversion)。有时编译器会在没有强制类型转换的情况下进行转换(conversion),这被称为隐式转换(implicit conversions) - Pete Becker
@Pete - 谢谢注意,无论如何它仍在转换中。通过增加警告设置,编译器将警告您有关任何未使用强制转换手动告诉编译器执行的转换。上面的代码生成了这个“warning C4244:'initializing':conversion from 'double' to 'float',possible loss of data”在res2 = y * z; 告诉您y正在被转换为浮点数。 - QuantumKarl
4个回答

7

请查看我的评论

这是一个明确定义的过程。变量z的中间表达式将被扩展为double,因此y * z将成为一个double表达式。然后进行隐式缩小转换以将其转换为float以存储在res2中。这种缩小转换同样适用于res1

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

许多期望算术或枚举类型的操作数的二元运算符会引起转换,并以类似的方式产生结果类型。目的是产生一个公共类型,这也是结果的类型。这种模式称为“通常算术转换”,其定义如下:
...
否则,如果任一操作数是双精度,则另一个操作数应转换为双精度。
否则,如果任一操作数是浮点型,则另一个操作数应转换为浮点型。
...
然而,这并不能保证等式成立。
这就是说,res1 不一定等于 res2 -- 它高度依赖于环境中 floatdouble 的精度。这两个文本可能甚至不相等 -- 4.23423451f 可能与 4.23423451 不等。你不能确定 static_cast<double>(static_cast<float>(4.23423451)) 是否等于 4.23423451

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

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

§4 标准转换 [conv] 如下所述:

标准转换是具有内置含义的隐式转换。第4条列举了这些转换的完整集合。标准转换序列是以下顺序的标准转换序列:
...
从以下集合中进行零或一次转换:整数提升、浮点提升、整数转换、浮点转换、浮点整数转换、指针转换、成员指针转换和布尔转换。
正如在§4.6浮点提升[conv.fpprom]中详细说明的那样,
一个float类型的prvalue可以转换为double类型的prvalue。值不变。
这个转换被称为浮点提升。
... 和§4.8浮点转换[conv.double]。
浮点类型的prvalue可以转换为另一种浮点类型的prvalue。如果源值可以在目标类型中精确表示,则转换的结果是该精确表示。如果源值位于两个相邻目标值之间,则转换的结果是这些值中任意一个的实现定义选择。否则,行为未定义。
作为浮点提升允许的转换不包括在浮点转换集合中。
问题在于我们有多种情况下转换并非提升,而是缩小到可能较低精度的类型(从double到float)。
基本上,任何时候将double转换为float,都可能会失去精度。

3

在比较浮点数是否相等时,您应该永远避免直接进行比较。


1
除非需要,比如在本问题中所述,否则不应将其实践。<g> - Pete Becker
1
浮点变量是非常好的公民。如果你说 float x = 1.1; float y = x; 那么肯定保证 x == y 是正确的。你应该做的是对涉及它们的计算进行假设。 - Kerrek SB
@KerrekSB:问题在于相等性是按位相等性,而不是语义相等性,这在值相同的情况下会失败。请阅读前面的评论。或者我可能漏掉了什么? - David Rodríguez - dribeas
1
@DavidRodríguez-dribeas:我非常确定相等运算符也是有语义的。我们不是在写“memcmp”代码!关键是浮点类型本质上并不是“摇晃的”、“模糊的”或者“损坏的”。这实际上完全只与计算有关。0.1总是0.1(作为一个输入值),但是“2 * 0.1”不等于“0.2”…… - Kerrek SB
相反地:如果所有的值都是双精度浮点数,C和C++将得到相同的结果。如果直接比较它们而不将其存储到适当类型的变量中,则结果可能会有所不同,这正好允许像英特尔使用80位数学运算一样的操作。 - Pete Becker
显示剩余5条评论

1
不,这并不是保证的。 xy 不一定具有相同的值。虽然在表达式 x * zy * z 中,两者都被提升为 double,但提升 x 到 double 的结果不一定等于 y 的值。而 x * z 被评估为 float,表达式 y * zz 提升为 double,并且乘法的结果可能不相等,因此将其转换回较窄类型可能会导致不同的值。

在这个问题中,xz都是float类型。 - Jesse Good
1
@JesseGood - 我认为重点在于当 x 转换为 double 类型时,xy 的值是不同的。 - Pete Becker
@PeteBecker:为什么将 x 提升为 double?它只在表达式 float res1 = x * z; 中使用,其中所有值都是 float - Jesse Good
@JesseGood - x没有被提升为double类型。 - Pete Becker
1
@PeteBecker:嗯,你的评论说“当x提升为double时”。我指出答案中说“在表达式x * z中提升为double”,这是不正确的,因为x和z是浮点数。 - Jesse Good
显示剩余2条评论

0

强制类型转换应该保持不变;但是,我曾经看到处理器和操作系统在高精度计算中影响实际数学。

但是,撇开这些不谈,使用static_cast来明确:

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

这样每个人都知道你的意思,而且你是想要投射事物。


原始代码中没有任何强制转换。问题是关于隐式转换,使用强制转换将其显式化不会影响它们的操作。当然,如果强制转换为错误的类型,将会破坏结果... - Pete Becker
@PB 我不确定为什么,但网页吞了角括号,尽管预览中显示它们。我使用它们来展示显式表述的替代方案,正如文章中所列举的一样。尽管存在“每个人都应该知道一切”的理论,但我经常发现人们在浏览时不知道某些事情,因此在这种情况下,我更喜欢明确表述。 - Jonathan Seng
是的,论坛软件可能会误导人。它们现在已经存在了。不过,我不会使用强制转换进行隐式转换,因为我可能会弄错,并且在未来的维护中类型可能会更改,这时强制转换就会出错。 - Pete Becker
这是一个有趣的辩论;然而,static_cast 的创建是为了让读者和文本扫描器都能够理解。当你像转换到 C++11 这样的更改时,你可能需要检查的一件事情是这些规则是否发生了变化。如果确实发生了变化,请使用 grep 查找静态转换。 - Jonathan Seng
浮点数运算规则在C++11中没有改变,static_cast的规则也没有改变。但是强制转换是用来告诉编译器当它不能或不想做你想要的事情时该怎么做的。如果你需要文档,请写注释,而不是强制转换。 - Pete Becker
我知道他们没有,也很可能不会这样做。但是当你在做某件事情时,你会担心初级程序员会误解或者更糟糕的是,将其转移到不同的处理器架构上,数据类型的大小可能会改变。你可以告诉程序员和编译器你想要什么。然而,争论中失去了重点。 - Jonathan Seng

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接