47

I am curious as to why float literals must be declared as so:

float f = 0.1f;

Instead of

float f = 0.1;

Why is the default type a double, why can't the compiler infer that it is a float from looking at the leftside of the assignment? Google only turns up explanation on what the default values are, not why they are so.

arynaq
  • 6,710
  • 9
  • 44
  • 74
  • 2
    There is a ton of information about doubles on [this page](http://docs.oracle.com/javase/specs/jls/se7/html/jls-4.html#jls-4.2.3), it is a great read for understanding floats and IEEE 754 better. – RyPope May 04 '13 at 01:40
  • I've always wondered this actually. It appears the issue lies with how the compiler detects decimal-numbers to begin with. It appears to default to double literal...I've always had personal issues with using doubles anyway (why would you need so much precision, even in fields of science or engineering?) – Singular1ty May 04 '13 at 01:40
  • 1
    I think there would be no downside to the compiler being able to recognise that. So as far as I can see there is no real reason behind it. A quick googling didn't point me a valid reason either, but I could be wrong. – DPM May 04 '13 at 01:42
  • 4
    It's the way it was defined. You can invent your own language and do it differently, if you wish. – Hot Licks May 04 '13 at 02:00
  • As to why the compiler can't infer the type, it's because, in the C-like languages, expression evaluation is strictly independent of the use of the expression (though C++ probably can be said to violate this from time to time). – Hot Licks May 04 '13 at 02:02
  • As a point of history, it appears from the Oak spec that `0.0` was originally a `float`, and the `d` suffix was required to make it `double`. [See my answer here](https://stackoverflow.com/a/41283832/3788176). – Andy Turner Oct 02 '19 at 20:01

3 Answers3

40

Why is the default type a double?

That's a question that would be best asked of the designers of the Java language. They are the only people who know the real reasons why that language design decision was made. But I expect that the reasoning was something along the following lines:

They needed to distinguish between the two types of literals because they do actually mean different values ... from a mathematical perspective.

Supposing they made "float" the default for literals, consider this example

// (Hypothetical "java" code ... )
double d = 0.1;
double d2 = 0.1d;

In the above, the d and d2 would actually have different values. In the first case, a low precision float value is converted to a higher precision double value at the point of assignment. But you cannot recover precision that isn't there.

I posit that a language design where those two statements are both legal, and mean different things is a BAD idea ... considering that the actual meaning of the first statement is different to the "natural" meaning.

By doing it the way they've done it:

double d = 0.1f;
double d2 = 0.1;

are both legal, and mean different things again. But in the first statement, the programmer's intention is clear, and the second statement the "natural" meaning is what the programmer gets. And in this case:

float f = 0.1f;
float f2 = 0.1;    // compilation error!

... the compiler picks up the mismatch.


I am guessing using floats is the exception and not the rule (using doubles instead) with modern hardware so at some point it would make sense to assume that the user intends 0.1f when he writes float f = 0.1;

They could do that already. But the problem is coming up with a set of type conversion rules that work ... and are simple enough that you don't need a degree in Java-ology to actually understand. Having 0.1 mean different things in different context would be confusing. And consider this:

void method(float f) { ... }
void method(double d) { ... }

// Which overload is called in the following?
this.method(1.0);

Programming language design is tricky. A change in one area can have consequences in others.


UPDATE to address some points raised by @supercat.

@supercat: Given the above overloads, which method will be invoked for method(16777217)? Is that the best choice?

I incorrectly commented ... compilation error. In fact the answer is method(float).

The JLS says this:

15.12.2.5. Choosing the Most Specific Method

If more than one member method is both accessible and applicable to a method invocation, it is necessary to choose one to provide the descriptor for the run-time method dispatch. The Java programming language uses the rule that the most specific method is chosen.

...

[The symbols m1 and m2 denote methods that are applicable.]

[If] m2 is not generic, and m1 and m2 are applicable by strict or loose invocation, and where m1 has formal parameter types S1, ..., Sn and m2 has formal parameter types T1, ..., Tn, the type Si is more specific than Ti for argument ei for all i (1 ≤ i ≤ n, n = k).

...

The above conditions are the only circumstances under which one method may be more specific than another.

A type S is more specific than a type T for any expression if S <: T (§4.10).

In this case, we are comparing method(float) and method(double) which are both applicable to the call. Since float <: double, it is more specific, and therefore method(float) will be selected.

@supercat: Such behavior may cause problems if e.g. an expression like int2 = (int) Math.Round(int1 * 3.5) or long2 = Math.Round(long1 * 3.5) gets replaced with int1 = (int) Math.Round(int2 * 3) or long2 = Math.Round(long1 * 3)

The change would look harmless, but the first two expressions are correct up to 613566756 or 2573485501354568 and the latter two fail above 5592405 [the last being completely bogus above 715827882].

If you are talking about a person making that change ... well yes.

However, the compiler won't make that change behind your back. For example, int1 * 3.5 has type double (the int is converted to a double), so you end up calling the Math.Round(double).

As a general rule, Java arithmetic will implicitly convert from "smaller" to "larger" numeric types, but not from "larger" to "smaller".

However, you do still need to be careful since (in your rounding example):

  • the product of a integer and floating point may not be representable with sufficient precision because (say) a float has fewer bits of precision than an int.

  • casting the result of Math.round(double) to an integer type can result in conversion to the smallest / largest value of the integer type.

But all of this illustrates that arithmetic support in a programming language is tricky, and there are inevitable gotcha's for a new or unwary programmer.

Stephen C
  • 698,415
  • 94
  • 811
  • 1,216
  • While the difference in precision is clear I suppose it makes sense that they decided to require the users to be explicit about the choice but I wonder for how long it will keep making sense. I am guessing using floats is the exception and not the rule (using doubles instead) with modern hardware so at some point it would make sense to assume that the user intends 0.1f when he writes float f = 0.1; – arynaq May 04 '13 at 13:31
  • Given the above overloads, which method will be invoked for `method(16777217)`? Is that the best choice? – supercat May 13 '14 at 23:05
  • @supercat - I think it would be a compilation error - an "ambiguous method application". (Try it and see ...) – Stephen C May 14 '14 at 05:31
  • @StephenC: I did try it. It invokes without complaint the method for `float` (with value 16777216f), since in the numerical types ranking defined by the existence of explicit and implicit conversions, `float` is "nearer" to `int` (or for that matter `long`) than is `double`. I suspect the reason Java allows implicit float-double conversions but not double-float is to make a method with (float,float) or (double,double) overloads favor the latter when given a float and a double; ironically, however, that same rule causes methods to favor `float` when given an `int` or `long`. – supercat May 14 '14 at 15:17
  • @StephenC: Such behavior may cause problems if e.g. an expression like `int2=(int)Math.Round(int1*3.5);` or `long2=Math.Round(long1*3.5);` gets replaced with `int1=(int)Math.Round(int2*3)` or `long2=Math.Round(long1*3);` The change would look harmless, but the first two expressions are correct up to 613566756 or 2573485501354568 and the latter two fail above 5592405 [the last being *completely* bogus above 715827882]. – supercat May 14 '14 at 15:33
  • (Note: I have addressed these points as an update to my answer.) – Stephen C May 23 '16 at 13:10
2

Ha, this is just the tip of the iceberg my friend.

Programmers coming from other languages certainly don't mind having to add a little F to a literal compared to:

SomeReallyLongClassName x = new SomeReallyLongClassName();

Pretty redundant, right?

It's true that you'd have to talk to core Java developers themselves to get more background. But as a pure surface-level explanation, one important concept to understand is what an expression is. In Java (I'm no expert so take this with a grain of salt), I believe at the compiler level your code is analyzed in terms of expressions; so:

float f

has a type, and

0.1f

also has a type (float).

Generally speaking, if you're going to assign one expression to another, the types must agree. There are a few very specific cases where this rule is relaxed (e.g., boxing a primitive like int in a reference type such as Integer); but in general it holds.

It might seem silly in this case, but here's a very similar case where it doesn't seem so silly:

double getDouble() {
    // some logic to return a double
}

void example() {
    float f = getDouble();
}

Now in this case, we can see that it makes sense for the compiler to spot something wrong. The value returned by getDouble will have 64 bits of precision whereas f can only contain 32 bits; so, without an explicit cast, it's possible the programmer has made a mistake.

These two scenarios are clearly different from a human point of view; but my point about expressions is that when code is first broken down into expressions and then analyzed, they are the same.

I'm sure the compiler authors could have written some not-so-clever logic to re-interpret literals based on the types of expressions they're assigned to; they simply didn't. Probably it wasn't considered worth the effort in comparison to other features.

For perspective, plenty of languages are able to do type inference; in C#, for example, you can do this:

var x = new SomeReallyLongClassName();

And the type of x will be inferred by the compiler based on that assignment.

For literals, though, C# is the same as Java in this respect.

Dan Tao
  • 125,917
  • 54
  • 300
  • 447
0

float has a very little precision, and so a more interesting question is; why it is supported at all? There are rare situations when float can same some memory (if you have millions of them) or you need them for exchanging data with something which expects float.

In general, using double is a better choice, almost as fast for modern PCs, and the memory save is minor compared with the extra precision it gives.

Java doesn't look at the left side in any situation to see how a value is used. e.g. the return type of a method is not part of the signature. It will cast down implicitly in some cases for assignment and operator assignments, but this is usually to keep some compatibility with C and is rather ad hoc IMHO.

Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
  • How can you say "there are rare situations when float can save some memory" when a float uses half the bits as a double?! And how often do you really need double precision? This seems like bad advice to me. – Kingand May 19 '18 at 13:31
  • @Kingand if you have a Float or Double they can use much the same space. Unless you have a large arrays of millions or billions of these, the difference in memory is trivial. – Peter Lawrey May 19 '18 at 14:54