Daniel
Daniel

Reputation: 6491

Java - ternary operator weird behaviour

I was trying to remove the fractional part from a double in case it is whole using:

(d % 1) == 0 ? d.intValue() : d

And encountered the following behavior which i don't understand:

public static void main(String[] args) {
    Double d = 5D;
    System.out.println((d % 1) == 0);                               // true
    System.out.println((d % 1) == 0 ? d.intValue() : "not whole");  // 5
    System.out.println((d % 1) == 0 ? d.intValue() : d);            // 5.0
}

As you can see on the third line, the operator chooses the else value - 5.0 even though the condition (d % 1) == 0 is met.

What's going on here?

Upvotes: 24

Views: 2133

Answers (4)

plugwash
plugwash

Reputation: 10523

In your case the second and third arguments of the ternery operator are types "int" and "Double". Java must convert these values to the same type so they can be returned from the ternary operator. The rules for doing this are given in the Java language specification. https://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.25

In your case these rules result in the conversion of both parameters to type "double" (the "Double" is unboxed, the int is value-converted).

The fix is to cast the arguments to the ternary operator so that they are of the same type (there may be more brackets in the below than strictly needed, i'm a bit rusty on java operator precedence rules).

System.out.println((d % 1) == 0 ? ((Number)(d.intValue())) : (Number)d); 

Upvotes: 1

celezar
celezar

Reputation: 442

It chooses correctly. Then it wraps it in double. These are 3 key points:

  1. If the second and third operands have the same type, that is the type of the conditional expression. In other words, you can avoid the whole mess by steering clear of mixed-type computation.

  2. If one of the operands is of type T where T is byte , short , or char and the other operand is a constant expression of type int whose value is representable in type T, the type of the conditional expression is T.

  3. Otherwise, binary numeric promotion is applied to the operand types, and the type of the conditional expression is the promoted type of the second and third operands.

Upvotes: 3

Eran
Eran

Reputation: 394146

The return type of the ternary conditional operator must be such that both the 2nd and 3rd operands can be assigned to it.

Therefore, in your second case, the return type of the operator is Object (since both d.intValue() and "not whole" must be assignable to it) while in the third case it is Double (since both d.intValue() and d must be assignable to it).

Printing an Object whose runtime type is Integer gives you 5 while printing a Double gives you 5.0.

Upvotes: 35

Peter Lawrey
Peter Lawrey

Reputation: 533880

The type of an expression a ? b : c is always the same as c or the closest common parent of b and c.

System.out.println((d % 1) == 0 ? d.intValue() : "not whole");  // Comparable a parent of Integer and String
System.out.println((d % 1) == 0 ? d.intValue() : d);            // Double is a widened int

BTW d % 1 will only check it is a whole not, not that it's small enough to fit in anint value. A safer check is to see if the value is the same when cast to an int or long

double d = 5.0;
if ((long) d == d)
    System.out.println((long) d);
else
    System.out.println(d);

or you can prevent it widening the long back to a double with

double d = 5.0;
System.out.println((long) d == d ? Long.toString((long) d) : Double.toString(d));

Upvotes: 11

Related Questions