说我有以下几点:
int i = 23;
float f = 3.14;
if (i == f) // do something
i
将被提升为afloat
并将两个float
数字进行比较,但是a可以float
代表所有int
值吗?为什么不同时将int
和都float
提升为double
?
说我有以下几点:
int i = 23;
float f = 3.14;
if (i == f) // do something
i
将被提升为afloat
并将两个float
数字进行比较,但是a可以float
代表所有int
值吗?为什么不同时将int
和都float
提升为double
?
float
可以表示范围的int
值,它只是不能代表那些长于7位准确(并double
不能代表超过15位准确)。
int
。)
Answers:
当在整体促销中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
,这样比较int
有unsigned
将在一个无符号比较(因此结束0u < -1
从一开始就位)。我怀疑这是一个指标,表明人们在过去将其unsigned
视为对限制的限制而int
不是对其值范围的扩展:我们现在不需要符号,因此让我们将多余的位用于更大的值范围。如果您有理由期望anint
会溢出,则可以使用它-在16位int
s的世界中,这是一个更大的担忧。
类型提升规则被设计为简单且以可预测的方式工作。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时肯定会溢出,从而导致未定义的行为。另一方面,转换i
为f
仅会导致精度下降,这无关紧要,因为f
精度相同,因此比较仍然有可能成功。此时,程序员应根据应用程序的要求来解释比较的结果。
最后,除了双精度浮点数会遇到表示整数(有限的有效数字位数)的相同问题外,在这两种类型上使用推广也会导致的精度更高i
,而f
注定要具有原始精度,因此,如果i
有效位数比f
开始时的数字多,则比较将不会成功。现在,这也是未定义的行为:比较可能对某些夫妇(i
,f
)成功,但对另一些夫妇却不成功。
int
和unsigned
,例如,具有同样大的范围。
可以
float
代表所有int
值吗?
对于一个典型的现代系统,其中int
和float
都以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。
long double
进行的计算通常比将double
其定义为不同类型的计算机要快。如果ANSI C提供了一种方法,可变参数函数的原型可以通过该方法指定将所有浮点值都转换为的类型(默认值为double
),那么可以说将对boost的所有操作double
转换为long double
(这可能会或可能不会比大double
),并且所有业务float
推广到long float
(可能是float
,long double
或任何在两者之间)。
printf
关于类型的破坏性行为long double
导致该类型不适合其最初的预期用途,因为它阻止了将double
操作数干净地提升到long double
甚至使用long double
上比using更快的处理器上double
。
1.f + 1.f
类型float
不是double
。C varargs很特殊。2.中间结果的精度与它们的浮点类型无关:很1.f + 1.e-10f - 1.f
可能是1.e-10f
。3.如果double
和long double
都符合IEEE 754,则它们分别为64和{80或128位}。如今,大多数FPU都是64位宽,位于矢量数据路径内。无论long double
在任何时间或任何地方,这种速度都不过是历史上的fl幸而已。
long double
是C语言的最新成员,大约C89。printf
引入它时,如果不破坏每个ABI或对其自身进行琐碎处理,就不可能将其设置为默认格式。
令人着迷的是,这里的许多答案都来自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的错。
double
,否则也应该single
或float
已经存在。
double
),对其进行操作,然后将最终结果转换回指定的类型(即最初可能是float
或double
)。在没有浮点单元的机器上,与对不同的浮点类型使用单独编码的操作相比,这种方法通常需要更少的代码,并且通常还会产生更好的结果。对于某些类型的应用程序,以其他方式执行操作可能会有所优势...
f5=f1*f2+f3*f4
通过计算双精度乘积然后执行加法一样对表达式求值将很容易产生0.51ulp的精度;在不提高精度的情况下,即使要确保1ulp的精度double
通常也要困难得多。
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类型。
这是另一种解释方法:隐式执行通常的算术转换,以将其值转换为通用类型。编译器首先执行整数提升,如果操作数仍然具有不同的类型,则它们将转换为以下层次结构中显示最高的类型:
来源。
float f1;
和float f2;
,f1 + f2
可能在计算double
精度。这也意味着,鉴于int i;
并且float f;
,i == f
可能在计算double
精度。但它并不需要计算i == f
的double
精度,这个问题问为什么不。
创建编程语言后,会直观地做出一些决定。
例如,为什么不将int + float转换为int + int而不是float + float或double + double?如果int-> float拥有相同的位数,为什么要调用它呢?为什么不将float-> int称为促销?
如果您依赖隐式类型转换,则应该知道它们的工作方式,否则只需手动转换即可。
本来可以设计某种语言而根本不进行任何自动类型转换。在设计阶段,并非每个决定都可以在逻辑上有充分的理由作出。
带有鸭子类型的JavaScript在引擎盖下的决策更加晦涩难懂。设计绝对逻辑的语言是不可能的,我认为这取决于Godel不完全性定理。您必须在逻辑,直觉,实践和理想之间取得平衡。
问题是为什么:因为它速度快,易于解释,易于编译,而这些都是在开发C语言时非常重要的原因。
您可能有不同的规则:对于算术值的每次比较,其结果都是对实际数值的比较。如果比较的表达式之一是常量,则比较琐碎。在比较有符号和无符号的int时,这是一条额外的指令;如果比较long long和double并在long long无法表示为double时想要正确的结果,则这将非常困难。(0u <-1将是错误的,因为它将比较数值0和-1而不考虑其类型)。
在Swift中,通过禁止不同类型之间的操作可以轻松解决该问题。
规则以16位整数(最小所需大小)编写。具有32位int的编译器肯定会将双方都转换为double。无论如何,现代硬件中都没有浮点寄存器,因此必须将其转换为双精度。现在,如果您有64位整数,则不太确定它会做什么。长双精度会比较合适(通常为80位,但这不是标准)。
int
按标准至少是16位,但是...-> gcc.gnu.org/wiki/avr-gcc
With -mint8 int is only 8 bits wide which does not comply to the C standard