Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Applying casts to the results of integer and floating point division: what's going on here?

I'm a beginner and there's something that's not making much sense to me. Please could be so kind as to explain where I'm going wrong. I'm sorry if this has been asked before.

Here the presence of the decimal point mean these get evaluated using floating point division.

System.out.println(1/3.0); // this prints: 0.3333333333333333
System.out.println(1.0/3); // this prints: 0.3333333333333333
System.out.println(1.0/3.0); // this prints: 0.3333333333333333

Apparently, this below is an example of "truncating-integer division." It seems a bit weird to me but ok.

System.out.println(1/3); // this prints: 0

Is it ok to say: "in the line below, the (double) cast is evaluated 1st. It effectively says: "treat 1/3 as a double - don't use truncating integer division. instead use floating point division.""

System.out.println((double)1/3); // this prints: 0.3333333333333333

Below, however we get 0.0 - how did that happen?

System.out.println((double)(1/3)); // this prints: 0.0

ok so maybe the extra parentheses mean the (1/3) gets evaluated 1st. It is evaluated using truncating integer division yielding 0. And then the double is applied giving us 0.0 Ok that makes sense

Ok so we maybe can propose a couple of general rules here:

Rule 1: (double) expression means apply the (double) cast first then evaluate the expression.

Rule 2: (double) (expression) means evaluate the expression then apply the cast. Great!

So in the next line below we have: (int) expression, so I guess we can apply rule 1). The (int) cast is evaluated 1st. It effectively says: "treat 1.0/3 as a int - don't use as much memory as you would with a double. Don't use floating point division, instead apply truncating integer division."" So we have 0 right? No.

System.out.println((int)1/3.0); // this prints: 0.3333333333333333

Ok so we have 0.33333 so the (int) cast is not evaluated 1st. It is as if it wasn't there. Let's propose a 3rd rule:

Rule 3: (int) expression means ignore the (int) cast altogether just evaluate the expression like the (int) isn't even there.

Ok applying rule 3 to the line below, we have (int) but we are just going to ignore it. 1.0/3.0 is evaluated with floating point division and we get 0.3333333. Success!

System.out.println((int)1.0/3.0); // this prints: 0.3333333333333333

And in the last line below, again we have (int) but we are just going to ignore it. (1.0/3) is evaluated using floating point division yielding 0.3333333333 right? No.

System.out.println((int)(1.0/3)); // this prints: 0

Ok now I'm confused. Please could you help me get my head round this?

like image 231
Literat Avatar asked Aug 04 '11 23:08

Literat


2 Answers

Java's language parsing rules are basically implying a lot of parentheses everywhere in your code based on its rules of order of operations. Understanding where Java thinks the parentheses are will help you understand this behavior. When you say:

(double) 1 / 3.0

... this is equivalent to saying:

((double) 1) / 3.0

This means that 1 gets converted to a double, and 3.0 is automatically a double, so you'll end up doing a floating point division rather than integer division. The same thing would happen with (double) 1 / 3, because the divisor is a double, so even though the dividend is an int, the system realizes that you want to do floating-point division. The compiler doesn't want you to lose precision unless you specifically ask to, so any time either the dividend or the divisor is a double, it will do double division.

On the other hand:

(int) 1 / 3.0

... is the same as saying:

((int) 1) / 3.0

In this case, you're telling the compiler what it already knew: that the divisor (1) is an int. Then you're asking it to divide by a double value (3.0). Since the dividend is a double, it will perform double division.

As you noted, 1/3 will produce zero, because that's how integer division works and both numbers are integers. The same will happen with 1/(int)3.0 because you're telling the compiler to make the 3.0 into an int before the division will occur (as in 1/((int)3.0)).

The final point to keep in mind is that (int) 0.33 will also get converted to 0, because an integer can't hold decimal values in it. So when you say (int)(1.0/3), you're doing double division, but then you're converting the double into an int afterwards, producing 0.

like image 83
StriplingWarrior Avatar answered Oct 04 '22 18:10

StriplingWarrior


This is specified in the Java Language Specification - Numeric Promotions

If any of the operands is of a reference type, unboxing conversion (§5.1.8) is performed.

Then:

If either operand is of type double, the other is converted to double.

Otherwise, if either operand is of type float, the other is converted to float.

Otherwise, if either operand is of type long, the other is converted to long.

Otherwise, both operands are converted to type int.

The concept is called widening conversion:

5.1.2 Widening Primitive Conversion

The following 19 specific conversions on primitive types are called the widening primitive conversions:

  • byte to short, int, long, float, or double
  • short to int, long, float, or double
  • char to int, long, float, or double
  • int to long, float, or double
  • long to float or double float to double

The reason why

(int) (1.0/3) == 0

is because you cast the result of 1.0/3 (0.33333... by the above rules) explicitly to an integer, which is equivalent to "floor(result)" (rounding down), so the result is 0.

Cast has a higher precedence than /, that's why the other operations were misleading to your understanding:

(double) 1 / 3

will explicitly cast 1 to a double value 1.0 first, so again the result by the above rules is equal to 0.3333....

like image 39
emboss Avatar answered Oct 04 '22 19:10

emboss