23

I'm trying to write a method which gets a double, verifies if the number has something after the dot and if it does—returns a double, if doesn't—returns an int.

public class Solution {
    public static void main(String[] args) {
        double d = 3.000000000;
        System.out.println(convert1(d));
        System.out.println(convert2(d));
    }

    static Object convert1(double d) {
        if(d % 1 == 0)
            return (int) d;
        else
            return d;
    }

    static Object convert2(double d) {
        return ((d%1) == 0) ? ((int) (d)) : d;
    }
}

Output:

3
3.0

So, everything I want happens in method convert1(), but doesn't happen in method convert2(). It seems as these methods must do the same work. But what I have done wrong?

RamenChef
  • 5,557
  • 11
  • 31
  • 43
Lysenko Andrii
  • 496
  • 4
  • 11
  • 2
    Completely off-topic for this question but are you aware that due to floating point inaccuracy hardly any integer represented as a double will be exactly that integer value? That is, I expect you will get the `x.0` often when you wouldn't expect. – Vincent van der Weele Aug 10 '14 at 18:33
  • This operator is actually called the conditional operator, as shown in the answer by @Patrick Collins. – andy256 Aug 10 '14 at 23:52
  • 2
    @Heuster -This is a common misonception. Actually many integer values convert exactly to doubles. In particular all with absolute value below about 2^53 IIRC, which is more than all 32bit integers. Its only after some conversion or certain kinds of operation that you lose that precision. (Addition, subtraction and in-range multiplication are fine.) – Michael Anderson Aug 11 '14 at 02:28
  • @MichaelAnderson: Any double is either small enough that if it were an integer, it would be precise, or so large that its precision doesn't extend beyond the decimal point. IOW, losing precision on a large floating point number cannot make it into a non-integer. (And yes, the limit is 2^53). – rici Aug 11 '14 at 02:59
  • @MichealAnderson OK, maybe I phrased it a bit too strong, this does not occur with integer arithmetic only. But since it is an issue here to see if a number is integer or not, we can safely assume that the OP is not restricted to integers. And this problem will definitely happen for `10 * 0.1`, for instance. – Vincent van der Weele Aug 11 '14 at 08:25

4 Answers4

29

You're seeing an effect similar to the one in this question.

Slightly different rules govern the way Java handles types with the ternary operator than with an if statement.

Specifically, the standard says:

The type of a conditional expression is determined as follows:

...

Otherwise, if the second and third operands have types that are convertible (§5.1.8) to numeric types, then there are several cases:

...

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

Flipping to that page of the standard, we see:

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

which is what's happening here, followed by autoboxing to a Double. It appears that no such conversion happens with the if statement, explaining the difference.

More broadly --- this isn't a very good idea. I don't think it's good design to return one of an int or a double depending on the value -- if you want to round something off, use Math.floor, and if you don't want decimals printed, use printf.

EDIT: I don't think it's a good idea to do hacky things to circumvent the regular numeric conversion system. Here's an idea that gives you a String directly, which appears to be what you want:

static String convert3(double d) {
    return ((d % 1 == 0) ? Integer.toString((int)d) : Double.toString(d));
}
Community
  • 1
  • 1
Patrick Collins
  • 10,306
  • 5
  • 30
  • 69
  • Thanks for a very detailed answer. I need this method for GUI, thus I can't use neither **Mats.floor** nor **printf**. – Lysenko Andrii Aug 10 '14 at 16:24
  • @user3922109 What's your use-case, exactly? Converting to one of two different numeric types is almost certainly not the way to go. Is what you need a string either with a decimal or without? – Patrick Collins Aug 10 '14 at 16:26
  • @user3922109 I added in an example to convert it directly to a `String`, which seems to be what you're looking for. – Patrick Collins Aug 10 '14 at 16:32
  • +1 for `this isn't a very good idea. I don't think it's good design to return one of an int or a double depending on the value` - it was the first thing that came to my mind when I saw OP's example –  Aug 10 '14 at 17:06
  • 1
    @vaxquis Yes, it seems like the OP is overwhelmingly likely to get bitten (again) by numeric promotion rules again. If he needs a string, he should use a string. – Patrick Collins Aug 10 '14 at 17:15
18

As the other answers have stated, this behavior is because both possible results of a ternary expression must have the same type.

Therefore, all you have to do to make your ternary version work the same way as convert1() is to cast the int to an Object:

static Object convert2(double d) {
    return ((d % 1) == 0) ? ((Object) (int) (d)) : d;
}
Keppil
  • 45,603
  • 8
  • 97
  • 119
  • 6
    You could do slightly better and cast to a `Number`, right? – Chris Hayes Aug 10 '14 at 21:56
  • @Chris: Yeah, I merely showed how the mechanics would work to make it work the same way as `convert1()`, which returns `Object`. A `Number` or a `String` are probably more logical return values for a method like this. – Keppil Aug 11 '14 at 04:22
11

The ternary operator requires both result values be the same type, so the int undergoes an automatic (safe) widening cast to double.

The ternary is not exactly the same as its if "equivalent".

Bohemian
  • 412,405
  • 93
  • 575
  • 722
0

To solve the problem with numbers after the dot:

public Object convert(double number){
  double whole = Math.floor(number);
  if(Math.abs(whole - number) < DELTA){
    return (int) number;
  }
  return number;
}

The DELTA is sufficiently small constant, to solve the problem with integers encoded in floating point format.

I have written the code from memory, but I think the idea behind it is clear.

jnovacho
  • 2,825
  • 6
  • 27
  • 44