7

I think that question is pretty straight. but here is an examples.

Example below is OK. I can take rounding and no truncating was done here.

public static void main(String[] args) {
    double d = 9.9;
    long l = (long)d;
    System.out.println(l);
}

Output:

9

And now number out of range of long:

public static void main(String[] args) {
    double d = 99999999999999999999999999999999.9;
    long l = (long)d;
    System.out.println(l);
}

Output:

9223372036854775807

This one troubles me. I cannot continue work with completely different number. I would rather get an error or an exception.

Is there any way to detect this in Java?

arenaq
  • 2,304
  • 2
  • 24
  • 31

2 Answers2

12

You can compare it with Long.MIN_VALUE and Long.MAX_VALUE:

public static boolean fitsLong(double d) {
    return d >= Long.MIN_VALUE && d < Long.MAX_VALUE;
}

Somewhat more sofisticated approach is to use BigDecimal:

double value = 1234567.9;
long l = BigDecimal.valueOf(value)
                   .setScale(0, RoundingMode.HALF_EVEN)
                   .longValueExact(); // 1234568

double value = 99999999999999999999999999999999.9;
long l = BigDecimal.valueOf(value)
                   .setScale(0, RoundingMode.HALF_EVEN)
                   .longValueExact(); // ArithmeticException

This way you can control how the rounding is performed.

You may ask, why there's strict inequality in fitsLong: d < Long.MAX_VALUE. Actually that's because the Long.MAX_VALUE itself cannot be represented as double number. When you cast (double)Long.MAX_VALUE, there's not enough precision in double type to represent it, so the closest representable value is selected which is 9223372036854775808.0 (Long_MAX_VALUE+1.0). So were it d <= Long.MAX_VALUE it would return true for number which is actually a little bigger as in this comparison Long.MAX_VALUE constant is promoted to double type. On the other hand Long.MIN_VALUE can be exactly represented in double type, thus here we have >=.

Also it's interesting why the following works:

double value = -9223372036854775809.9; // Long.MIN_VALUE-1.9
System.out.println(fitsLong(value)); // returns true

That's because you actually did not subtract anything from the Long.MIN_VALUE. See:

double d1 = Long.MIN_VALUE;
double d2 = -9223372036854775809.9;
System.out.println(d1 == d2); // true

The double precision is not enough to distinguish between -9223372036854775808 and -9223372036854775809.9, so it's actually the same double number. During the compilation it's converted to binary form, and binary form for these two numbers is the same. Thus having compiled program you cannot distinguish whether -9223372036854775808 or -9223372036854775809.9 was in the source code.

If you feel that it's still the issue, construct the BigDecimal from the String:

long l = new BigDecimal("-9223372036854775808.2")
                   .setScale(0, RoundingMode.HALF_EVEN)
                   .longValueExact(); // ok, -9223372036854775808

long l = new BigDecimal("-9223372036854775808.9")
                   .setScale(0, RoundingMode.HALF_EVEN)
                   .longValueExact(); // ArithmeticException
Tagir Valeev
  • 97,161
  • 19
  • 222
  • 334
  • For the first solution) Actually you cannot. I tried it and it wont work. For example try a value 9223372036854775809.9 which is incremented Long.MAX_VALUE by 2.9. It will pass. – arenaq Sep 08 '15 at 12:03
  • You are right, but when I use 9223372036854775807.0 for both solutions. FitsLong gives me true and BigDecimal throws an exception. That double value may be unchanged, but at least one of the solutions has to be wrong. – arenaq Sep 08 '15 at 12:24
  • 1
    @arenaq double has 53 bits of mantissa and can be precise only to ~15-17 digits. There's no way for it to differentiate between 9223372036854775809.9 and 9223372036854775807 http://stackoverflow.com/q/588004/995714 – phuclv Sep 08 '15 at 12:31
  • 1
    @arenaq, that's a subtle question. As `Long.MAX_VALUE` cannot be exactly expressed in double and its canonical representation is a little bit bigger, thus probably you should exclude it from `fitsLong` (change `<=` to strict `<`). But on the other hand, you may consider this case as rounding, not the truncation (e.g. `System.out.println((long)1000000000000000010.0);` yields `1000000000000000000`, but this is rounding). – Tagir Valeev Sep 08 '15 at 12:32
  • I know, but that does not explain why these solutions behave differently for the same double. Try this code http://pastebin.com/FT7dtd5p – arenaq Sep 08 '15 at 12:33
  • 1
    @arenaq, because in comparison both numbers are promoted to `double` type, thus `Long.MAX_VALUE` becomes the same double number which is actually a little bigger, thus the comparison produces true. On the other hand, `BigDecimal.valueOf` works with exact canonical representation of the number and knows that it's bigger than `Long.MAX_VALUE`. – Tagir Valeev Sep 08 '15 at 12:35
  • @TagirValeev so that fitsLong solutions is wrong and BigDecimal is right? I am little confused... – arenaq Sep 08 '15 at 12:38
  • @arenaq, I edited the answer and explained this corner case, also replacing the rounding example with negative numbers as it's sounds more logical now. – Tagir Valeev Sep 08 '15 at 12:49
1

When you cast a floating point type to an int or long, the result is either the nearest integer (rounding towards zero), or the MIN_VALUE or MAX_VALUE for int or long. See JLS 5.1.3.

Hence, one alternative would be to do the typecast and then test for the appropriate MIN_VALUE or MAX_VALUE.

Note that Long.MAX_VALUE is 9223372036854775807 ... which is the number your test program outputs!

(However, this doesn't work if you are casting a floating point type to byte, char or short. See the link above for the explanation.)

Stephen C
  • 698,415
  • 94
  • 811
  • 1,216