我很好奇为什么浮动文字必须这样声明:
float f = 0.1f;
代替
float f = 0.1;
为什么默认类型是双精度,为什么编译器不能从赋值左侧推断它是浮点数?谷歌只解释默认值是什么,而不是为什么会这样。
我很好奇为什么浮动文字必须这样声明:
float f = 0.1f;
代替
float f = 0.1;
为什么默认类型是双精度,为什么编译器不能从赋值左侧推断它是浮点数?谷歌只解释默认值是什么,而不是为什么会这样。
为什么默认类型是双精度的?
这是最好向 Java 语言的设计者提出的问题。他们是唯一知道做出该语言设计决定的真正原因的人。但我希望推理大致如下:
他们需要区分这两种文字,因为从数学的角度来看,它们实际上确实意味着不同的值。
假设他们将“浮动”作为文字的默认值,请考虑这个例子
// (Hypothetical "java" code ... )
double d = 0.1;
double d2 = 0.1d;
在上面,d
andd2
实际上会有不同的值。在第一种情况下,在分配点将低精度float
值转换为更高精度double
值。但是您无法恢复不存在的精度。
我认为这两个语句都是合法的并且意味着不同的事物的语言设计是一个坏主意……考虑到第一个语句的实际含义与“自然”含义不同。
按照他们的做法:
double d = 0.1f;
double d2 = 0.1;
都是合法的,并且再次意味着不同的东西。但是在第一个语句中,程序员的意图是明确的,而第二个语句“自然”的含义是程序员得到的。在这种情况下:
float f = 0.1f;
float f2 = 0.1; // compilation error!
...编译器拾取不匹配。
我猜使用浮点数是例外,而不是现代硬件的规则(使用双精度数),因此在某些时候假设用户在编写时打算使用 0.1f 是有意义的
float f = 0.1;
他们已经可以做到了。但是问题在于提出了一组有效的类型转换规则......并且足够简单,以至于您不需要 Java 学学位即可真正理解。在0.1
不同的上下文中意味着不同的事物会令人困惑。考虑一下:
void method(float f) { ... }
void method(double d) { ... }
// Which overload is called in the following?
this.method(1.0);
编程语言设计很棘手。一个领域的变化可能会对其他领域产生影响。
更新以解决@supercat 提出的一些问题。
@supercat:鉴于上述重载,将为方法(16777217)调用哪个方法?那是最好的选择吗?
我错误地评论了...编译错误。事实上答案是method(float)
。
JLS 是这样说的:
如果多个成员方法既可访问又适用于方法调用,则有必要选择一个为运行时方法分派提供描述符。Java 编程语言使用选择最具体方法的规则。
...
[符号 m1 和 m2 表示适用的方法。]
[如果] m2 不是泛型,并且 m1 和 m2 可以通过严格或 松散调用来应用,并且 m1 具有形参类型 S1、...、Sn 且 m2 具有形参类型 T1、...、Tn,则类型对于所有 i (1 ≤ i ≤ n, n = k),对于自变量 ei,Si 比 Ti 更具体。
...
上述条件是唯一一种方法可能比另一种方法更具体的情况。
如果 S <: T (§4.10),对于任何表达式,类型 S 比类型 T 更具体。
在这种情况下,我们正在比较method(float)
哪些method(double)
都适用于调用。由于float
<: double
,它更具体,因此method(float)
将被选中。
int2 = (int) Math.Round(int1 * 3.5)
@supercat:这种行为可能会导致问题,例如,如果像or的表达式long2 = Math.Round(long1 * 3.5)
被替换为int1 = (int) Math.Round(int2 * 3)
orlong2 = Math.Round(long1 * 3)
这种变化看起来是无害的,但是前两个表达式在
613566756
or之前是正确2573485501354568
的,而后两个表达式在上面失败5592405
了[最后一个在上面完全是假的715827882
]。
如果你说的是一个人做出这样的改变……嗯,是的。
但是,编译器不会在你背后做出改变。例如,int1 * 3.5
has 类型double
(int
转换为 a double
),因此您最终调用Math.Round(double)
.
作为一般规则,Java 算术将隐式从“较小”转换为“较大”数字类型,但不会从“较大”转换为“较小”。
但是,您仍然需要小心,因为(在您的舍入示例中):
整数和浮点的乘积可能无法以足够的精度表示,因为(比如说) afloat
的精度位比 a 少int
。
将结果Math.round(double)
转换为整数类型会导致转换为整数类型的最小值/最大值。
但是所有这些都说明编程语言中的算术支持是棘手的,对于新手或粗心的程序员来说,不可避免地会遇到问题。
哈,这只是我朋友的冰山一角。
与以下相比,来自其他语言的程序员当然不介意在F
文字上添加一些内容:
SomeReallyLongClassName x = new SomeReallyLongClassName();
相当多余,对吧?
确实,您必须亲自与核心 Java 开发人员交谈以获得更多背景知识。但作为纯粹的表层解释,要理解的一个重要概念是表达式是什么。在 Java 中(我不是专家,所以对此持保留态度),我相信在编译器级别,您的代码是根据表达式进行分析的;所以:
float f
有一个类型,并且
0.1f
还有一个类型 ( float
)。
一般来说,如果您要将一个表达式分配给另一个表达式,则类型必须一致。有一些非常具体的情况可以放宽此规则(例如,将原始类型装箱为int
引用类型,例如Integer
);但总的来说它是成立的。
在这种情况下可能看起来很傻,但这是一个非常相似的情况,它看起来并不那么傻:
double getDouble() {
// some logic to return a double
}
void example() {
float f = getDouble();
}
现在在这种情况下,我们可以看到编译器发现错误是有意义的。返回的值getDouble
将具有 64 位精度,而f
只能包含 32 位;因此,如果没有明确的演员表,程序员可能犯了错误。
从人类的角度来看,这两种情况明显不同;但我关于表达式的观点是,当代码首先分解为表达式然后分析时,它们是相同的。
我确信编译器作者可能已经编写了一些不太聪明的逻辑来根据分配给它们的表达式类型重新解释文字;他们根本没有。与其他功能相比,可能不值得付出努力。
从角度来看,很多语言都能够进行类型推断。例如,在 C# 中,您可以这样做:
var x = new SomeReallyLongClassName();
并且类型x
将由编译器根据该赋值来推断。
但是,对于文字,C# 在这方面与 Java 相同。
float
精度很低,所以一个更有趣的问题是;为什么完全支持它?在极少数情况下float
可以相同的一些内存(如果你有数百万个)或者你需要它们来与期望浮动的东西交换数据。
一般来说,使用double
是更好的选择,几乎与现代 PC 一样快,并且与它提供的额外精度相比,内存节省很小。
Java 在任何情况下都不会查看左侧来查看值是如何使用的。例如,方法的返回类型不是签名的一部分。在某些情况下,它会隐式地用于赋值和运算符赋值,但这通常是为了保持与 C 的一些兼容性,并且是临时的恕我直言。