为什么当float不能表示所有的int值时,C ++将int提升为float?
说我有以下几点:
int i = 23; float f = 3.14; if (i == f) // do something
i
将被提升为一个float
,并将两个float
进行比较,但float
是否可以表示所有的int
值? 为什么不把int
和float
提升为double
?
当int
在积分促销中被提升为unsigned
时,负值也被丢失(这导致0u < -1
为真)这样的乐趣。
像C中的大多数机制一样(在C ++中inheritance),通常的算术转换应该根据硬件操作来理解。 C的制造者对他们工作的机器的汇编语言非常熟悉,他们写C使他们自己和像他们这样的人在编写那些以前用汇编写的东西(比如UNIX核心)。
现在,处理器通常不会有混合types的指令(将float添加到double,将int与float进行比较等),因为这会浪费晶圆上的不动产 – 您必须执行您希望支持不同types的操作码的次数更多。 你只有“加int到int”,“比较浮点数到浮点数”,“乘以无符号数和无符号数”等指令使得通常的算术转换在第一位是必须的 – 它们是指令的两种types的映射家庭是最有意义的使用他们。
从习惯于编写低级机器代码的人的angular度来看,如果您使用的是混合types,那么在一般情况下,您最可能考虑的汇编程序指令是那些需要最less转换的指令。 在浮点运算的情况下尤其如此,其中转换运行时代是昂贵的,特别是在20世纪70年代早期,当时C被开发出来,计算机运行缓慢,以及在软件中进行浮点计算时。 这在通常的算术转换中显示 – 只有一个操作数是经过转换的(除了long
/ unsigned int
, long
可以转换为unsigned long
,在大多数机器上不需要做任何事情。在适用例外情况下)。
所以,通常的算术转换是为了完成汇编代码大部分时间所做的事情而编写的:您有两种不适合的types,将其转换为另一种。 这是你在汇编代码中要做的事情,除非你有特殊的理由不这样做,对于那些习惯于编写汇编代码的人来说, 他们有特定的理由强迫进行不同的转换,明确要求转换是自然的。 毕竟,你可以简单地写
if((double) i < (double) f)
顺便提一句,在这种情况下,值得注意的是, unsigned
在层次结构中高于int
,因此比较int
和unsigned
将以无符号比较(因此从开始0u < -1
位)结束。 我怀疑这是一个指标,以前的人认为unsigned
限制是对int
的限制,而不是对它的值范围的扩展:现在我们不需要这个符号,所以让我们使用额外的位来获取更大的值范围。 如果你有理由期待int
会溢出,那么你会使用它 – 在16位int
s的世界中更大的担心。
即使double
可能也不能表示所有的int
值,这取决于int
包含多less位。
为什么不把int和float都提升为double?
可能是因为将两种types转换为double
比使用其中一个已经是float
的操作数作为float
更昂贵。 它还会引入与算术运算符规则不相容的比较运算符的特殊规则。
也不能保证如何表示浮点types,所以假设将int
转换为double
(甚至long double
)来进行比较将会解决任何问题。
types提升规则的devise很简单,并以可预测的方式工作。 C / C ++中的types自然可以用它们可以表示的值的范围来 “sorting”。 详情请参阅此 。 尽pipe浮点types不能表示由整型表示的所有整数,因为它们不能表示相同数量的有效数字,但它们可能代表更宽的范围。
为了具有可预测的行为,当需要types提升时,数字types总是被转换为具有较大范围的types,以避免较小范围的溢出。 想象一下:
int i = 23464364; // more digits than float can represent! float f = 123.4212E36f; // larger range than int can represent! if (i == f) { /* do something */ }
如果转换是对整型进行的,float f
肯定会在转换为int时溢出,导致未定义的行为。 另一方面,将i
转换为f
只会导致精度的损失,这是不相关的,因为f
具有相同的精度,因此比较成功的可能性仍然存在。 这时由程序员根据应用需求来解释比较结果。
最后,除了双精度浮点数遭遇相同问题表示整数(有限数字有效数字)的事实之外,在这两种types上使用提升会导致对i
有更高的精度表示,而f
注定要有原始精度,所以比较不会成功,如果i
有一个更有效的数字比f
开始。 现在这也是未定义的行为:比较可能会成功的一些夫妇( i
, f
),但不为别人。
可以
float
代表所有的int
值吗?
对于一个典型的现代系统, int
和float
都存储在32位中, 有些事情要付出 32位的整数值不会将1对1映射到包含分数的相同大小的集合上。
i
将被提升为一个float
,两个float
数字将被比较…
不必要。 你不知道会应用什么精度。 C ++ 14§5/ 12:
浮点操作数的值和浮点expression式的结果可以以比该types所要求的更高的精度和范围来表示; types不会因此而改变。
虽然i
在升级后有名义typesfloat
,但可能使用double
硬件表示值。 C ++不保证浮点精度丢失或溢出。 (这在C ++ 14中并不新鲜,从古代开始就是从Cinheritance而来的)。
为什么不把
int
和float
提升为double
?
如果你想要在任何地方达到最佳的精度,那就用double
来代替,而且你永远不会看到float
或long double
,但可能运行速度较慢。 对于大多数有限精度types的使用案例来说,规则的devise是相对明智的,因为考虑到一台机器可以提供多种备选精度。
大多数时候,快速和松散是足够好的,所以机器可以自由地做任何最简单的事情。 这可能意味着四舍五入的单精度比较,或双精度和四舍五入。
但是,这样的规则是最终的妥协,有时会失败。 要精确指定C ++(或C)中的算术,有助于明确地进行转换和提升。 许多用于额外可靠软件的样式指南完全禁止使用隐式转换,大多数编译器会提供警告来帮助您清除它们。
要了解这些妥协是如何产生的,可以仔细阅读C基本原理文档 。 (最新版本涵盖了C99)。PDP-11或K&R的日子里,这不仅仅是无谓的包袱。
令人着迷的是,这里的许多答案都是从C语言的起源出发,明确地将K&R和历史包袱命名为int与float相结合转换为float的原因。
这是指责错误的一方。 在K&R C中,没有像浮点计算那样的东西。 所有的浮点运算都是以双精度完成的。 出于这个原因,一个整数(或任何其他)从来没有隐式转换为浮点数,但只有一个双。 一个float也不能是一个函数参数的types:如果你真的真的想要避免转换成double,你必须传递一个指针来浮动。 出于这个原因,function
int x(float a) { ... }
和
int y(a) float a; { ... }
有不同的调用约定。 第一个得到一个浮动参数,第二个(现在不再允许作为语法)得到一个双重的参数。
单精度浮点算术和函数参数只有ANSI C引入。Kernighan / Ritchie是无辜的。
现在使用新的单浮点expression式 (之前的单浮点数只是一个存储格式),也必须有新的types转换。 无论ANSI C团队在这里挑选什么(而且我会为了一个更好的select而蒙受损失)并不是K&R的错。
Q1:float可以表示所有的int值吗?
如本答案所述,IEE754可以完全表示浮点数的所有整数,最多约为233。
Q2:为什么不把int和float都提升为double?
标准中对这些转换的规则是对K&R中规则的轻微修改:这些修改适应添加的types和保值规则。 显式许可证被添加到执行“更宽”types的计算中,而不是绝对必要的,因为这有时会产生更小更快的代码,更不用说正确的答案。 只要得到相同的最终结果,计算也可以按“规则”的规则进行。 总是可以使用明确的转换来获得所需types的值。
资源
以更宽的types进行计算意味着给定的float f1;
并float f2;
, f1 + f2
可能以double
精度计算。 这意味着,给int i;
和float f;
, i == f
可能会计算在double
精度。 但是不要求以双精度来计算i == f
,因为hvd在注释中说明了。
另外C标准是这样说的。 这些被称为通常的算术转换。 以下描述直接从ANSI C标准中获取。
…如果任一操作数的types为float,则另一个操作数的types为float。
来源 ,你也可以在裁判看到它。
相关链接是这个答案 。 更多的分析来源在这里 。
下面是解释这个的另一种方法:通常的算术转换是隐式执行的,以通用types转换它们的值。 编译器首先执行整数提升,如果操作数仍然有不同的types,那么它们被转换为在以下层次结构中出现最高的types:
来源 。
当创build编程语言时,直观地做出一些决定。
例如,为什么不将int + float转换为int + int而不是float + float或double + double? 为什么调用int->浮动促销,如果它拥有相同的位? 为什么不调用float-> int升级?
如果您依赖隐式types转换,您应该知道它们是如何工作的,否则只需手动转换。
有些语言可能根本没有任何自动types转换。 并不是在devise阶段的每一个决定都可以在逻辑上有一个很好的理由。
用鸭子打字的JavaScript更隐蔽了。 devise一个绝对合乎逻辑的语言是不可能的,我想这是Godel不完备性定理。 你必须平衡逻辑,直觉,实践和理想。
问题是为什么:因为它快速,易于解释,易于编译,而这些都是在C语言开发的时候非常重要的原因。
你可能有一个不同的规则:对于每一个算术值的比较,结果是比较实际的数值。 如果比较的expression式之一是常量,那么比较有符号和无符号整数时,这将是一个微不足道的地方,如果比较long long和double并且在long long不能表示为double时想要正确的结果,则相当困难。 (0u <-1将是错误的,因为它会比较数值0和-1而不考虑它们的types)。
在Swift中,这个问题很容易通过不允许不同types的操作来解决。
规则是写入16位整数(最小所需的大小)。 您的编译器与32位整数肯定会转换双方双。 现代硬件中没有浮点寄存器,所以它必须转换为双精度。 现在,如果你有64位整数,我不太确定它做了什么。 长双倍将是适当的(通常80位,但它甚至不是标准)。