当浮点数不能表示所有int值时,为什么C ++将int提升为浮点数?


70

说我有以下几点:

int i = 23;
float f = 3.14;
if (i == f) // do something

i将被提升为afloat并将两个float数字进行比较,但是a可以float代表所有int值吗?为什么不同时将int和都float提升为double



14
float可以表示范围int值,它只是不能代表那些长于7位准确(并double不能代表超过15位准确)。
Hot Licks 2015年

2
通常,(小于32位的值除外)该规则(类似于大多数其他语言)是将表达式中的值“提升”为最接近的通用表示形式。(此外,3.14永远都不会等于int。)
Hot Licks

3
可能是因为C就是这样做的
fredoverflow

14
当然,还应该考虑到对浮点数==通常是个坏主意...
J Fabian Meier 2015年

Answers:


69

当在整体促销中int被提升为时unsigned,负值也会丢失(这会带来0u < -1真实的乐趣)。

像C中的大多数机制(在C ++中继承)一样,通常的算术转换应从硬件操作的角度进行理解。C的创造者非常熟悉他们所使用的机器的汇编语言,他们编写C的目的是使自己和自己的人在编写直到那时才可以汇编的东西(例如UNIX)时才有意义。核心)。

现在,处理器通常没有混合类型的指令(将float加为double,将int与float比较,等等),因为这将浪费晶圆上的大量资源-您必须实现您想要支持不同类型的操作码数量增加了两倍。仅具有“将int添加到int”,“将float比较为float”,“将unsigned乘以unsigned”等指令首先需要进行常规的算术转换-它们是两种类型的指令映射与他们一起使用最有意义的家庭。

从习惯于编写低级机器代码的人的角度来看,如果您有混合类型,那么一般情况下最有可能考虑的汇编指令是那些需要最少转换的指令。浮点运算尤其如此,因为转换运算会耗费大量运行时间,尤其是在1970年代初,开发C语言时,计算机运行缓慢,并且浮点运算是在软件中完成的。这表明在通常的算术转换中,仅对一个操作数进行了转换(long/的唯一例外是/ unsigned int,其中long可以转换为unsigned long,这在大多数机器上不需要执行任何操作。 )。

因此,编写通常的算术转换来执行汇编编码器在大多数情况下会执行的操作:您有两种不合适的类型,将一种转换为另一种类型即可。除非有特殊原因,否则这是您在汇编程序代码中要执行的操作,除非您是习惯于编写汇编程序代码并确实有特定原因要强制执行不同转换的人员,否则明确要求转换是自然的。毕竟,您可以简单地写

if((double) i < (double) f)

有趣的是,注意在此背景下,顺便说一句,这unsigned是分级高于int,这样比较intunsigned将在一个无符号比较(因此结束0u < -1从一开始就位)。我怀疑这是一个指标,表明人们在过去将其unsigned视为对限制的限制而int不是对其值范围的扩展:我们现在不需要符号,因此让我们将多余的位用于更大的值范围。如果您有理由期望anint会溢出,则可以使用它-在16位ints的世界中,这是一个更大的担忧。


1
“丢失”……“应该从硬件操作的角度理解”……不,考虑到允许有效实施的局限性,它们在算术上独立于硬件操作而定义。取1s补码和符号幅度的llok。
Deduplicator

36
我应该认为,无可否认,就当今的硬件而言,可以理解C早期开发中的许多设计决策。不要将它们与当今的标准化程序相混淆-到C标准化时,已经发生了20年不受控制的编译器增长,并且委员会必须将著名的编译器正在做的事情编纂成文。他们当然以与硬件​​无关的方式写下来,但这是它与硬件考虑因素密切相关的原因。如果C作为规范开始,情况可能会有所不同。
Wintermute 2015年

3
那是一堵文字墙。如何添加TL; DR部分或类似内容?
哥伦布2015年

1
我是编码的新手,也是因为整数可以表示为浮点数,因为它只有整数,但是不能将浮点数表示为int,因为它可以有小数,而INT不支持?有人知道吗
Ahkam Nihardeen

6
@Akham基本上是这样的:int-> float有时是完美的,并且在主观上比“ float-> int”的错误更少。我还假设在1970年代,int-> float对于所有int而言通常都是无损的,例如在PDP 11上只有16位。从今天的角度出发,OP一直在怀疑int具有32位,以便不能在单个精度浮点数中精确表示较大的数字,因此默认情况下为什么在大多数情况下有损转换是默认的。
彼得-恢复莫妮卡2015年

13

甚至double可能无法表示所有int值,具体取决于int包含的位数。

为什么不将int和float都提升为两倍呢?

可能是因为它更昂贵的两种类型的转换double比操作数使用一个,这已经是一个float作为float。它还将为比较运算符引入与算术运算符规则不兼容的特殊规则。

也不能保证浮点类型将如何表示,因此假定转换intdouble(甚至long double)进行比较将解决任何问题,这是盲目的。


10

类型提升规则被设计为简单且以可预测的方式工作。C / C ++中的类型自然按照它们可以表示的值范围进行“排序” 。请参阅了解详情。尽管浮点类型不能表示整数类型表示的所有整数,因为它们不能表示相同数量的有效数字,但它们可能能够表示更大的范围。

为了具有可预测的行为,当需要类型提升时,数字类型始终会转换为范围较大的类型,以避免较小的类型溢出。想象一下:

int i = 23464364; // more digits than float can represent!
float f = 123.4212E36f; // larger range than int can represent!
if (i == f) { /* do something */ }

如果对整数类型进行转换,则将floatf转换为int时肯定会溢出,从而导致未定义的行为。另一方面,转换if仅会导致精度下降,这无关紧要,因为f精度相同,因此比较仍然有可能成功。此时,程序员应根据应用程序的要求来解释比较的结果。

最后,除了双精度浮点数会遇到表示整数(有限的有效数字位数)的相同问题外,在这两种类型上使用推广也会导致的精度更高i,而f注定要具有原始精度,因此,如果i有效位数比f开始时的数字多,则比较将不会成功。现在,这也是未定义的行为:比较可能对某些夫妇(if)成功,但对另一些夫妇却不成功。


这些类型按其最大值而不是范围的大小排序。intunsigned,例如,具有同样大的范围。
nwellnhof

@nwellnhof我认为您是对的:我相信编译器会根据lvalue的类型将an解释unsigned intintor,反之亦然。
乔瓦尼·博塔 Giovanni Botta)2015年

8

可以float代表所有int值吗?

对于一个典型的现代系统,其中intfloat都以32位存储。一定要给的东西。价值32位的整数不会一对一映射到包含小数的相同大小的集合上。

i将被提升到一个float和两个float数字进行比较...

不必要。您真的不知道将采用哪种精度。C ++ 14§5/ 12:

浮动操作数的值和浮动表达式的结果可以比类型所需的精度和范围大。类型不会因此改变。

虽然 i升级后具有名义类型float,但该值可以使用double硬件表示。C ++不保证浮点精度损失或溢出。(这在C ++ 14中不是什么新事物;它自古以来就继承自C。)

为什么不同时推广 int和都float提升为double

如果您想在任何地方都获得最佳精度,请改用,double而永远不会看到float。或long double,但运行速度可能较慢。考虑到一台机器可以提供多种精度,这些规则被设计为对于大多数有限精度类型的用例而言相对合理。

在大多数情况下,快速和宽松就足够了,因此机器可以自由地执行最简单的操作。这可能意味着四舍五入,单精度比较或双精度而不进行四舍五入。

但是,这样的规则最终是折衷的,有时会失败。要在C ++(或C)中精确指定算术,它有助于使转换和提升明确化。许多关于超可靠软件的样式指南都禁止完全使用隐式转换,并且大多数编译器会提供警告来帮助您删除它们。

要了解这些妥协是如何产生的,可以阅读C基本原理文档。(最新版本最多涵盖C99。)从PDP-11或K&R时代起,这不只是笨拙的包bag。


@FractalizeR谢谢……我参加聚会很晚,所以我们将看看会发生什么。
Potatoswatter 2015年

从历史上看,使用onlong double进行的计算通常比将double其定义为不同类型的计算机要快。如果ANSI C提供了一种方法,可变参数函数的原型可以通过该方法指定将所有浮点值都转换为的类型(默认值为double),那么可以说将对boost的所有操作double转换为long double(这可能会或可能不会比大double),并且所有业务float推广到long float(可能是floatlong double或任何在两者之间)。
2015年

不幸的是,printf关于类型的破坏性行为long double导致该类型不适合其最初的预期用途,因为它阻止了将double操作数干净地提升到long double甚至使用long double上比using更快的处理器上double
2015年

@supercat那里有一些误解。1.浮点操作数的提升不像整数的那样:表达式的1.f + 1.f类型float不是double。C varargs很特殊。2.中间结果的精度与它们的浮点类型无关:很1.f + 1.e-10f - 1.f可能是1.e-10f。3.如果doublelong double都符合IEEE 754,则它们分别为64和{80或128位}。如今,大多数FPU都是64位宽,位于矢量数据路径内。无论long double在任何时间或任何地方,这种速度都不过是历史上的fl幸而已。
Potatoswatter

另外,IIRClong double是C语言的最新成员,大约C89。printf引入它时,如果不破坏每个ABI或对其自身进行琐碎处理,就不可能将其设置为默认格式。
Potatoswatter

6

令人着迷的是,这里的许多答案都来自C语言的起源,明确地将K&R和历史包名命名为将int与float结合时转换为float的原因。

这是指责错误的政党。在K&R C中,没有浮点计算之类的东西。 所有浮点运算均以双精度进行。因此,从未将整数(或其他任何东西)隐式转换为浮点数,而仅将其转换为双精度数。浮点数也不可能是函数参数的类型:如果确实要避免转换为双精度数,则必须传递一个指向浮点数的指针。因此,功能

int x(float a)
{ ... }

int y(a)
float a;
{ ... }

有不同的调用约定。第一个获取一个float参数,第二个获取(现在不再作为语法允许)一个double参数。

单精度浮点算术和函数参数仅在ANSI C中引入。Kernighan/ Ritchie是无辜的。

现在,有了新可用的单个float表达式(以前的单个float仅是一种存储格式),还必须进行新的类型转换。不管ANSI C团队在这里选择了什么(我会为更好的选择而迷失)不是K&R的错。


在K&R C中如何调用该双精度类型?我想这不应该是double,否则也应该singlefloat已经存在。
Ruslan

@Ruslan:你理解错了。他只是在谈论浮动操作,而不是浮动存储
塞巴斯蒂安·马赫2015年

@Ruslan:按照最初的意图,在C中,对浮点类型进行的所有操作都会将所有操作数转换为精度最高的类型(碰巧是double),对其进行操作,然后将最终结果转换回指定的类型(即最初可能是floatdouble)。在没有浮点单元的机器上,与对不同的浮点类型使用单独编码的操作相比,这种方法通常需要更少的代码,并且通常还会产生更好的结果。对于某些类型的应用程序,以其他方式执行操作可能会有所优势...
supercat

...但是像f5=f1*f2+f3*f4通过计算双精度乘积然后执行加法一样对表达式求值将很容易产生0.51ulp的精度;在不提高精度的情况下,即使要确保1ulp的精度double通常也要困难得多。
超级猫

5

Q1:浮点数可以表示所有int值吗?

IEE754可以将所有整数精确地表示为浮点数,最多约2 23,如本答案所述

问题2:为什么不将int和float都提高到两倍?

这些转换的标准中的规则是对K&R中的那些规则的略微修改:这些修改包含添加的类型和值保留规则。添加了显式许可证,以比绝对必要的“更广泛”的类型执行计算,因为这有时会产生更小和更快的代码,更不用说正确答案了。只要获得相同的最终结果,也可以按照规则使用“更窄”的类型执行计算。显式强制转换始终可以用于获取所需类型的值。

资源

以更广泛的类型执行计算意味着 float f1;float f2;f1 + f2可能在计算double精度。这也意味着,鉴于int i;并且float f;i == f可能在计算double精度。但是i == f,如注释中所述,不需要以双精度计算。

C标准也这样说。这些被称为通常的算术转换。以下描述直接取自ANSI C标准。

...如果任何一个操作数具有float类型,则另一个操作数将转换为float类型。

来源,您也可以在参考资料中看到它。

相关的链接是这里 答案。更多分析的资料在这里

这是另一种解释方法:隐式执行通常的算术转换,以将其值转换为通用类型。编译器首先执行整数提升,如果操作数仍然具有不同的类型,则它们将转换为以下层次结构中显示最高的类型:

在此处输入图片说明

来源


2
“因为C标准是这样说的。” 甚至不能回答C为什么这样工作的问题,更不用说回答C ++为什么如此工作的问题了。

@hvd,您在编辑上引起了我的注意,我认为它现在有所改善。:)
gsamaras

2
是的,那绝对是一个进步。不过,这并不是问题所要问的。执行计算在更宽的类型装置,其给定的float f1;float f2;f1 + f2可能在计算double精度。这也意味着,鉴于int i;并且float f;i == f可能在计算double精度。但它并不需要计算i == fdouble精度,这个问题问为什么不。

哦,你说得对@hvd。如果您认为我应该删除答案,请告诉我。
gsamaras 2015年

2
通常,改善答案要好于删除答案:)您的答案中确实包含了相关信息,我认为值得保留。(FWIW,我也没有很好的答案,否则我已经发布了。)

3

创建编程语言后,会直观地做出一些决定。

例如,为什么不将int + float转换为int + int而不是float + float或double + double?如果int-> float拥有相同的位数,为什么要调用它呢?为什么不将float-> int称为促销?

如果您依赖隐式类型转换,则应该知道它们的工作方式,否则只需手动转换即可。

本来可以设计某种语言而根本不进行任何自动类型转换。在设计阶段,并非每个决定都可以在逻辑上有充分的理由作出。

带有鸭子类型的JavaScript在引擎盖下的决策更加晦涩难懂。设计绝对逻辑的语言是不可能的,我认为这取决于Godel不完全性定理。您必须在逻辑,直觉,实践和理想之间取得平衡。


1
“如果拥有相同的位数,为什么要调用int-> float促销呢?” 这不称为促销。
Jonathan Wakely 2015年

1
使javascript比较古怪的属性是其类型,而不是鸭子类型。鸭≈动态的。
伊夫2015年

@Eevee我会说鸭子≈无类型多态性。
doc

2

问题是为什么:因为它速度快,易于解释,易于编译,而这些都是在开发C语言时非常重要的原因。

您可能有不同的规则:对于算术值的每次比较,其结果都是对实际数值的比较。如果比较的表达式之一是常量,则比较琐碎。在比较有符号和无符号的int时,这是一条额外的指令;如果比较long long和double并在long long无法表示为double时想要正确的结果,则这将非常困难。(0u <-1将是错误的,因为它将比较数值0和-1而不考虑其类型)。

在Swift中,通过禁止不同类型之间的操作可以轻松解决该问题。


2

规则以16位整数(最小所需大小)编写。具有32位int的编译器肯定会将双方都转换为double。无论如何,现代硬件中都没有浮点寄存器,因此必须将其转换为双精度。现在,如果您有64位整数,则不太确定它会做什么。长双精度会比较合适(通常为80位,但这不是标准)。


不要以为每台机器都是PC或Mac。有很多16/32位硬件在使用,它们具有32位FPU寄存器或根本没有FPU。甚至8位现代硬件(例如AVR CPU),甚至都没有积分的硬件划分,更不用说浮点单元了。这些CPU嵌入在您周围的各种“愚蠢”设备中。您被这些东西(路由器,电梯,交通信号灯控制器,打印机...)包围着。它们通常是用C或C ++编程的,所以请不要低估低端产品-它可能比闪亮的Haswell台式机更为重要。
doc

我还没有看到int不是16位的这样的。
2015年

int按标准至少是16位,但是...-> gcc.gnu.org/wiki/avr-gcc
doc

@doc:那里也是16位。
2015年

8位int与-mint8 With -mint8 int is only 8 bits wide which does not comply to the C standard
商务部
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.